/* * 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.Collections.Generic; using QuantConnect.Data; using QuantConnect.Data.UniverseSelection; using QuantConnect.Data.Consolidators; using QuantConnect.Indicators; using QuantConnect.Securities; namespace QuantConnect.Algorithm.Framework.Alphas { /// /// Alpha model that uses an EMA cross to create insights /// public class EmaCrossAlphaModel : AlphaModel { private readonly int _fastPeriod; private readonly int _slowPeriod; private readonly Resolution _resolution; private readonly int _predictionInterval; /// /// This is made protected for testing purposes /// protected Dictionary SymbolDataBySymbol { get; } /// /// Initializes a new instance of the class /// /// The fast EMA period /// The slow EMA period /// The resolution of data sent into the EMA indicators public EmaCrossAlphaModel( int fastPeriod = 12, int slowPeriod = 26, Resolution resolution = Resolution.Daily ) { _fastPeriod = fastPeriod; _slowPeriod = slowPeriod; _resolution = resolution; _predictionInterval = fastPeriod; SymbolDataBySymbol = new Dictionary(); Name = $"{nameof(EmaCrossAlphaModel)}({fastPeriod},{slowPeriod},{resolution})"; } /// /// Updates this alpha model with the latest data from the algorithm. /// This is called each time the algorithm receives data for subscribed securities /// /// The algorithm instance /// The new data available /// The new insights generated public override IEnumerable Update(QCAlgorithm algorithm, Slice data) { var insights = new List(); foreach (var symbolData in SymbolDataBySymbol.Values) { if (symbolData.Fast.IsReady && symbolData.Slow.IsReady) { var insightPeriod = _resolution.ToTimeSpan().Multiply(_predictionInterval); if (symbolData.FastIsOverSlow) { if (symbolData.Slow > symbolData.Fast) { insights.Add(Insight.Price(symbolData.Symbol, insightPeriod, InsightDirection.Down)); } } else if (symbolData.SlowIsOverFast) { if (symbolData.Fast > symbolData.Slow) { insights.Add(Insight.Price(symbolData.Symbol, insightPeriod, InsightDirection.Up)); } } } symbolData.FastIsOverSlow = symbolData.Fast > symbolData.Slow; } return insights; } /// /// Event fired each time the we add/remove securities from the data feed /// /// The algorithm instance that experienced the change in securities /// The security additions and removals from the algorithm public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes) { foreach (var added in changes.AddedSecurities) { SymbolData symbolData; if (!SymbolDataBySymbol.TryGetValue(added.Symbol, out symbolData)) { SymbolDataBySymbol[added.Symbol] = new SymbolData(added, _fastPeriod, _slowPeriod, algorithm, _resolution); } else { // a security that was already initialized was re-added, reset the indicators symbolData.Fast.Reset(); symbolData.Slow.Reset(); } } foreach (var removed in changes.RemovedSecurities) { SymbolData symbolData; if (SymbolDataBySymbol.TryGetValue(removed.Symbol, out symbolData)) { // clean up our consolidators symbolData.RemoveConsolidators(); SymbolDataBySymbol.Remove(removed.Symbol); } } } /// /// Contains data specific to a symbol required by this model /// public class SymbolData { private readonly QCAlgorithm _algorithm; private readonly IDataConsolidator _fastConsolidator; private readonly IDataConsolidator _slowConsolidator; private readonly ExponentialMovingAverage _fast; private readonly ExponentialMovingAverage _slow; private readonly Security _security; /// /// Symbol associated with the data /// public Symbol Symbol => _security.Symbol; /// /// Fast Exponential Moving Average (EMA) /// public ExponentialMovingAverage Fast => _fast; /// /// Slow Exponential Moving Average (EMA) /// public ExponentialMovingAverage Slow => _slow; /// /// True if the fast is above the slow, otherwise false. /// This is used to prevent emitting the same signal repeatedly /// public bool FastIsOverSlow { get; set; } /// /// Flag indicating if the Slow EMA is over the Fast one /// public bool SlowIsOverFast => !FastIsOverSlow; /// /// Initializes an instance of the class SymbolData with the given arguments /// public SymbolData( Security security, int fastPeriod, int slowPeriod, QCAlgorithm algorithm, Resolution resolution) { _algorithm = algorithm; _security = security; _fastConsolidator = algorithm.ResolveConsolidator(security.Symbol, resolution); _slowConsolidator = algorithm.ResolveConsolidator(security.Symbol, resolution); algorithm.SubscriptionManager.AddConsolidator(security.Symbol, _fastConsolidator); algorithm.SubscriptionManager.AddConsolidator(security.Symbol, _slowConsolidator); // create fast/slow EMAs _fast = new ExponentialMovingAverage(security.Symbol, fastPeriod, ExponentialMovingAverage.SmoothingFactorDefault(fastPeriod)); _slow = new ExponentialMovingAverage(security.Symbol, slowPeriod, ExponentialMovingAverage.SmoothingFactorDefault(slowPeriod)); algorithm.RegisterIndicator(security.Symbol, _fast, _fastConsolidator); algorithm.RegisterIndicator(security.Symbol, _slow, _slowConsolidator); algorithm.WarmUpIndicator(security.Symbol, _fast, resolution); algorithm.WarmUpIndicator(security.Symbol, _slow, resolution); } /// /// Remove Fast and Slow consolidators /// public void RemoveConsolidators() { _algorithm.SubscriptionManager.RemoveConsolidator(Symbol, _fastConsolidator); _algorithm.SubscriptionManager.RemoveConsolidator(Symbol, _slowConsolidator); } } } }