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  protected readonly Dictionary<Symbol, SymbolData> _symbolData;
41 
42  /// <summary>
43  /// Initializes a new instance of the <see cref="MacdAlphaModel"/> class
44  /// </summary>
45  /// <param name="fastPeriod">The MACD fast period</param>
46  /// <param name="slowPeriod">The MACD slow period</param>
47  /// <param name="signalPeriod">The smoothing period for the MACD signal</param>
48  /// <param name="movingAverageType">The type of moving average to use in the MACD</param>
49  /// <param name="resolution">The resolution of data sent into the MACD indicator</param>
51  int fastPeriod = 12,
52  int slowPeriod = 26,
53  int signalPeriod = 9,
54  MovingAverageType movingAverageType = MovingAverageType.Exponential,
55  Resolution resolution = Resolution.Daily
56  )
57  {
58  _fastPeriod = fastPeriod;
59  _slowPeriod = slowPeriod;
60  _signalPeriod = signalPeriod;
61  _movingAverageType = movingAverageType;
62  _resolution = resolution;
63  _symbolData = new Dictionary<Symbol, SymbolData>();
64  Name = $"{nameof(MacdAlphaModel)}({fastPeriod},{slowPeriod},{signalPeriod},{movingAverageType},{resolution})";
65  }
66 
67  /// <summary>
68  /// Determines an insight for each security based on it's current MACD signal
69  /// </summary>
70  /// <param name="algorithm">The algorithm instance</param>
71  /// <param name="data">The new data available</param>
72  /// <returns>The new insights generated</returns>
73  public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
74  {
75  foreach (var sd in _symbolData.Values)
76  {
77  if (sd.Security.Price == 0)
78  {
79  continue;
80  }
81 
82  var direction = InsightDirection.Flat;
83  var normalizedSignal = sd.MACD.Signal / sd.Security.Price;
84  if (normalizedSignal > BounceThresholdPercent)
85  {
86  direction = InsightDirection.Up;
87  }
88  else if (normalizedSignal < -BounceThresholdPercent)
89  {
90  direction = InsightDirection.Down;
91  }
92 
93  // ignore signal for same direction as previous signal
94  if (direction == sd.PreviousDirection)
95  {
96  continue;
97  }
98 
99  sd.PreviousDirection = direction;
100 
101  if (direction == InsightDirection.Flat)
102  {
103  CancelInsights(algorithm, sd.Security.Symbol);
104  continue;
105  }
106 
107  var insightPeriod = _resolution.ToTimeSpan().Multiply(_fastPeriod);
108  var insight = Insight.Price(sd.Security.Symbol, insightPeriod, direction);
109  _insightCollection.Add(insight);
110 
111  yield return insight;
112  }
113  }
114 
115  /// <summary>
116  /// Event fired each time the we add/remove securities from the data feed.
117  /// This initializes the MACD for each added security and cleans up the indicator for each removed security.
118  /// </summary>
119  /// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
120  /// <param name="changes">The security additions and removals from the algorithm</param>
121  public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
122  {
123  foreach (var added in changes.AddedSecurities)
124  {
125  if (_symbolData.ContainsKey(added.Symbol))
126  {
127  continue;
128  }
129  _symbolData.Add(added.Symbol, new SymbolData(algorithm, added, _fastPeriod, _slowPeriod, _signalPeriod, _movingAverageType, _resolution));
130  }
131 
132  foreach (var removed in changes.RemovedSecurities)
133  {
134  var symbol = removed.Symbol;
135 
136  SymbolData data;
137  if (_symbolData.TryGetValue(symbol, out data))
138  {
139  // clean up our consolidator
140  algorithm.SubscriptionManager.RemoveConsolidator(symbol, data.Consolidator);
141  _symbolData.Remove(symbol);
142  }
143 
144  // remove from insight collection manager
145  CancelInsights(algorithm, symbol);
146  }
147  }
148 
149  private void CancelInsights(QCAlgorithm algorithm, Symbol symbol)
150  {
151  if (_insightCollection.TryGetValue(symbol, out var insights))
152  {
153  algorithm.Insights.Cancel(insights);
154  _insightCollection.Clear(new[] { symbol });
155  }
156  }
157 
158  public class SymbolData
159  {
160  public InsightDirection? PreviousDirection { get; set; }
161 
162  public readonly Security Security;
163  public readonly IDataConsolidator Consolidator;
164  public readonly MovingAverageConvergenceDivergence MACD;
165 
166  public SymbolData(QCAlgorithm algorithm, Security security, int fastPeriod, int slowPeriod, int signalPeriod, MovingAverageType movingAverageType, Resolution resolution)
167  {
168  Security = security;
169  Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution);
170  algorithm.SubscriptionManager.AddConsolidator(security.Symbol, Consolidator);
171 
172  MACD = new MovingAverageConvergenceDivergence(fastPeriod, slowPeriod, signalPeriod, movingAverageType);
173 
174  algorithm.RegisterIndicator(security.Symbol, MACD, Consolidator);
175  algorithm.WarmUpIndicator(security.Symbol, MACD, resolution);
176  }
177  }
178  }
179 }