chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using Accord.Math;
|
||||
using Accord.Statistics;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Consolidators;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Indicators;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp.Alphas
|
||||
{
|
||||
/// <summary>
|
||||
/// Energy prices, especially Oil and Natural Gas, are in general fairly correlated,
|
||||
/// meaning they typically move in the same direction as an overall trend.This Alpha
|
||||
/// uses this idea and implements an Alpha Model that takes Natural Gas ETF price
|
||||
/// movements as a leading indicator for Crude Oil ETF price movements.We take the
|
||||
/// Natural Gas/Crude Oil ETF pair with the highest historical price correlation and
|
||||
/// then create insights for Crude Oil depending on whether or not the Natural Gas ETF price change
|
||||
/// is above/below a certain threshold that we set (arbitrarily).
|
||||
///
|
||||
/// This alpha is part of the Benchmark Alpha Series created by QuantConnect which are open
|
||||
/// sourced so the community and client funds can see an example of an alpha.
|
||||
///</summary>
|
||||
public class GasAndCrudeOilEnergyCorrelationAlpha : QCAlgorithm
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2018, 1, 1);
|
||||
SetCash(100000);
|
||||
|
||||
// Set zero transaction fees
|
||||
SetSecurityInitializer(security => security.FeeModel = new ConstantFeeModel(0));
|
||||
|
||||
Func<string, Symbol> ToSymbol = x => QuantConnect.Symbol.Create(x, SecurityType.Equity, Market.USA);
|
||||
var naturalGas = new[] { "UNG", "BOIL", "FCG" }.Select(ToSymbol).ToArray();
|
||||
var crudeOil = new[] { "USO", "UCO", "DBO" }.Select(ToSymbol).ToArray();
|
||||
|
||||
// Manually curated universe
|
||||
UniverseSettings.Resolution = Resolution.Minute;
|
||||
SetUniverseSelection(new ManualUniverseSelectionModel(naturalGas.Concat(crudeOil)));
|
||||
|
||||
// Use PairsAlphaModel to establish insights
|
||||
SetAlpha(new PairsAlphaModel(naturalGas, crudeOil, 90, Resolution.Minute));
|
||||
|
||||
// Equally weigh securities in portfolio, based on insights
|
||||
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
|
||||
|
||||
// Set Custom Execution Model
|
||||
SetExecution(new CustomExecutionModel());
|
||||
|
||||
// Set Null Risk Management Model
|
||||
SetRiskManagement(new NullRiskManagementModel());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This Alpha model assumes that the ETF for natural gas is a good leading-indicator
|
||||
/// of the price of the crude oil ETF.The model will take in arguments for a threshold
|
||||
/// at which the model triggers an insight, the length of the look-back period for evaluating
|
||||
/// rate-of-change of UNG prices, and the duration of the insight
|
||||
/// </summary>
|
||||
private class PairsAlphaModel : AlphaModel
|
||||
{
|
||||
private readonly Symbol[] _leading;
|
||||
private readonly Symbol[] _following;
|
||||
private readonly int _historyDays;
|
||||
private readonly int _lookback;
|
||||
private readonly decimal _differenceTrigger = 0.75m;
|
||||
private readonly Resolution _resolution;
|
||||
private readonly TimeSpan _predictionInterval;
|
||||
private readonly Dictionary<Symbol, SymbolData> _symbolDataBySymbol;
|
||||
private Tuple<SymbolData, SymbolData> _pair;
|
||||
|
||||
private DateTime _nextUpdate;
|
||||
|
||||
public PairsAlphaModel(
|
||||
Symbol[] naturalGas,
|
||||
Symbol[] crudeOil,
|
||||
int historyDays = 90,
|
||||
Resolution resolution = Resolution.Hour,
|
||||
int lookback = 5,
|
||||
decimal differenceTrigger = 0.75m)
|
||||
{
|
||||
_leading = naturalGas;
|
||||
_following = crudeOil;
|
||||
_historyDays = historyDays;
|
||||
_resolution = resolution;
|
||||
_lookback = lookback;
|
||||
_differenceTrigger = differenceTrigger;
|
||||
_symbolDataBySymbol = new Dictionary<Symbol, SymbolData>();
|
||||
_predictionInterval = resolution.ToTimeSpan().Multiply(lookback);
|
||||
}
|
||||
|
||||
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
|
||||
{
|
||||
if (_nextUpdate == DateTime.MinValue || algorithm.Time > _nextUpdate)
|
||||
{
|
||||
CorrelationPairsSelection();
|
||||
_nextUpdate = algorithm.Time.AddDays(30);
|
||||
}
|
||||
|
||||
var magnitude = (double)Math.Round(_pair.Item1.Return / 100, 6);
|
||||
|
||||
if (_pair.Item1.Return > _differenceTrigger)
|
||||
{
|
||||
yield return Insight.Price(_pair.Item2.Symbol, _predictionInterval, InsightDirection.Up, magnitude);
|
||||
}
|
||||
if (_pair.Item1.Return < -_differenceTrigger)
|
||||
{
|
||||
yield return Insight.Price(_pair.Item2.Symbol, _predictionInterval, InsightDirection.Down, magnitude);
|
||||
}
|
||||
}
|
||||
|
||||
public void CorrelationPairsSelection()
|
||||
{
|
||||
var maxCorrelation = -1.0;
|
||||
var matrix = new double[_historyDays, _following.Length + 1];
|
||||
|
||||
// Get returns for each oil ETF
|
||||
for (var j = 0; j < _following.Length; j++)
|
||||
{
|
||||
SymbolData symbolData2;
|
||||
if (_symbolDataBySymbol.TryGetValue(_following[j], out symbolData2))
|
||||
{
|
||||
var dailyReturn2 = symbolData2.DailyReturnArray;
|
||||
for (var i = 0; i < _historyDays; i++)
|
||||
{
|
||||
matrix[i, j + 1] = symbolData2.DailyReturnArray[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns for each natural gas ETF
|
||||
for (var j = 0; j < _leading.Length; j++)
|
||||
{
|
||||
SymbolData symbolData1;
|
||||
if (_symbolDataBySymbol.TryGetValue(_leading[j], out symbolData1))
|
||||
{
|
||||
for (var i = 0; i < _historyDays; i++)
|
||||
{
|
||||
matrix[i, 0] = symbolData1.DailyReturnArray[i];
|
||||
}
|
||||
|
||||
var column = matrix.Correlation().GetColumn(0);
|
||||
var correlation = column.RemoveAt(0).Max();
|
||||
|
||||
// Calculate the pair with highest historical correlation
|
||||
if (correlation > maxCorrelation)
|
||||
{
|
||||
var maxIndex = column.IndexOf(correlation) - 1;
|
||||
if (maxIndex < 0) continue;
|
||||
var symbolData2 = _symbolDataBySymbol[_following[maxIndex]];
|
||||
_pair = Tuple.Create(symbolData1, symbolData2);
|
||||
maxCorrelation = correlation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
|
||||
{
|
||||
foreach (var removed in changes.RemovedSecurities)
|
||||
{
|
||||
if (_symbolDataBySymbol.ContainsKey(removed.Symbol))
|
||||
{
|
||||
_symbolDataBySymbol[removed.Symbol].RemoveConsolidators(algorithm);
|
||||
_symbolDataBySymbol.Remove(removed.Symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize data for added securities
|
||||
var symbols = changes.AddedSecurities.Select(x => x.Symbol);
|
||||
var dailyHistory = algorithm.History(symbols, _historyDays + 1, Resolution.Daily);
|
||||
if (symbols.Count() > 0 && dailyHistory.Count() == 0)
|
||||
{
|
||||
algorithm.Debug($"{algorithm.Time} :: No daily data");
|
||||
}
|
||||
|
||||
dailyHistory.PushThrough(bar =>
|
||||
{
|
||||
SymbolData symbolData;
|
||||
if (!_symbolDataBySymbol.TryGetValue(bar.Symbol, out symbolData))
|
||||
{
|
||||
symbolData = new SymbolData(algorithm, bar.Symbol, _historyDays, _lookback, _resolution);
|
||||
_symbolDataBySymbol.Add(bar.Symbol, symbolData);
|
||||
}
|
||||
// Update daily rate of change indicator
|
||||
symbolData.UpdateDailyRateOfChange(bar);
|
||||
});
|
||||
|
||||
algorithm.History(symbols, _lookback, _resolution).PushThrough(bar =>
|
||||
{
|
||||
// Update rate of change indicator with given resolution
|
||||
if (_symbolDataBySymbol.ContainsKey(bar.Symbol))
|
||||
{
|
||||
_symbolDataBySymbol[bar.Symbol].UpdateRateOfChange(bar);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains data specific to a symbol required by this model
|
||||
/// </summary>
|
||||
private class SymbolData
|
||||
{
|
||||
private readonly RateOfChangePercent _dailyReturn;
|
||||
private readonly IDataConsolidator _dailyConsolidator;
|
||||
private readonly RollingWindow<IndicatorDataPoint> _dailyReturnHistory;
|
||||
private readonly IDataConsolidator _consolidator;
|
||||
|
||||
public Symbol Symbol { get; }
|
||||
|
||||
public RateOfChangePercent Return { get; }
|
||||
|
||||
public double[] DailyReturnArray => _dailyReturnHistory
|
||||
.OrderBy(x => x.EndTime)
|
||||
.Select(x => (double)x.Value).ToArray();
|
||||
|
||||
public SymbolData(QCAlgorithm algorithm, Symbol symbol, int dailyLookback, int lookback, Resolution resolution)
|
||||
{
|
||||
Symbol = symbol;
|
||||
|
||||
_dailyReturn = new RateOfChangePercent($"{symbol}.DailyROCP(1)", 1);
|
||||
_dailyConsolidator = algorithm.ResolveConsolidator(symbol, Resolution.Daily);
|
||||
_dailyReturnHistory = new RollingWindow<IndicatorDataPoint>(dailyLookback);
|
||||
_dailyReturn.Updated += (s, e) => _dailyReturnHistory.Add(e);
|
||||
algorithm.RegisterIndicator(symbol, _dailyReturn, _dailyConsolidator);
|
||||
|
||||
Return = new RateOfChangePercent($"{symbol}.ROCP({lookback})", lookback);
|
||||
_consolidator = algorithm.ResolveConsolidator(symbol, resolution);
|
||||
algorithm.RegisterIndicator(symbol, Return, _consolidator);
|
||||
}
|
||||
|
||||
public void RemoveConsolidators(QCAlgorithm algorithm)
|
||||
{
|
||||
algorithm.SubscriptionManager.RemoveConsolidator(Symbol, _consolidator);
|
||||
algorithm.SubscriptionManager.RemoveConsolidator(Symbol, _dailyConsolidator);
|
||||
}
|
||||
|
||||
public void UpdateRateOfChange(BaseData data)
|
||||
{
|
||||
Return.Update(data.EndTime, data.Value);
|
||||
}
|
||||
|
||||
internal void UpdateDailyRateOfChange(BaseData data)
|
||||
{
|
||||
_dailyReturn.Update(data.EndTime, data.Value);
|
||||
}
|
||||
|
||||
public override string ToString() => Return.ToDetailedString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides an implementation of IExecutionModel that immediately submits market orders to achieve the desired portfolio targets
|
||||
/// </summary>
|
||||
private class CustomExecutionModel : ExecutionModel
|
||||
{
|
||||
private readonly PortfolioTargetCollection _targetsCollection = new PortfolioTargetCollection();
|
||||
private Symbol _previousSymbol;
|
||||
|
||||
/// <summary>
|
||||
/// Immediately submits orders for the specified portfolio targets.
|
||||
/// </summary>
|
||||
/// <param name="algorithm">The algorithm instance</param>
|
||||
/// <param name="targets">The portfolio targets to be ordered</param>
|
||||
public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
|
||||
{
|
||||
_targetsCollection.AddRange(targets);
|
||||
|
||||
foreach (var target in _targetsCollection.OrderByMarginImpact(algorithm))
|
||||
{
|
||||
var openQuantity = algorithm.Transactions.GetOpenOrders(target.Symbol)
|
||||
.Sum(x => x.Quantity);
|
||||
var existing = algorithm.Securities[target.Symbol].Holdings.Quantity + openQuantity;
|
||||
var quantity = target.Quantity - existing;
|
||||
|
||||
// Liquidate positions in Crude Oil ETF that is no longer part of the highest-correlation pair
|
||||
if (_previousSymbol != null && target.Symbol != _previousSymbol)
|
||||
{
|
||||
algorithm.Liquidate(_previousSymbol);
|
||||
}
|
||||
if (quantity != 0)
|
||||
{
|
||||
algorithm.MarketOrder(target.Symbol, quantity);
|
||||
_previousSymbol = target.Symbol;
|
||||
}
|
||||
}
|
||||
_targetsCollection.ClearFulfilled(algorithm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp.Alphas
|
||||
{
|
||||
/// <summary>
|
||||
/// Equity indices exhibit mean reversion in daily returns. The Internal Bar Strength indicator (IBS),
|
||||
/// which relates the closing price of a security to its daily range can be used to identify overbought
|
||||
/// and oversold securities.
|
||||
///
|
||||
/// This alpha ranks 33 global equity ETFs on its IBS value the previous day and predicts for the following day
|
||||
/// that the ETF with the highest IBS value will decrease in price, and the ETF with the lowest IBS value
|
||||
/// will increase in price.
|
||||
///
|
||||
/// Source: Kakushadze, Zura, and Juan Andrés Serur. “4. Exchange-Traded Funds (ETFs).” 151 Trading Strategies, Palgrave Macmillan, 2018, pp. 90–91.
|
||||
///
|
||||
/// This alpha is part of the Benchmark Alpha Series created by QuantConnect which are open sourced so the community and client funds can see an example of an alpha.
|
||||
///</summary>
|
||||
public class GlobalEquityMeanReversionIBSAlpha : QCAlgorithm
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2018, 1, 1);
|
||||
SetCash(100000);
|
||||
|
||||
// Set zero transaction fees
|
||||
SetSecurityInitializer(security => security.FeeModel = new ConstantFeeModel(0));
|
||||
|
||||
// Global Equity ETF tickers
|
||||
var symbols = new[] {
|
||||
"ECH", "EEM", "EFA", "EPHE", "EPP", "EWA", "EWC", "EWG",
|
||||
"EWH", "EWI", "EWJ", "EWL", "EWM", "EWM", "EWO", "EWP",
|
||||
"EWQ", "EWS", "EWT", "EWU", "EWY", "EWZ", "EZA", "FXI",
|
||||
"GXG", "IDX", "ILF", "EWM", "QQQ", "RSX", "SPY", "THD"}
|
||||
.Select(x => QuantConnect.Symbol.Create(x, SecurityType.Equity, Market.USA));
|
||||
|
||||
// Manually curated universe
|
||||
UniverseSettings.Resolution = Resolution.Daily;
|
||||
SetUniverseSelection(new ManualUniverseSelectionModel(symbols));
|
||||
|
||||
// Use MeanReversionIBSAlphaModel to establish insights
|
||||
SetAlpha(new MeanReversionIBSAlphaModel());
|
||||
|
||||
// Equally weigh securities in portfolio, based on insights
|
||||
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
|
||||
|
||||
// Set Immediate Execution Model
|
||||
SetExecution(new ImmediateExecutionModel());
|
||||
|
||||
// Set Null Risk Management Model
|
||||
SetRiskManagement(new NullRiskManagementModel());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses ranking of Internal Bar Strength (IBS) to create direction prediction for insights
|
||||
/// </summary>
|
||||
private class MeanReversionIBSAlphaModel : AlphaModel
|
||||
{
|
||||
private readonly int _numberOfStocks;
|
||||
private readonly TimeSpan _predictionInterval;
|
||||
|
||||
public MeanReversionIBSAlphaModel(
|
||||
int lookback = 1,
|
||||
int numberOfStocks = 2,
|
||||
Resolution resolution = Resolution.Daily)
|
||||
{
|
||||
_numberOfStocks = numberOfStocks;
|
||||
_predictionInterval = resolution.ToTimeSpan().Multiply(lookback);
|
||||
}
|
||||
|
||||
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
|
||||
{
|
||||
var symbolsIBS = new Dictionary<Symbol, decimal>();
|
||||
var returns = new Dictionary<Symbol, decimal>();
|
||||
|
||||
foreach (var kvp in algorithm.ActiveSecurities)
|
||||
{
|
||||
var security = kvp.Value;
|
||||
if (security.HasData)
|
||||
{
|
||||
var high = security.High;
|
||||
var low = security.Low;
|
||||
var hilo = high - low;
|
||||
|
||||
// Do not consider symbol with zero open and avoid division by zero
|
||||
if (security.Open * hilo != 0)
|
||||
{
|
||||
// Internal bar strength (IBS)
|
||||
symbolsIBS.Add(security.Symbol, (security.Close - low) / hilo);
|
||||
returns.Add(security.Symbol, security.Close / security.Open - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var insights = new List<Insight>();
|
||||
|
||||
// Number of stocks cannot be higher than half of symbolsIBS length
|
||||
var numberOfStocks = Math.Min((int)(symbolsIBS.Count / 2.0), _numberOfStocks);
|
||||
if (numberOfStocks == 0)
|
||||
{
|
||||
return insights;
|
||||
}
|
||||
|
||||
// Rank securities with the highest IBS value
|
||||
var ordered = from entry in symbolsIBS
|
||||
orderby Math.Round(entry.Value, 6) descending, entry.Key
|
||||
select entry;
|
||||
var highIBS = ordered.Take(numberOfStocks); // Get highest IBS
|
||||
var lowIBS = ordered.Reverse().Take(numberOfStocks); // Get lowest IBS
|
||||
|
||||
// Emit "down" insight for the securities with the highest IBS value
|
||||
foreach (var kvp in highIBS)
|
||||
{
|
||||
insights.Add(Insight.Price(kvp.Key, _predictionInterval, InsightDirection.Down, Math.Abs((double)returns[kvp.Key])));
|
||||
}
|
||||
|
||||
// Emit "up" insight for the securities with the highest IBS value
|
||||
foreach (var kvp in lowIBS)
|
||||
{
|
||||
insights.Add(Insight.Price(kvp.Key, _predictionInterval, InsightDirection.Up, Math.Abs((double)returns[kvp.Key])));
|
||||
}
|
||||
|
||||
return insights;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Consolidators;
|
||||
using QuantConnect.Data.Fundamental;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Indicators;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp.Alphas
|
||||
{
|
||||
/// <summary>
|
||||
/// This alpha picks stocks according to Joel Greenblatt's Magic Formula.
|
||||
/// First, each stock is ranked depending on the relative value of the ratio EV/EBITDA. For example, a stock
|
||||
/// that has the lowest EV/EBITDA ratio in the security universe receives a score of one while a stock that has
|
||||
/// the tenth lowest EV/EBITDA score would be assigned 10 points.
|
||||
///
|
||||
/// Then, each stock is ranked and given a score for the second valuation ratio, Return on Capital (ROC).
|
||||
/// Similarly, a stock that has the highest ROC value in the universe gets one score point.
|
||||
/// The stocks that receive the lowest combined score are chosen for insights.
|
||||
///
|
||||
/// Source: Greenblatt, J. (2010) The Little Book That Beats the Market
|
||||
///
|
||||
/// This alpha is part of the Benchmark Alpha Series created by QuantConnect which are open
|
||||
/// sourced so the community and client funds can see an example of an alpha.
|
||||
///</summary>
|
||||
public class GreenblattMagicFormulaAlpha : QCAlgorithm
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2018, 1, 1);
|
||||
SetCash(100000);
|
||||
|
||||
// Set zero transaction fees
|
||||
SetSecurityInitializer(security => security.FeeModel = new ConstantFeeModel(0));
|
||||
|
||||
// Select stocks using MagicFormulaUniverseSelectionModel
|
||||
SetUniverseSelection(new GreenBlattMagicFormulaUniverseSelectionModel());
|
||||
|
||||
// Use RateOfChangeAlphaModel to establish insights
|
||||
SetAlpha(new RateOfChangeAlphaModel());
|
||||
|
||||
// Equally weigh securities in portfolio, based on insights
|
||||
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
|
||||
|
||||
// Set Immediate Execution Model
|
||||
SetExecution(new ImmediateExecutionModel());
|
||||
|
||||
// Set Null Risk Management Model
|
||||
SetRiskManagement(new NullRiskManagementModel());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses Rate of Change (ROC) to create magnitude prediction for insights.
|
||||
/// </summary>
|
||||
private class RateOfChangeAlphaModel : AlphaModel
|
||||
{
|
||||
private readonly int _lookback;
|
||||
private readonly Resolution _resolution;
|
||||
private readonly TimeSpan _predictionInterval;
|
||||
private readonly Dictionary<Symbol, SymbolData> _symbolDataBySymbol;
|
||||
|
||||
public RateOfChangeAlphaModel(
|
||||
int lookback = 1,
|
||||
Resolution resolution = Resolution.Daily)
|
||||
{
|
||||
_lookback = lookback;
|
||||
_resolution = resolution;
|
||||
_predictionInterval = resolution.ToTimeSpan().Multiply(lookback);
|
||||
_symbolDataBySymbol = new Dictionary<Symbol, SymbolData>();
|
||||
}
|
||||
|
||||
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
|
||||
{
|
||||
var insights = new List<Insight>();
|
||||
|
||||
foreach (var kvp in _symbolDataBySymbol)
|
||||
{
|
||||
var symbolData = kvp.Value;
|
||||
if (symbolData.CanEmit)
|
||||
{
|
||||
var magnitude = Convert.ToDouble(Math.Abs(symbolData.Return));
|
||||
insights.Add(Insight.Price(kvp.Key, _predictionInterval, InsightDirection.Up, magnitude));
|
||||
}
|
||||
}
|
||||
|
||||
return insights;
|
||||
}
|
||||
|
||||
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
|
||||
{
|
||||
// Clean up data for removed securities
|
||||
foreach (var removed in changes.RemovedSecurities)
|
||||
{
|
||||
SymbolData symbolData;
|
||||
if (_symbolDataBySymbol.TryGetValue(removed.Symbol, out symbolData))
|
||||
{
|
||||
symbolData.RemoveConsolidators(algorithm);
|
||||
_symbolDataBySymbol.Remove(removed.Symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize data for added securities
|
||||
var symbols = changes.AddedSecurities.Select(x => x.Symbol);
|
||||
var history = algorithm.History(symbols, _lookback, _resolution);
|
||||
if (symbols.Count() == 0 && history.Count() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
history.PushThrough(bar =>
|
||||
{
|
||||
SymbolData symbolData;
|
||||
if (!_symbolDataBySymbol.TryGetValue(bar.Symbol, out symbolData))
|
||||
{
|
||||
symbolData = new SymbolData(algorithm, bar.Symbol, _lookback, _resolution);
|
||||
_symbolDataBySymbol[bar.Symbol] = symbolData;
|
||||
}
|
||||
symbolData.WarmUpIndicators(bar);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains data specific to a symbol required by this model
|
||||
/// </summary>
|
||||
private class SymbolData
|
||||
{
|
||||
private readonly Symbol _symbol;
|
||||
private readonly IDataConsolidator _consolidator;
|
||||
private long _previous = 0;
|
||||
|
||||
public RateOfChange Return { get; }
|
||||
|
||||
public bool CanEmit
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_previous == Return.Samples)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_previous = Return.Samples;
|
||||
return Return.IsReady;
|
||||
}
|
||||
}
|
||||
|
||||
public SymbolData(QCAlgorithm algorithm, Symbol symbol, int lookback, Resolution resolution)
|
||||
{
|
||||
_symbol = symbol;
|
||||
Return = new RateOfChange($"{symbol}.ROC({lookback})", lookback);
|
||||
_consolidator = algorithm.ResolveConsolidator(symbol, resolution);
|
||||
algorithm.RegisterIndicator(symbol, Return, _consolidator);
|
||||
}
|
||||
|
||||
internal void RemoveConsolidators(QCAlgorithm algorithm)
|
||||
{
|
||||
algorithm.SubscriptionManager.RemoveConsolidator(_symbol, _consolidator);
|
||||
}
|
||||
|
||||
internal void WarmUpIndicators(BaseData bar)
|
||||
{
|
||||
Return.Update(bar.EndTime, bar.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a universe according to Joel Greenblatt's Magic Formula, as a universe selection model for the framework algorithm.
|
||||
/// From the universe QC500, stocks are ranked using the valuation ratios, Enterprise Value to EBITDA(EV/EBITDA) and Return on Assets(ROA).
|
||||
/// </summary>
|
||||
private class GreenBlattMagicFormulaUniverseSelectionModel : FundamentalUniverseSelectionModel
|
||||
{
|
||||
private const int _numberOfSymbolsCoarse = 500;
|
||||
private const int _numberOfSymbolsFine = 20;
|
||||
private const int _numberOfSymbolsInPortfolio = 10;
|
||||
private int _lastMonth = -1;
|
||||
private Dictionary<Symbol, double> _dollarVolumeBySymbol;
|
||||
|
||||
public GreenBlattMagicFormulaUniverseSelectionModel() : base(true)
|
||||
{
|
||||
_dollarVolumeBySymbol = new ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs coarse selection for constituents.
|
||||
/// The stocks must have fundamental data
|
||||
/// The stock must have positive previous-day close price
|
||||
/// The stock must have positive volume on the previous trading day
|
||||
/// </summary>
|
||||
public override IEnumerable<Symbol> SelectCoarse(QCAlgorithm algorithm, IEnumerable<CoarseFundamental> coarse)
|
||||
{
|
||||
if (algorithm.Time.Month == _lastMonth)
|
||||
{
|
||||
return algorithm.Universe.Unchanged;
|
||||
}
|
||||
_lastMonth = algorithm.Time.Month;
|
||||
|
||||
_dollarVolumeBySymbol = (
|
||||
from cf in coarse
|
||||
where cf.HasFundamentalData
|
||||
orderby cf.DollarVolume descending
|
||||
select new { cf.Symbol, cf.DollarVolume }
|
||||
)
|
||||
.Take(_numberOfSymbolsCoarse)
|
||||
.ToDictionary(x => x.Symbol, x => x.DollarVolume);
|
||||
|
||||
return _dollarVolumeBySymbol.Keys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// QC500: Performs fine selection for the coarse selection constituents
|
||||
/// The company's headquarter must in the U.S.
|
||||
/// The stock must be traded on either the NYSE or NASDAQ
|
||||
/// At least half a year since its initial public offering
|
||||
/// The stock's market cap must be greater than 500 million
|
||||
///
|
||||
/// Magic Formula: Rank stocks by Enterprise Value to EBITDA(EV/EBITDA)
|
||||
/// Rank subset of previously ranked stocks(EV/EBITDA), using the valuation ratio Return on Assets(ROA)
|
||||
/// </summary>
|
||||
public override IEnumerable<Symbol> SelectFine(QCAlgorithm algorithm, IEnumerable<FineFundamental> fine)
|
||||
{
|
||||
var filteredFine =
|
||||
from x in fine
|
||||
where x.CompanyReference.CountryId == "USA"
|
||||
where x.CompanyReference.PrimaryExchangeID == "NYS" || x.CompanyReference.PrimaryExchangeID == "NAS"
|
||||
where (algorithm.Time - x.SecurityReference.IPODate).TotalDays > 180
|
||||
where x.EarningReports.BasicAverageShares.ThreeMonths * x.EarningReports.BasicEPS.TwelveMonths * x.ValuationRatios.PERatio > 5e8
|
||||
select x;
|
||||
|
||||
double count = filteredFine.Count();
|
||||
if (count == 0)
|
||||
{
|
||||
return Enumerable.Empty<Symbol>();
|
||||
}
|
||||
|
||||
var percent = _numberOfSymbolsFine / count;
|
||||
|
||||
// Select stocks with top dollar volume in every single sector
|
||||
var myDict = (
|
||||
from x in filteredFine
|
||||
group x by x.CompanyReference.IndustryTemplateCode into g
|
||||
let y = (
|
||||
from item in g
|
||||
orderby _dollarVolumeBySymbol[item.Symbol] descending
|
||||
select item
|
||||
)
|
||||
let c = (int)Math.Ceiling(y.Count() * percent)
|
||||
select new { g.Key, Value = y.Take(c) }
|
||||
)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
// Stocks in QC500 universe
|
||||
var topFine = myDict.Values.SelectMany(x => x);
|
||||
|
||||
// Magic Formula:
|
||||
// Rank stocks by Enterprise Value to EBITDA (EV/EBITDA)
|
||||
// Rank subset of previously ranked stocks (EV/EBITDA), using the valuation ratio Return on Assets (ROA)
|
||||
return topFine
|
||||
// Sort stocks in the security universe of QC500 based on Enterprise Value to EBITDA valuation ratio
|
||||
.OrderByDescending(x => x.ValuationRatios.EVToEBITDA)
|
||||
.Take(_numberOfSymbolsFine)
|
||||
// sort subset of stocks that have been sorted by Enterprise Value to EBITDA, based on the valuation ratio Return on Assets (ROA)
|
||||
.OrderByDescending(x => x.ValuationRatios.ForwardROA)
|
||||
.Take(_numberOfSymbolsInPortfolio)
|
||||
.Select(x => x.Symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Indicators;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp.Alphas
|
||||
{
|
||||
/// <summary>
|
||||
/// Reversal strategy that goes long when price crosses below SMA and Short when price crosses above SMA.
|
||||
/// The trading strategy is implemented only between 10AM - 3PM (NY time). Research suggests this is due to
|
||||
/// institutional trades during market hours which need hedging with the USD. Source paper:
|
||||
/// LeBaron, Zhao: Intraday Foreign Exchange Reversals
|
||||
/// http://people.brandeis.edu/~blebaron/wps/fxnyc.pdf
|
||||
/// http://www.fma.org/Reno/Papers/ForeignExchangeReversalsinNewYorkTime.pdf
|
||||
///
|
||||
/// This alpha is part of the Benchmark Alpha Series created by QuantConnect which are open sourced so the community and client funds can see an example of an alpha.
|
||||
///</summary>
|
||||
public class IntradayReversalCurrencyMarketsAlpha : QCAlgorithm
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2015, 1, 1);
|
||||
SetCash(100000);
|
||||
|
||||
// Set zero transaction fees
|
||||
SetSecurityInitializer(security => security.FeeModel = new ConstantFeeModel(0));
|
||||
|
||||
// Select resolution
|
||||
var resolution = Resolution.Hour;
|
||||
|
||||
// Reversion on the USD.
|
||||
var symbols = new[] { QuantConnect.Symbol.Create("EURUSD", SecurityType.Forex, Market.Oanda) };
|
||||
|
||||
// Set requested data resolution
|
||||
UniverseSettings.Resolution = resolution;
|
||||
SetUniverseSelection(new ManualUniverseSelectionModel(symbols));
|
||||
|
||||
// Use IntradayReversalAlphaModel to establish insights
|
||||
SetAlpha(new IntradayReversalAlphaModel(5, resolution));
|
||||
|
||||
// Equally weigh securities in portfolio, based on insights
|
||||
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
|
||||
|
||||
// Set Immediate Execution Model
|
||||
SetExecution(new ImmediateExecutionModel());
|
||||
|
||||
// Set Null Risk Management Model
|
||||
SetRiskManagement(new NullRiskManagementModel());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alpha model that uses a Price/SMA Crossover to create insights on Hourly Frequency.
|
||||
/// Frequency: Hourly data with 5-hour simple moving average.
|
||||
/// Strategy:
|
||||
/// Reversal strategy that goes Long when price crosses below SMA and Short when price crosses above SMA.
|
||||
/// The trading strategy is implemented only between 10AM - 3PM (NY time)
|
||||
/// </summary>
|
||||
private class IntradayReversalAlphaModel : AlphaModel
|
||||
{
|
||||
private readonly int _periodSma;
|
||||
private readonly Resolution _resolution;
|
||||
private readonly Dictionary<Symbol, SymbolData> _cache;
|
||||
|
||||
public IntradayReversalAlphaModel(
|
||||
int periodSma = 5,
|
||||
Resolution resolution = Resolution.Hour)
|
||||
{
|
||||
_periodSma = periodSma;
|
||||
_resolution = resolution;
|
||||
_cache = new Dictionary<Symbol, SymbolData>();
|
||||
Name = "IntradayReversalAlphaModel";
|
||||
}
|
||||
|
||||
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
|
||||
{
|
||||
// Set the time to close all positions at 3PM
|
||||
var timeToClose = algorithm.Time.Date.Add(new TimeSpan(0, 15, 1, 0));
|
||||
|
||||
var insights = new List<Insight>();
|
||||
|
||||
foreach (var kvp in algorithm.ActiveSecurities)
|
||||
{
|
||||
var symbol = kvp.Key;
|
||||
|
||||
SymbolData symbolData;
|
||||
|
||||
if (ShouldEmitInsight(algorithm, symbol) &&
|
||||
_cache.TryGetValue(symbol, out symbolData))
|
||||
{
|
||||
var price = kvp.Value.Price;
|
||||
|
||||
var direction = symbolData.IsUptrend(price)
|
||||
? InsightDirection.Up
|
||||
: InsightDirection.Down;
|
||||
|
||||
// Ignore signal for same direction as previous signal (when no crossover)
|
||||
if (direction == symbolData.PreviousDirection)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save the current Insight Direction to check when the crossover happens
|
||||
symbolData.PreviousDirection = direction;
|
||||
|
||||
// Generate insight
|
||||
insights.Add(Insight.Price(symbol, timeToClose, direction));
|
||||
}
|
||||
}
|
||||
|
||||
return insights;
|
||||
}
|
||||
|
||||
private bool ShouldEmitInsight(QCAlgorithm algorithm, Symbol symbol)
|
||||
{
|
||||
var timeOfDay = algorithm.Time.TimeOfDay;
|
||||
|
||||
return algorithm.Securities[symbol].HasData &&
|
||||
timeOfDay >= TimeSpan.FromHours(10) &&
|
||||
timeOfDay <= TimeSpan.FromHours(15);
|
||||
}
|
||||
|
||||
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
|
||||
{
|
||||
foreach (var symbol in changes.AddedSecurities.Select(x => x.Symbol))
|
||||
{
|
||||
if (_cache.ContainsKey(symbol)) continue;
|
||||
_cache.Add(symbol, new SymbolData(algorithm, symbol, _periodSma, _resolution));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains data specific to a symbol required by this model
|
||||
/// </summary>
|
||||
private class SymbolData
|
||||
{
|
||||
private readonly SimpleMovingAverage _priceSMA;
|
||||
|
||||
public InsightDirection PreviousDirection { get; set; }
|
||||
|
||||
public SymbolData(QCAlgorithm algorithm, Symbol symbol, int periodSma, Resolution resolution)
|
||||
{
|
||||
PreviousDirection = InsightDirection.Flat;
|
||||
_priceSMA = algorithm.SMA(symbol, periodSma, resolution);
|
||||
}
|
||||
|
||||
public bool IsUptrend(decimal price)
|
||||
{
|
||||
return _priceSMA.IsReady && price < Math.Round(_priceSMA * 1.001m, 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Indicators;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp.Alphas
|
||||
{
|
||||
/// <summary>
|
||||
/// This alpha aims to capture the mean-reversion effect of ETFs during lunch-break by ranking 20 ETFs
|
||||
/// on their return between the close of the previous day to 12:00 the day after and predicting mean-reversion
|
||||
/// in price during lunch-break.
|
||||
///
|
||||
/// Source: Lunina, V. (June 2011). The Intraday Dynamics of Stock Returns and Trading Activity: Evidence from OMXS 30 (Master's Essay, Lund University).
|
||||
/// Retrieved from http://lup.lub.lu.se/luur/download?func=downloadFile&recordOId=1973850&fileOId=1973852
|
||||
///
|
||||
/// This alpha is part of the Benchmark Alpha Series created by QuantConnect which are open sourced so the community and client funds can see an example of an alpha.
|
||||
///</summary>
|
||||
public class MeanReversionLunchBreakAlpha : QCAlgorithm
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2018, 1, 1);
|
||||
SetCash(100000);
|
||||
|
||||
// Set zero transaction fees
|
||||
SetSecurityInitializer(security => security.FeeModel = new ConstantFeeModel(0));
|
||||
|
||||
// Use Hourly Data For Simplicity
|
||||
UniverseSettings.Resolution = Resolution.Hour;
|
||||
SetUniverseSelection(new CoarseFundamentalUniverseSelectionModel(CoarseSelectionFunction));
|
||||
|
||||
// Use MeanReversionLunchBreakAlphaModel to establish insights
|
||||
SetAlpha(new MeanReversionLunchBreakAlphaModel());
|
||||
|
||||
// Equally weigh securities in portfolio, based on insights
|
||||
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
|
||||
|
||||
// Set Immediate Execution Model
|
||||
SetExecution(new ImmediateExecutionModel());
|
||||
|
||||
// Set Null Risk Management Model
|
||||
SetRiskManagement(new NullRiskManagementModel());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sort the data by daily dollar volume and take the top '20' ETFs
|
||||
/// </summary>
|
||||
private IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
|
||||
{
|
||||
return (from cf in coarse
|
||||
where !cf.HasFundamentalData
|
||||
orderby cf.DollarVolume descending
|
||||
select cf.Symbol).Take(20);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the price return between the close of previous day to 12:00 the day after to
|
||||
/// predict mean-reversion of stock price during lunch break and creates direction prediction
|
||||
/// for insights accordingly.
|
||||
/// </summary>
|
||||
private class MeanReversionLunchBreakAlphaModel : AlphaModel
|
||||
{
|
||||
private const Resolution _resolution = Resolution.Hour;
|
||||
private readonly TimeSpan _predictionInterval;
|
||||
private readonly Dictionary<Symbol, SymbolData> _symbolDataBySymbol;
|
||||
|
||||
public MeanReversionLunchBreakAlphaModel(int lookback = 1)
|
||||
{
|
||||
_predictionInterval = _resolution.ToTimeSpan().Multiply(lookback);
|
||||
_symbolDataBySymbol = new Dictionary<Symbol, SymbolData>();
|
||||
}
|
||||
|
||||
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
|
||||
{
|
||||
foreach (var kvp in _symbolDataBySymbol)
|
||||
{
|
||||
if (data.Bars.ContainsKey(kvp.Key))
|
||||
{
|
||||
var bar = data.Bars.GetValue(kvp.Key);
|
||||
kvp.Value.Update(bar.EndTime, bar.Close);
|
||||
}
|
||||
}
|
||||
|
||||
return algorithm.Time.Hour == 12
|
||||
? _symbolDataBySymbol.Select(kvp => kvp.Value.Insight)
|
||||
: Enumerable.Empty<Insight>();
|
||||
}
|
||||
|
||||
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
|
||||
{
|
||||
foreach (var security in changes.RemovedSecurities)
|
||||
{
|
||||
if (_symbolDataBySymbol.ContainsKey(security.Symbol))
|
||||
{
|
||||
_symbolDataBySymbol.Remove(security.Symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve price history for all securities in the security universe
|
||||
// and update the indicators in the SymbolData object
|
||||
var symbols = changes.AddedSecurities.Select(x => x.Symbol);
|
||||
var history = algorithm.History(symbols, 1, _resolution);
|
||||
if (symbols.Count() > 0 && history.Count() == 0)
|
||||
{
|
||||
algorithm.Debug($"No data on {algorithm.Time}");
|
||||
}
|
||||
|
||||
history.PushThrough(bar =>
|
||||
{
|
||||
SymbolData symbolData;
|
||||
if (!_symbolDataBySymbol.TryGetValue(bar.Symbol, out symbolData))
|
||||
{
|
||||
symbolData = new SymbolData(bar.Symbol, _predictionInterval);
|
||||
}
|
||||
symbolData.Update(bar.EndTime, bar.Price);
|
||||
_symbolDataBySymbol[bar.Symbol] = symbolData;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains data specific to a symbol required by this model
|
||||
/// </summary>
|
||||
private class SymbolData
|
||||
{
|
||||
// Mean value of returns for magnitude prediction
|
||||
private readonly SimpleMovingAverage _meanOfPriceChange = new RateOfChangePercent(1).SMA(3);
|
||||
// Price change from close price the previous day
|
||||
private readonly RateOfChangePercent _priceChange = new RateOfChangePercent(3);
|
||||
|
||||
private readonly Symbol _symbol;
|
||||
private readonly TimeSpan _period;
|
||||
|
||||
public Insight Insight
|
||||
{
|
||||
get
|
||||
{
|
||||
// Emit "down" insight for the securities that increased in value and
|
||||
// emit "up" insight for securities that have decreased in value
|
||||
var direction = _priceChange > 0 ? InsightDirection.Down : InsightDirection.Up;
|
||||
var magnitude = Convert.ToDouble(Math.Abs(_meanOfPriceChange));
|
||||
return Insight.Price(_symbol, _period, direction, magnitude);
|
||||
}
|
||||
}
|
||||
|
||||
public SymbolData(Symbol symbol, TimeSpan period)
|
||||
{
|
||||
_symbol = symbol;
|
||||
_period = period;
|
||||
}
|
||||
|
||||
public bool Update(DateTime time, decimal value)
|
||||
{
|
||||
return _meanOfPriceChange.Update(time, value) &
|
||||
_priceChange.Update(time, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp.Alphas
|
||||
{
|
||||
///<summary>
|
||||
/// Alpha Benchmark Strategy capitalizing on ETF rebalancing causing momentum during trending markets.
|
||||
/// Strategy by Prof. Shum, reposted by Ernie Chan.
|
||||
/// Source: http://epchan.blogspot.com/2012/10/a-leveraged-etfs-strategy.html
|
||||
///</summary>
|
||||
/// <meta name="tag" content="alphastream" />
|
||||
/// <meta name="tag" content="algorithm framework" />
|
||||
/// <meta name="tag" content="etf" />
|
||||
public class RebalancingLeveragedETFAlpha : QCAlgorithm, IRegressionAlgorithmDefinition
|
||||
{
|
||||
private readonly List<ETFGroup> Groups = new List<ETFGroup>();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2017, 6, 1);
|
||||
SetEndDate(2018, 8, 1);
|
||||
SetCash(100000);
|
||||
|
||||
var underlying = new List<string> { "SPY", "QLD", "DIA", "IJR", "MDY", "IWM", "QQQ", "IYE", "EEM", "IYW", "EFA", "GAZB", "SLV", "IEF", "IYM", "IYF", "IYH", "IYR", "IYC", "IBB", "FEZ", "USO", "TLT" };
|
||||
var ultraLong = new List<string> { "SSO", "UGL", "DDM", "SAA", "MZZ", "UWM", "QLD", "DIG", "EET", "ROM", "EFO", "BOIL", "AGQ", "UST", "UYM", "UYG", "RXL", "URE", "UCC", "BIB", "ULE", "UCO", "UBT" };
|
||||
var ultraShort = new List<string> { "SDS", "GLL", "DXD", "SDD", "MVV", "TWM", "QID", "DUG", "EEV", "REW", "EFU", "KOLD", "ZSL", "PST", "SMN", "SKF", "RXD", "SRS", "SCC", "BIS", "EPV", "SCO", "TBT" };
|
||||
|
||||
for (var i = 0; i < underlying.Count; i++)
|
||||
{
|
||||
Groups.Add(new ETFGroup(AddEquity(underlying[i]).Symbol, AddEquity(ultraLong[i]).Symbol, AddEquity(ultraShort[i]).Symbol));
|
||||
}
|
||||
|
||||
// Manually curated universe
|
||||
SetUniverseSelection(new ManualUniverseSelectionModel());
|
||||
|
||||
// Select the demonstration alpha model
|
||||
SetAlpha(new RebalancingLeveragedETFAlphaModel(Groups));
|
||||
|
||||
// Select our default model types
|
||||
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
|
||||
|
||||
// Equally weigh securities in portfolio, based on insights
|
||||
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
|
||||
|
||||
// Set Immediate Execution Model
|
||||
SetExecution(new ImmediateExecutionModel());
|
||||
|
||||
// Set Null Risk Management Model
|
||||
SetRiskManagement(new NullRiskManagementModel());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
|
||||
/// </summary>
|
||||
public bool CanRunLocally { get; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate which languages this algorithm is written in.
|
||||
/// </summary>
|
||||
public List<Language> Languages { get; } = new() { Language.CSharp, Language.Python };
|
||||
|
||||
/// <summary>
|
||||
/// Data Points count of all timeslices of algorithm
|
||||
/// </summary>
|
||||
public long DataPoints => 0;
|
||||
|
||||
/// </summary>
|
||||
/// Data Points count of the algorithm history
|
||||
/// </summary>
|
||||
public int AlgorithmHistoryDataPoints => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Final status of the algorithm
|
||||
/// </summary>
|
||||
public AlgorithmStatus AlgorithmStatus => AlgorithmStatus.Completed;
|
||||
|
||||
/// <summary>
|
||||
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
|
||||
{
|
||||
{"Total Orders", "2465"},
|
||||
{"Average Win", "0.26%"},
|
||||
{"Average Loss", "-0.24%"},
|
||||
{"Compounding Annual Return", "7.848%"},
|
||||
{"Drawdown", "17.500%"},
|
||||
{"Expectancy", "0.035"},
|
||||
{"Net Profit", "9.233%"},
|
||||
{"Sharpe Ratio", "0.492"},
|
||||
{"Loss Rate", "50%"},
|
||||
{"Win Rate", "50%"},
|
||||
{"Profit-Loss Ratio", "1.06"},
|
||||
{"Alpha", "0.585"},
|
||||
{"Beta", "-24.639"},
|
||||
{"Annual Standard Deviation", "0.19"},
|
||||
{"Annual Variance", "0.036"},
|
||||
{"Information Ratio", "0.387"},
|
||||
{"Tracking Error", "0.19"},
|
||||
{"Treynor Ratio", "-0.004"},
|
||||
{"Total Fees", "$9029.33"}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the underlying ETF has experienced a return >= 1% since the previous day's close up to the current time at 14:15,
|
||||
/// then buy it's ultra ETF right away, and exit at the close. If the return is <= -1%, sell it's ultra-short ETF.
|
||||
/// </summary>
|
||||
class RebalancingLeveragedETFAlphaModel : AlphaModel
|
||||
{
|
||||
private DateTime _date;
|
||||
private readonly List<ETFGroup> _etfGroups;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new leveraged ETF rebalancing alpha
|
||||
/// </summary>
|
||||
public RebalancingLeveragedETFAlphaModel(List<ETFGroup> etfGroups)
|
||||
{
|
||||
_etfGroups = etfGroups;
|
||||
_date = DateTime.MinValue;
|
||||
Name = "RebalancingLeveragedETFAlphaModel";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scan to see if the returns are greater than 1% at 2.15pm to emit an insight.
|
||||
/// </summary>
|
||||
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
|
||||
{
|
||||
// Initialize:
|
||||
var insights = new List<Insight>();
|
||||
var magnitude = 0.0005;
|
||||
|
||||
// Paper suggests leveraged ETF's rebalance from 2.15pm - to close
|
||||
// giving an insight period of 105 minutes.
|
||||
var period = TimeSpan.FromMinutes(105);
|
||||
|
||||
if (algorithm.Time.Date != _date)
|
||||
{
|
||||
_date = algorithm.Time.Date;
|
||||
|
||||
// Save yesterday's price and reset the signal.
|
||||
foreach (var group in _etfGroups)
|
||||
{
|
||||
var history = algorithm.History(group.Underlying, 1, Resolution.Daily);
|
||||
group.YesterdayClose = history.Select(x => x.Close).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the returns are > 1% at 14.15
|
||||
if (algorithm.Time.Hour == 14 && algorithm.Time.Minute == 15)
|
||||
{
|
||||
foreach (var group in _etfGroups)
|
||||
{
|
||||
if (group.YesterdayClose == 0) continue;
|
||||
var returns = (algorithm.Portfolio[group.Underlying].Price - group.YesterdayClose) / group.YesterdayClose;
|
||||
|
||||
if (returns > 0.01m)
|
||||
{
|
||||
insights.Add(Insight.Price(group.UltraLong, period, InsightDirection.Up, magnitude));
|
||||
}
|
||||
else if (returns < -0.01m)
|
||||
{
|
||||
insights.Add(Insight.Price(group.UltraShort, period, InsightDirection.Down, magnitude));
|
||||
}
|
||||
}
|
||||
}
|
||||
return insights;
|
||||
}
|
||||
}
|
||||
|
||||
class ETFGroup
|
||||
{
|
||||
public Symbol Underlying;
|
||||
public Symbol UltraLong;
|
||||
public Symbol UltraShort;
|
||||
public decimal YesterdayClose;
|
||||
|
||||
/// <summary>
|
||||
/// Group the underlying ETF and it's ultra ETFs
|
||||
/// </summary>
|
||||
/// <param name="underlying">The underlying indexETF</param>
|
||||
/// <param name="ultraLong">The long-leveraged version of underlying ETF</param>
|
||||
/// <param name="ultraShort">The short-leveraged version of the underlying ETF</param>
|
||||
public ETFGroup(Symbol underlying, Symbol ultraLong, Symbol ultraShort)
|
||||
{
|
||||
Underlying = underlying;
|
||||
UltraLong = ultraLong;
|
||||
UltraShort = ultraShort;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Indicators;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp.Alphas
|
||||
{
|
||||
/// <summary>
|
||||
/// A number of companies publicly trade two different classes of shares
|
||||
/// in US equity markets. If both assets trade with reasonable volume, then
|
||||
/// the underlying driving forces of each should be similar or the same. Given
|
||||
/// this, we can create a relatively dollar-neutral long/short portfolio using
|
||||
/// the dual share classes. Theoretically, any deviation of this portfolio from
|
||||
/// its mean-value should be corrected, and so the motivating idea is based on
|
||||
/// mean-reversion. Using a Simple Moving Average indicator, we can
|
||||
/// compare the value of this portfolio against its SMA and generate insights
|
||||
/// to buy the under-valued symbol and sell the over-valued symbol.
|
||||
///
|
||||
/// This alpha is part of the Benchmark Alpha Series created by QuantConnect which are open sourced so the community and client funds can see an example of an alpha.
|
||||
/// </summary>
|
||||
public class ShareClassMeanReversionAlpha : QCAlgorithm
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2019, 1, 1);
|
||||
SetCash(100000);
|
||||
|
||||
// Set zero transaction fees
|
||||
SetSecurityInitializer(security => security.FeeModel = new ConstantFeeModel(0));
|
||||
|
||||
SetWarmUp(20);
|
||||
|
||||
// Setup Universe settings and tickers to be used
|
||||
var symbols = new[] { "VIA", "VIAB" }
|
||||
.Select(x => QuantConnect.Symbol.Create(x, SecurityType.Equity, Market.USA));
|
||||
|
||||
// Set requested data resolution
|
||||
UniverseSettings.Resolution = Resolution.Minute;
|
||||
SetUniverseSelection(new ManualUniverseSelectionModel(symbols));
|
||||
|
||||
// Use ShareClassMeanReversionAlphaModel to establish insights
|
||||
SetAlpha(new ShareClassMeanReversionAlphaModel(symbols));
|
||||
|
||||
// Equally weigh securities in portfolio, based on insights
|
||||
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
|
||||
|
||||
// Set Immediate Execution Model
|
||||
SetExecution(new ImmediateExecutionModel());
|
||||
|
||||
// Set Null Risk Management Model
|
||||
SetRiskManagement(new NullRiskManagementModel());
|
||||
}
|
||||
|
||||
private class ShareClassMeanReversionAlphaModel : AlphaModel
|
||||
{
|
||||
private const double _insightMagnitude = 0.001;
|
||||
private readonly Symbol _longSymbol;
|
||||
private readonly Symbol _shortSymbol;
|
||||
private readonly TimeSpan _insightPeriod;
|
||||
private readonly SimpleMovingAverage _sma;
|
||||
private readonly RollingWindow<decimal> _positionWindow;
|
||||
private decimal _alpha;
|
||||
private decimal _beta;
|
||||
private bool _invested;
|
||||
|
||||
public ShareClassMeanReversionAlphaModel(
|
||||
IEnumerable<Symbol> symbols,
|
||||
Resolution resolution = Resolution.Minute)
|
||||
{
|
||||
if (symbols.Count() != 2)
|
||||
{
|
||||
throw new ArgumentException("ShareClassMeanReversionAlphaModel: symbols parameter must contain 2 elements");
|
||||
}
|
||||
_longSymbol = symbols.ToArray()[0];
|
||||
_shortSymbol = symbols.ToArray()[1];
|
||||
_insightPeriod = resolution.ToTimeSpan().Multiply(5);
|
||||
_sma = new SimpleMovingAverage(2);
|
||||
_positionWindow = new RollingWindow<decimal>(2);
|
||||
}
|
||||
|
||||
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
|
||||
{
|
||||
// Check to see if either ticker will return a NoneBar, and skip the data slice if so
|
||||
if (data.Bars.Count < 2)
|
||||
{
|
||||
return Enumerable.Empty<Insight>();
|
||||
}
|
||||
|
||||
// If Alpha and Beta haven't been calculated yet, then do so
|
||||
if (_alpha == 0 || _beta == 0)
|
||||
{
|
||||
CalculateAlphaBeta(algorithm);
|
||||
}
|
||||
|
||||
// Update indicator and Rolling Window for each data slice passed into Update() method
|
||||
if (!UpdateIndicators(data))
|
||||
{
|
||||
return Enumerable.Empty<Insight>();
|
||||
}
|
||||
|
||||
// Check to see if the portfolio is invested. If no, then perform value comparisons and emit insights accordingly
|
||||
if (!_invested)
|
||||
{
|
||||
//Reset invested boolean
|
||||
_invested = true;
|
||||
|
||||
if (_positionWindow[0] > _sma)
|
||||
{
|
||||
return Insight.Group(new[]
|
||||
{
|
||||
Insight.Price(_longSymbol, _insightPeriod, InsightDirection.Down, _insightMagnitude),
|
||||
Insight.Price(_shortSymbol, _insightPeriod, InsightDirection.Up, _insightMagnitude),
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return Insight.Group(new[]
|
||||
{
|
||||
Insight.Price(_longSymbol, _insightPeriod, InsightDirection.Up, _insightMagnitude),
|
||||
Insight.Price(_shortSymbol, _insightPeriod, InsightDirection.Down, _insightMagnitude),
|
||||
});
|
||||
}
|
||||
}
|
||||
// If the portfolio is invested and crossed back over the SMA, then emit flat insights
|
||||
else if (_invested && CrossedMean())
|
||||
{
|
||||
_invested = false;
|
||||
}
|
||||
|
||||
return Enumerable.Empty<Insight>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate Alpha and Beta, the initial number of shares for each security needed to achieve a 50/50 weighting
|
||||
/// </summary>
|
||||
/// <param name="algorithm"></param>
|
||||
private void CalculateAlphaBeta(QCAlgorithm algorithm)
|
||||
{
|
||||
_alpha = algorithm.CalculateOrderQuantity(_longSymbol, 0.5);
|
||||
_beta = algorithm.CalculateOrderQuantity(_shortSymbol, 0.5);
|
||||
algorithm.Log($"{algorithm.Time} :: Alpha: {_alpha} Beta: {_beta}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate position value and update the SMA indicator and Rolling Window
|
||||
/// </summary>
|
||||
private bool UpdateIndicators(Slice data)
|
||||
{
|
||||
var positionValue = (_alpha * data[_longSymbol].Close) - (_beta * data[_shortSymbol].Close);
|
||||
_sma.Update(data[_longSymbol].EndTime, positionValue);
|
||||
_positionWindow.Add(positionValue);
|
||||
return _sma.IsReady && _positionWindow.IsReady;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check to see if the position value has crossed the SMA and then return a boolean value
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool CrossedMean()
|
||||
{
|
||||
return (_positionWindow[0] >= _sma && _positionWindow[1] < _sma)
|
||||
|| (_positionWindow[1] >= _sma && _positionWindow[0] < _sma);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp.Alphas
|
||||
{
|
||||
/// <summary>
|
||||
/// Identify "pumped" penny stocks and predict that the price of a "Pumped" penny stock reverts to mean
|
||||
///
|
||||
/// This alpha is part of the Benchmark Alpha Series created by QuantConnect which are open sourced so the community and client funds can see an example of an alpha.
|
||||
///</summary>
|
||||
public class SykesShortMicroCapAlpha : QCAlgorithm
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2018, 1, 1);
|
||||
SetCash(100000);
|
||||
|
||||
// Set zero transaction fees
|
||||
SetSecurityInitializer(security => security.FeeModel = new ConstantFeeModel(0));
|
||||
|
||||
// Select stocks using PennyStockUniverseSelectionModel
|
||||
UniverseSettings.Resolution = Resolution.Daily;
|
||||
SetUniverseSelection(new PennyStockUniverseSelectionModel());
|
||||
|
||||
// Use SykesShortMicroCapAlphaModel to establish insights
|
||||
SetAlpha(new SykesShortMicroCapAlphaModel());
|
||||
|
||||
// Equally weigh securities in portfolio, based on insights
|
||||
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
|
||||
|
||||
// Set Immediate Execution Model
|
||||
SetExecution(new ImmediateExecutionModel());
|
||||
|
||||
// Set Null Risk Management Model
|
||||
SetRiskManagement(new NullRiskManagementModel());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs coarse selection for constituents.
|
||||
/// The stocks must have fundamental data
|
||||
/// The stock must have positive previous-day close price
|
||||
/// The stock must have volume between $1000000 and $10000 on the previous trading day
|
||||
/// The stock must cost less than $5'''
|
||||
/// </summary>
|
||||
private class PennyStockUniverseSelectionModel : FundamentalUniverseSelectionModel
|
||||
{
|
||||
private const int _numberOfSymbolsCoarse = 500;
|
||||
private int _lastMonth = -1;
|
||||
|
||||
public PennyStockUniverseSelectionModel() : base(false)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<Symbol> SelectCoarse(QCAlgorithm algorithm, IEnumerable<CoarseFundamental> coarse)
|
||||
{
|
||||
var month = algorithm.Time.Month;
|
||||
if (month == _lastMonth)
|
||||
{
|
||||
return algorithm.Universe.Unchanged;
|
||||
}
|
||||
_lastMonth = month;
|
||||
|
||||
return (from cf in coarse
|
||||
where cf.HasFundamentalData
|
||||
where cf.Volume < 1000000
|
||||
where cf.Volume > 10000
|
||||
where cf.Price < 5
|
||||
orderby cf.DollarVolume descending
|
||||
select cf.Symbol).Take(_numberOfSymbolsCoarse);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses ranking of intraday percentage difference between open price and close price to create magnitude and direction prediction for insights
|
||||
/// </summary>
|
||||
private class SykesShortMicroCapAlphaModel : AlphaModel
|
||||
{
|
||||
private readonly int _numberOfStocks;
|
||||
private readonly TimeSpan _predictionInterval;
|
||||
|
||||
public SykesShortMicroCapAlphaModel(
|
||||
int lookback = 1,
|
||||
int numberOfStocks = 10,
|
||||
Resolution resolution = Resolution.Daily)
|
||||
{
|
||||
_numberOfStocks = numberOfStocks;
|
||||
_predictionInterval = resolution.ToTimeSpan().Multiply(lookback);
|
||||
}
|
||||
|
||||
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
|
||||
{
|
||||
return (
|
||||
from entry in algorithm.ActiveSecurities
|
||||
let security = entry.Value
|
||||
where security.HasData && security.Open > 0
|
||||
// Rank penny stocks on one day price change
|
||||
let Magnitude = security.Close / security.Open - 1
|
||||
orderby Math.Round(Magnitude, 6), security.Symbol descending
|
||||
select Insight.Price(security.Symbol, _predictionInterval, InsightDirection.Down, Math.Abs((double)Magnitude)))
|
||||
// Retrieve list of _numberOfStocks "pumped" penny stocks
|
||||
.Take(_numberOfStocks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp.Alphas
|
||||
{
|
||||
/// <summary>
|
||||
/// In a perfect market, you could buy 100 EUR worth of USD, sell 100 EUR worth of GBP,
|
||||
/// and then use the GBP to buy USD and wind up with the same amount in USD as you received when
|
||||
/// you bought them with EUR. This relationship is expressed by the Triangle Exchange Rate, which is
|
||||
///
|
||||
/// Triangle Exchange Rate = (A/B) * (B/C) * (C/A)
|
||||
///
|
||||
/// where (A/B) is the exchange rate of A-to-B. In a perfect market, TER = 1, and so when
|
||||
/// there is a mispricing in the market, then TER will not be 1 and there exists an arbitrage opportunity.
|
||||
///
|
||||
/// This alpha is part of the Benchmark Alpha Series created by QuantConnect which are open sourced so the community and client funds can see an example of an alpha.
|
||||
/// </summary>
|
||||
public class TriangleExchangeRateArbitrageAlpha : QCAlgorithm
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2019, 2, 1);
|
||||
SetCash(100000);
|
||||
|
||||
// Set zero transaction fees
|
||||
SetSecurityInitializer(security => security.FeeModel = new ConstantFeeModel(0));
|
||||
|
||||
// Select trio of currencies to trade where
|
||||
// Currency A = USD
|
||||
// Currency B = EUR
|
||||
// Currency C = GBP
|
||||
var symbols = new[] { "EURUSD", "EURGBP", "GBPUSD" }
|
||||
.Select(x => QuantConnect.Symbol.Create(x, SecurityType.Forex, Market.Oanda));
|
||||
|
||||
// Set requested data resolution
|
||||
UniverseSettings.Resolution = Resolution.Minute;
|
||||
SetUniverseSelection(new ManualUniverseSelectionModel(symbols));
|
||||
|
||||
// Use ForexTriangleArbitrageAlphaModel to establish insights
|
||||
SetAlpha(new ForexTriangleArbitrageAlphaModel(symbols, Resolution.Minute));
|
||||
|
||||
// Equally weigh securities in portfolio, based on insights
|
||||
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
|
||||
|
||||
// Set Immediate Execution Model
|
||||
SetExecution(new ImmediateExecutionModel());
|
||||
|
||||
// Set Null Risk Management Model
|
||||
SetRiskManagement(new NullRiskManagementModel());
|
||||
}
|
||||
|
||||
private class ForexTriangleArbitrageAlphaModel : AlphaModel
|
||||
{
|
||||
private readonly Symbol[] _symbols;
|
||||
private readonly TimeSpan _insightPeriod;
|
||||
|
||||
public ForexTriangleArbitrageAlphaModel(
|
||||
IEnumerable<Symbol> symbols,
|
||||
Resolution resolution = Resolution.Minute)
|
||||
{
|
||||
_symbols = symbols.ToArray();
|
||||
_insightPeriod = resolution.ToTimeSpan().Multiply(5);
|
||||
}
|
||||
|
||||
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
|
||||
{
|
||||
// Check to make sure all currency symbols are present
|
||||
if (data.QuoteBars.Count < 3)
|
||||
{
|
||||
return Enumerable.Empty<Insight>();
|
||||
}
|
||||
|
||||
// Extract QuoteBars for all three Forex securities
|
||||
var barA = data[_symbols[0]];
|
||||
var barB = data[_symbols[1]];
|
||||
var barC = data[_symbols[2]];
|
||||
|
||||
// Calculate the triangle exchange rate
|
||||
// Bid(Currency A -> Currency B) * Bid(Currency B -> Currency C) * Bid(Currency C -> Currency A)
|
||||
// If exchange rates are priced perfectly, then this yield 1.If it is different than 1, then an arbitrage opportunity exists
|
||||
var triangleRate = barA.Ask.Close / barB.Bid.Close / barC.Ask.Close;
|
||||
|
||||
// If the triangle rate is significantly different than 1, then emit insights
|
||||
if (triangleRate > 1.0005m)
|
||||
{
|
||||
return Insight.Group(new[]
|
||||
{
|
||||
Insight.Price(_symbols[0], _insightPeriod, InsightDirection.Up, 0.0001),
|
||||
Insight.Price(_symbols[1], _insightPeriod, InsightDirection.Down, 0.0001),
|
||||
Insight.Price(_symbols[2], _insightPeriod, InsightDirection.Up, 0.0001)
|
||||
});
|
||||
}
|
||||
|
||||
return Enumerable.Empty<Insight>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Orders.Fees;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp.Alphas
|
||||
{
|
||||
/// <summary>
|
||||
/// Leveraged ETFs (LETF) promise a fixed leverage ratio with respect to an underlying asset or an index.
|
||||
/// A Triple-Leveraged ETF allows speculators to amplify their exposure to the daily returns of an underlying index by a factor of 3.
|
||||
///
|
||||
/// Increased volatility generally decreases the value of a LETF over an extended period of time as daily compounding is amplified.
|
||||
///
|
||||
/// This alpha emits short-biased insight to capitalize on volatility decay for each listed pair of TL-ETFs, by rebalancing the
|
||||
/// ETFs with equal weights each day.
|
||||
///
|
||||
/// This alpha is part of the Benchmark Alpha Series created by QuantConnect which are open sourced so the community and client funds can see an example of an alpha.
|
||||
/// </summary>
|
||||
public class TripleLeveragedETFPairVolatilityDecayAlpha : QCAlgorithm
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SetStartDate(2018, 1, 1);
|
||||
|
||||
SetCash(100000);
|
||||
|
||||
// Set zero transaction fees
|
||||
SetSecurityInitializer(security => security.FeeModel = new ConstantFeeModel(0));
|
||||
|
||||
// 3X ETF pair tickers
|
||||
var ultraLong = QuantConnect.Symbol.Create("UGLD", SecurityType.Equity, Market.USA);
|
||||
var ultraShort = QuantConnect.Symbol.Create("DGLD", SecurityType.Equity, Market.USA);
|
||||
|
||||
// Manually curated universe
|
||||
UniverseSettings.Resolution = Resolution.Daily;
|
||||
SetUniverseSelection(new ManualUniverseSelectionModel(new[] { ultraLong, ultraShort }));
|
||||
|
||||
// Select the demonstration alpha model
|
||||
SetAlpha(new RebalancingTripleLeveragedETFAlphaModel(ultraLong, ultraShort));
|
||||
|
||||
// Equally weigh securities in portfolio, based on insights
|
||||
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
|
||||
|
||||
// Set Immediate Execution Model
|
||||
SetExecution(new ImmediateExecutionModel());
|
||||
|
||||
// Set Null Risk Management Model
|
||||
SetRiskManagement(new NullRiskManagementModel());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebalance a pair of 3x leveraged ETFs and predict that the value of both ETFs in each pair will decrease.
|
||||
/// </summary>
|
||||
private class RebalancingTripleLeveragedETFAlphaModel : AlphaModel
|
||||
{
|
||||
private const double _magnitude = 0.001;
|
||||
private readonly Symbol _ultraLong;
|
||||
private readonly Symbol _ultraShort;
|
||||
private readonly TimeSpan _period;
|
||||
|
||||
public RebalancingTripleLeveragedETFAlphaModel(Symbol ultraLong, Symbol ultraShort)
|
||||
{
|
||||
// Giving an insight period 1 days.
|
||||
_period = QuantConnect.Time.OneDay;
|
||||
|
||||
_ultraLong = ultraLong;
|
||||
_ultraShort = ultraShort;
|
||||
|
||||
Name = "RebalancingTripleLeveragedETFAlphaModel";
|
||||
}
|
||||
|
||||
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
|
||||
{
|
||||
return Insight.Group(new[]
|
||||
{
|
||||
Insight.Price(_ultraLong, _period, InsightDirection.Down, _magnitude),
|
||||
Insight.Price(_ultraShort, _period, InsightDirection.Down, _magnitude)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
|
||||
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Execution;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Algorithm.Framework.Risk;
|
||||
using QuantConnect.Algorithm.Framework.Selection;
|
||||
using QuantConnect.Brokerages;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Data.Consolidators;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using QuantConnect.Indicators;
|
||||
using QuantConnect.Orders.Fees;
|
||||
|
||||
namespace QuantConnect.Algorithm.CSharp.Alphas
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a demonstration algorithm. It trades UVXY.
|
||||
/// Dual Thrust alpha model is used to produce insights.
|
||||
/// Those input parameters have been chosen that gave acceptable results on a series
|
||||
/// of random backtests run for the period from Oct, 2016 till Feb, 2019.
|
||||
/// </summary>
|
||||
class VIXDualThrustAlpha : QCAlgorithm
|
||||
{
|
||||
// -- STRATEGY INPUT PARAMETERS --
|
||||
private decimal _k1 = 0.63m;
|
||||
private decimal _k2 = 0.63m;
|
||||
private int _rangePeriod = 20;
|
||||
private int _consolidatorBars = 30;
|
||||
|
||||
// -- INITIALIZE --
|
||||
public override void Initialize()
|
||||
{
|
||||
// Settings
|
||||
SetStartDate(2016, 10, 01);
|
||||
SetSecurityInitializer(s => s.SetFeeModel(new ConstantFeeModel(0m)));
|
||||
SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin);
|
||||
|
||||
// Universe Selection
|
||||
UniverseSettings.Resolution = Resolution.Minute; // it's minute by default, but lets leave this param here
|
||||
var symbols = new[] { QuantConnect.Symbol.Create("UVXY", SecurityType.Equity, Market.USA) };
|
||||
SetUniverseSelection(new ManualUniverseSelectionModel(symbols));
|
||||
|
||||
// Warming up
|
||||
var resolutionInTimeSpan = UniverseSettings.Resolution.ToTimeSpan();
|
||||
var warmUpTimeSpan = resolutionInTimeSpan.Multiply(_consolidatorBars).Multiply(_rangePeriod);
|
||||
SetWarmUp(warmUpTimeSpan);
|
||||
|
||||
// Alpha Model
|
||||
SetAlpha(new DualThrustAlphaModel(_k1, _k2, _rangePeriod, UniverseSettings.Resolution, _consolidatorBars));
|
||||
|
||||
// Portfolio Construction
|
||||
SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
|
||||
|
||||
// Execution
|
||||
SetExecution(new ImmediateExecutionModel());
|
||||
|
||||
// Risk Management
|
||||
SetRiskManagement(new MaximumDrawdownPercentPerSecurity(0.03m));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alpha model that uses dual-thrust strategy to create insights
|
||||
/// https://medium.com/@FMZ_Quant/dual-thrust-trading-strategy-2cc74101a626
|
||||
/// or here:
|
||||
/// https://www.quantconnect.com/tutorials/strategy-library/dual-thrust-trading-algorithm
|
||||
/// </summary>
|
||||
public class DualThrustAlphaModel : AlphaModel
|
||||
{
|
||||
private readonly decimal _k1;
|
||||
private readonly decimal _k2;
|
||||
private readonly TimeSpan _consolidatorTimeSpan;
|
||||
private readonly int _rangePeriod;
|
||||
private readonly Dictionary<Symbol, SymbolData> _symbolDataBySymbol;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the class
|
||||
/// </summary>
|
||||
/// <param name="k1">Coefficient for upper band</param>
|
||||
/// <param name="k2">Coefficient for lower band</param>
|
||||
/// <param name="rangePeriod">Amount of last bars to calculate the range</param>
|
||||
/// <param name="resolution">The resolution of data sent into the EMA indicators</param>
|
||||
/// <param name="barsToConsolidate">If we want alpha o work on trade bars whose length is
|
||||
/// different from the standard resolution - 1m 1h etc. - we need to pass this parameters along
|
||||
/// with proper data resolution</param>
|
||||
public DualThrustAlphaModel(
|
||||
decimal k1,
|
||||
decimal k2,
|
||||
int rangePeriod,
|
||||
Resolution resolution = Resolution.Daily,
|
||||
int barsToConsolidate = 1
|
||||
)
|
||||
{
|
||||
// coefficient that used to determine upper and lower borders of a breakout channel
|
||||
_k1 = k1;
|
||||
_k2 = k2;
|
||||
|
||||
// period the range is calculated over
|
||||
_rangePeriod = rangePeriod;
|
||||
|
||||
// initialize with empty dict.
|
||||
_symbolDataBySymbol = new Dictionary<Symbol, SymbolData>();
|
||||
|
||||
// time for bars we make the calculations on
|
||||
_consolidatorTimeSpan = resolution.ToTimeSpan().Multiply(barsToConsolidate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates this alpha model with the latest data from the algorithm.
|
||||
/// This is called each time the algorithm receives data for subscribed securities
|
||||
/// </summary>
|
||||
/// <param name="algorithm">The algorithm instance</param>
|
||||
/// <param name="data">The new data available</param>
|
||||
/// <returns>The new insights generated</returns>
|
||||
public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
|
||||
{
|
||||
var insights = new List<Insight>();
|
||||
|
||||
// in 5 days after emission an insight is to be considered expired
|
||||
int insightCloseAddDays = 5;
|
||||
|
||||
foreach (var symbolData in _symbolDataBySymbol.Values)
|
||||
{
|
||||
var range = symbolData.Range;
|
||||
var symbol = symbolData.Symbol;
|
||||
var security = algorithm.Securities[symbol];
|
||||
|
||||
if (symbolData.IsReady)
|
||||
{
|
||||
// buying condition
|
||||
// - (1) price is above upper line
|
||||
// - (2) and we are not long. this is a first time we crossed the line lately
|
||||
if (security.Price > symbolData.UpperLine && !algorithm.Portfolio[symbol].IsLong)
|
||||
{
|
||||
DateTime insightCloseTimeUtc = algorithm.UtcTime.AddDays(insightCloseAddDays);
|
||||
insights.Add(Insight.Price(symbolData.Symbol, insightCloseTimeUtc, InsightDirection.Up));
|
||||
}
|
||||
|
||||
// selling condition
|
||||
// - (1) price is lower that lower line
|
||||
// - (2) and we are not short. this is a first time we crossed the line lately
|
||||
if (security.Price < symbolData.LowerLine && !algorithm.Portfolio[symbol].IsShort)
|
||||
{
|
||||
DateTime insightCloseTimeUtc = algorithm.UtcTime.AddDays(insightCloseAddDays);
|
||||
insights.Add(Insight.Price(symbolData.Symbol, insightCloseTimeUtc, InsightDirection.Down));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return insights;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event fired each time the we add/remove securities from the data feed
|
||||
/// </summary>
|
||||
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
|
||||
/// <param name="changes">The security additions and removals from the algorithm</param>
|
||||
public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
|
||||
{
|
||||
// added
|
||||
foreach (var added in changes.AddedSecurities)
|
||||
{
|
||||
SymbolData symbolData;
|
||||
if (!_symbolDataBySymbol.TryGetValue(added.Symbol, out symbolData))
|
||||
{
|
||||
// add symbol/symbolData pair to collection
|
||||
symbolData = new SymbolData(_rangePeriod, _consolidatorTimeSpan)
|
||||
{
|
||||
Symbol = added.Symbol,
|
||||
K1 = _k1,
|
||||
K2 = _k2
|
||||
};
|
||||
|
||||
_symbolDataBySymbol[added.Symbol] = symbolData;
|
||||
|
||||
//register consolidator
|
||||
algorithm.SubscriptionManager.AddConsolidator(added.Symbol, symbolData.GetConsolidator());
|
||||
}
|
||||
}
|
||||
|
||||
// removed
|
||||
foreach (var removed in changes.RemovedSecurities)
|
||||
{
|
||||
SymbolData symbolData;
|
||||
if (_symbolDataBySymbol.TryGetValue(removed.Symbol, out symbolData))
|
||||
{
|
||||
// unsubscribe consolidator from data updates
|
||||
algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, symbolData.GetConsolidator());
|
||||
|
||||
// remove item from dictionary collection
|
||||
if (!_symbolDataBySymbol.Remove(removed.Symbol))
|
||||
{
|
||||
algorithm.Error("Unable to remove data from collection: DualThrustAlphaModel");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains data specific to a symbol required by this model
|
||||
/// </summary>
|
||||
private class SymbolData
|
||||
{
|
||||
// rolling to contain items over the looking back period
|
||||
private readonly RollingWindow<TradeBar> _rangeWindow;
|
||||
|
||||
// we calculate our logic on bars
|
||||
private readonly TradeBarConsolidator _consolidator;
|
||||
|
||||
// current range value
|
||||
public decimal Range { get; private set; }
|
||||
|
||||
// upper Line
|
||||
public decimal UpperLine { get; private set; }
|
||||
|
||||
// lower Line
|
||||
public decimal LowerLine { get; private set; }
|
||||
|
||||
// symbol value
|
||||
public Symbol Symbol { get; set; }
|
||||
|
||||
// k1
|
||||
public decimal K1 { private get; set; }
|
||||
|
||||
// k2
|
||||
public decimal K2 { private get; set; }
|
||||
|
||||
// data is ready when rolling window is ready
|
||||
public bool IsReady => _rangeWindow.IsReady;
|
||||
|
||||
/// <summary>
|
||||
/// Main constructor for the class
|
||||
/// </summary>
|
||||
/// <param name="rangePeriod">Range period</param>
|
||||
/// <param name="consolidatorResolution">Time length of consolidator</param>
|
||||
public SymbolData(int rangePeriod, TimeSpan consolidatorResolution)
|
||||
{
|
||||
_rangeWindow = new RollingWindow<TradeBar>(rangePeriod);
|
||||
_consolidator = new TradeBarConsolidator(consolidatorResolution);
|
||||
|
||||
// event fired at new consolidated trade bar
|
||||
_consolidator.DataConsolidated += (sender, consolidated) =>
|
||||
{
|
||||
// add new tradebar to
|
||||
_rangeWindow.Add(consolidated);
|
||||
|
||||
if (IsReady)
|
||||
{
|
||||
var hh = _rangeWindow.Select(x => x.High).Max();
|
||||
var hc = _rangeWindow.Select(x => x.Close).Max();
|
||||
var lc = _rangeWindow.Select(x => x.Close).Min();
|
||||
var ll = _rangeWindow.Select(x => x.Low).Min();
|
||||
|
||||
Range = Math.Max(hh - lc, hc - ll);
|
||||
|
||||
UpperLine = consolidated.Close + K1 * Range;
|
||||
LowerLine = consolidated.Close - K2 * Range;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the interior consolidator
|
||||
/// </summary>
|
||||
public TradeBarConsolidator GetConsolidator()
|
||||
{
|
||||
return _consolidator;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user