Lean  $LEAN_TAG$
HistoricalReturnsAlphaModel.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;
17 using System.Collections.Generic;
18 using QuantConnect.Data;
23 
25 {
26  /// <summary>
27  /// Alpha model that uses historical returns to create insights
28  /// </summary>
30  {
31  private readonly int _lookback;
32  private readonly Resolution _resolution;
33  private readonly TimeSpan _predictionInterval;
34  private readonly Dictionary<Symbol, SymbolData> _symbolDataBySymbol;
35  private readonly InsightCollection _insightCollection;
36 
37  /// <summary>
38  /// Initializes a new instance of the <see cref="HistoricalReturnsAlphaModel"/> class
39  /// </summary>
40  /// <param name="lookback">Historical return lookback period</param>
41  /// <param name="resolution">The resolution of historical data</param>
43  int lookback = 1,
44  Resolution resolution = Resolution.Daily
45  )
46  {
47  _lookback = lookback;
48  _resolution = resolution;
49  _predictionInterval = _resolution.ToTimeSpan().Multiply(_lookback);
50  _symbolDataBySymbol = new Dictionary<Symbol, SymbolData>();
51  _insightCollection = new InsightCollection();
52  Name = $"{nameof(HistoricalReturnsAlphaModel)}({lookback},{resolution})";
53  }
54 
55  /// <summary>
56  /// Updates this alpha model with the latest data from the algorithm.
57  /// This is called each time the algorithm receives data for subscribed securities
58  /// </summary>
59  /// <param name="algorithm">The algorithm instance</param>
60  /// <param name="data">The new data available</param>
61  /// <returns>The new insights generated</returns>
62  public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
63  {
64  var insights = new List<Insight>();
65  foreach (var (symbol, symbolData) in _symbolDataBySymbol)
66  {
67  if (symbolData.CanEmit())
68  {
69  var direction = InsightDirection.Flat;
70  var magnitude = (double)symbolData.ROC.Current.Value;
71  if (magnitude > 0) direction = InsightDirection.Up;
72  if (magnitude < 0) direction = InsightDirection.Down;
73 
74  if (direction == InsightDirection.Flat)
75  {
76  CancelInsights(algorithm, symbol);
77  continue;
78  }
79 
80  insights.Add(Insight.Price(symbolData.Security.Symbol, _predictionInterval, direction, magnitude, null));
81  }
82  }
83  _insightCollection.AddRange(insights);
84  return insights;
85  }
86 
87  /// <summary>
88  /// Event fired each time the we add/remove securities from the data feed
89  /// </summary>
90  /// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
91  /// <param name="changes">The security additions and removals from the algorithm</param>
92  public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
93  {
94  // clean up data for removed securities
95  foreach (var removed in changes.RemovedSecurities)
96  {
97  SymbolData data;
98  if (_symbolDataBySymbol.TryGetValue(removed.Symbol, out data))
99  {
100  _symbolDataBySymbol.Remove(removed.Symbol);
101  algorithm.SubscriptionManager.RemoveConsolidator(removed.Symbol, data.Consolidator);
102  }
103 
104  CancelInsights(algorithm, removed.Symbol);
105  }
106 
107  // initialize data for added securities
108  var addedSymbols = new List<Symbol>();
109  foreach (var added in changes.AddedSecurities)
110  {
111  if (!_symbolDataBySymbol.ContainsKey(added.Symbol))
112  {
113  var symbolData = new SymbolData(algorithm, added, _lookback, _resolution);
114  _symbolDataBySymbol[added.Symbol] = symbolData;
115  addedSymbols.Add(symbolData.Security.Symbol);
116  }
117  }
118 
119  if (addedSymbols.Count > 0)
120  {
121  // warmup our indicators by pushing history through the consolidators
122  algorithm.History(addedSymbols, _lookback, _resolution)
123  .PushThrough(bar =>
124  {
125  SymbolData symbolData;
126  if (_symbolDataBySymbol.TryGetValue(bar.Symbol, out symbolData))
127  {
128  symbolData.ROC.Update(bar.EndTime, bar.Value);
129  }
130  });
131  }
132  }
133 
134  private void CancelInsights(QCAlgorithm algorithm, Symbol symbol)
135  {
136  if (_insightCollection.TryGetValue(symbol, out var insights))
137  {
138  algorithm.Insights.Cancel(insights);
139  _insightCollection.Clear(new[] { symbol });
140  }
141  }
142 
143  /// <summary>
144  /// Contains data specific to a symbol required by this model
145  /// </summary>
146  private class SymbolData
147  {
148  public Security Security;
149  public IDataConsolidator Consolidator;
150  public RateOfChange ROC;
151  public long previous = 0;
152 
153  public SymbolData(QCAlgorithm algorithm, Security security, int lookback, Resolution resolution)
154  {
155  Security = security;
156  Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution);
157  algorithm.SubscriptionManager.AddConsolidator(security.Symbol, Consolidator);
158  ROC = new RateOfChange(security.Symbol.ToString(), lookback);
159  algorithm.RegisterIndicator(security.Symbol, ROC, Consolidator);
160  }
161 
162  public bool CanEmit()
163  {
164  if (previous == ROC.Samples) return false;
165  previous = ROC.Samples;
166  return ROC.IsReady;
167  }
168  }
169  }
170 }