chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -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. 9091.
///
/// 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 &lt;= -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;
}
}
}
}