Lean  $LEAN_TAG$
MacdAlphaModel.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System.Collections.Generic;
17 using System.Linq;
18 using QuantConnect.Data;
23 
25 {
26  /// <summary>
27  /// Defines a custom alpha model that uses MACD crossovers. The MACD signal line is
28  /// used to generate up/down insights if it's stronger than the bounce threshold.
29  /// If the MACD signal is within the bounce threshold then a flat price insight is returned.
30  /// </summary>
31  public class MacdAlphaModel : AlphaModel
32  {
33  private readonly int _fastPeriod;
34  private readonly int _slowPeriod;
35  private readonly int _signalPeriod;
36  private readonly MovingAverageType _movingAverageType;
37  private readonly Resolution _resolution;
38  private const decimal BounceThresholdPercent = 0.01m;
39  private InsightCollection _insightCollection = new();
40 
41  /// <summary>
42  /// Dictionary containing basic information for each symbol present as key
43  /// </summary>
44  protected Dictionary<Symbol, SymbolData> _symbolData { get; init; }
45 
46  /// <summary>
47  /// Initializes a new instance of the <see cref="MacdAlphaModel"/> class
48  /// </summary>
49  /// <param name="fastPeriod">The MACD fast period</param>
50  /// <param name="slowPeriod">The MACD slow period</param>
51  /// <param name="signalPeriod">The smoothing period for the MACD signal</param>
52  /// <param name="movingAverageType">The type of moving average to use in the MACD</param>
53  /// <param name="resolution">The resolution of data sent into the MACD indicator</param>
55  int fastPeriod = 12,
56  int slowPeriod = 26,
57  int signalPeriod = 9,
58  MovingAverageType movingAverageType = MovingAverageType.Exponential,
59  Resolution resolution = Resolution.Daily
60  )
61  {
62  _fastPeriod = fastPeriod;
63  _slowPeriod = slowPeriod;
64  _signalPeriod = signalPeriod;
65  _movingAverageType = movingAverageType;
66  _resolution = resolution;
67  _symbolData = new Dictionary<Symbol, SymbolData>();
68  Name = $"{nameof(MacdAlphaModel)}({fastPeriod},{slowPeriod},{signalPeriod},{movingAverageType},{resolution})";
69  }
70 
71  /// <summary>
72  /// Determines an insight for each security based on it's current MACD signal
73  /// </summary>
74  /// <param name="algorithm">The algorithm instance</param>
75  /// <param name="data">The new data available</param>
76  /// <returns>The new insights generated</returns>
77  public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
78  {
79  foreach (var sd in _symbolData.Values)
80  {
81  if (sd.Security.Price == 0)
82  {
83  continue;
84  }
85 
86  var direction = InsightDirection.Flat;
87  var normalizedSignal = sd.MACD.Signal / sd.Security.Price;
88  if (normalizedSignal > BounceThresholdPercent)
89  {
90  direction = InsightDirection.Up;
91  }
92  else if (normalizedSignal < -BounceThresholdPercent)
93  {
94  direction = InsightDirection.Down;
95  }
96 
97  // ignore signal for same direction as previous signal
98  if (direction == sd.PreviousDirection)
99  {
100  continue;
101  }
102 
103  sd.PreviousDirection = direction;
104 
105  if (direction == InsightDirection.Flat)
106  {
107  CancelInsights(algorithm, sd.Security.Symbol);
108  continue;
109  }
110 
111  var insightPeriod = _resolution.ToTimeSpan().Multiply(_fastPeriod);
112  var insight = Insight.Price(sd.Security.Symbol, insightPeriod, direction);
113  _insightCollection.Add(insight);
114 
115  yield return insight;
116  }
117  }
118 
119  /// <summary>
120  /// Event fired each time the we add/remove securities from the data feed.
121  /// This initializes the MACD for each added security and cleans up the indicator for each removed security.
122  /// </summary>
123  /// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
124  /// <param name="changes">The security additions and removals from the algorithm</param>
125  public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
126  {
127  foreach (var added in changes.AddedSecurities)
128  {
129  if (_symbolData.ContainsKey(added.Symbol))
130  {
131  continue;
132  }
133  _symbolData.Add(added.Symbol, new SymbolData(algorithm, added, _fastPeriod, _slowPeriod, _signalPeriod, _movingAverageType, _resolution));
134  }
135 
136  foreach (var removed in changes.RemovedSecurities)
137  {
138  var symbol = removed.Symbol;
139 
140  SymbolData data;
141  if (_symbolData.TryGetValue(symbol, out data))
142  {
143  // clean up our consolidator
144  algorithm.SubscriptionManager.RemoveConsolidator(symbol, data.Consolidator);
145  _symbolData.Remove(symbol);
146  }
147 
148  // remove from insight collection manager
149  CancelInsights(algorithm, symbol);
150  }
151  }
152 
153  private void CancelInsights(QCAlgorithm algorithm, Symbol symbol)
154  {
155  if (_insightCollection.TryGetValue(symbol, out var insights))
156  {
157  algorithm.Insights.Cancel(insights);
158  _insightCollection.Clear(new[] { symbol });
159  }
160  }
161 
162  /// <summary>
163  /// Class representing basic data of a symbol
164  /// </summary>
165  public class SymbolData
166  {
167  /// <summary>
168  /// Previous direction property
169  /// </summary>
170  public InsightDirection? PreviousDirection { get; set; }
171 
172  /// <summary>
173  /// Security of the Symbol Data
174  /// </summary>
175  public Security Security { get; init; }
176 
177  /// <summary>
178  /// Consolidator property
179  /// </summary>
180  public IDataConsolidator Consolidator { get; init; }
181 
182  /// <summary>
183  /// Moving Average Convergence Divergence indicator
184  /// </summary>
186 
187  /// <summary>
188  /// Initializes an instance of the SymbolData class with the given arguments
189  /// </summary>
190  public SymbolData(QCAlgorithm algorithm, Security security, int fastPeriod, int slowPeriod, int signalPeriod, MovingAverageType movingAverageType, Resolution resolution)
191  {
192  Security = security;
193  Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution);
195 
196  MACD = new MovingAverageConvergenceDivergence(fastPeriod, slowPeriod, signalPeriod, movingAverageType);
197 
198  algorithm.RegisterIndicator(security.Symbol, MACD, Consolidator);
199  algorithm.WarmUpIndicator(security.Symbol, MACD, resolution);
200  }
201  }
202  }
203 }