Lean  $LEAN_TAG$
RsiAlphaModel.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;
22 
24 {
25  /// <summary>
26  /// Uses Wilder's RSI to create insights. Using default settings, a cross over below 30 or above 70 will
27  /// trigger a new insight.
28  /// </summary>
29  public class RsiAlphaModel : AlphaModel
30  {
31  private readonly Dictionary<Symbol, SymbolData> _symbolDataBySymbol = new Dictionary<Symbol, SymbolData>();
32 
33  private readonly int _period;
34  private readonly Resolution _resolution;
35 
36  /// <summary>
37  /// Initializes a new instance of the <see cref="RsiAlphaModel"/> class
38  /// </summary>
39  /// <param name="period">The RSI indicator period</param>
40  /// <param name="resolution">The resolution of data sent into the RSI indicator</param>
41  public RsiAlphaModel(
42  int period = 14,
43  Resolution resolution = Resolution.Daily
44  )
45  {
46  _period = period;
47  _resolution = resolution;
48  Name = $"{nameof(RsiAlphaModel)}({_period},{_resolution})";
49  }
50 
51  /// <summary>
52  /// Updates this alpha model with the latest data from the algorithm.
53  /// This is called each time the algorithm receives data for subscribed securities
54  /// </summary>
55  /// <param name="algorithm">The algorithm instance</param>
56  /// <param name="data">The new data available</param>
57  /// <returns>The new insights generated</returns>
58  public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
59  {
60  var insights = new List<Insight>();
61  foreach (var kvp in _symbolDataBySymbol)
62  {
63  var symbol = kvp.Key;
64  var rsi = kvp.Value.RSI;
65  var previousState = kvp.Value.State;
66  var state = GetState(rsi, previousState);
67 
68  if (state != previousState && rsi.IsReady)
69  {
70  var insightPeriod = _resolution.ToTimeSpan().Multiply(_period);
71 
72  switch (state)
73  {
74  case State.TrippedLow:
75  insights.Add(Insight.Price(symbol, insightPeriod, InsightDirection.Up));
76  break;
77 
78  case State.TrippedHigh:
79  insights.Add(Insight.Price(symbol, insightPeriod, InsightDirection.Down));
80  break;
81  }
82  }
83 
84  kvp.Value.State = state;
85  }
86 
87  return insights;
88  }
89 
90  /// <summary>
91  /// Cleans out old security data and initializes the RSI for any newly added securities.
92  /// This functional also seeds any new indicators using a history request.
93  /// </summary>
94  /// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
95  /// <param name="changes">The security additions and removals from the algorithm</param>
96  public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
97  {
98  // clean up data for removed securities
99  foreach (var security in changes.RemovedSecurities)
100  {
101  SymbolData symbolData;
102  if (_symbolDataBySymbol.TryGetValue(security.Symbol, out symbolData))
103  {
104  _symbolDataBySymbol.Remove(security.Symbol);
105  symbolData.Dispose();
106  }
107  }
108 
109  // initialize data for added securities
110  var addedSymbols = new List<Symbol>();
111  foreach (var added in changes.AddedSecurities)
112  {
113  if (!_symbolDataBySymbol.ContainsKey(added.Symbol))
114  {
115  var symbolData = new SymbolData(algorithm, added.Symbol, _period, _resolution);
116  _symbolDataBySymbol[added.Symbol] = symbolData;
117  addedSymbols.Add(added.Symbol);
118  }
119  }
120 
121  if (addedSymbols.Count > 0)
122  {
123  // warmup our indicators by pushing history through the consolidators
124  algorithm.History(addedSymbols, _period, _resolution)
125  .PushThrough(data =>
126  {
127  SymbolData symbolData;
128  if (_symbolDataBySymbol.TryGetValue(data.Symbol, out symbolData))
129  {
130  symbolData.Update(data);
131  }
132  });
133  }
134  }
135 
136  /// <summary>
137  /// Determines the new state. This is basically cross-over detection logic that
138  /// includes considerations for bouncing using the configured bounce tolerance.
139  /// </summary>
140  private State GetState(RelativeStrengthIndex rsi, State previous)
141  {
142  if (rsi > 70m)
143  {
144  return State.TrippedHigh;
145  }
146 
147  if (rsi < 30m)
148  {
149  return State.TrippedLow;
150  }
151 
152  if (previous == State.TrippedLow)
153  {
154  if (rsi > 35m)
155  {
156  return State.Middle;
157  }
158  }
159 
160  if (previous == State.TrippedHigh)
161  {
162  if (rsi < 65m)
163  {
164  return State.Middle;
165  }
166  }
167 
168  return previous;
169  }
170 
171  /// <summary>
172  /// Contains data specific to a symbol required by this model
173  /// </summary>
174  private class SymbolData : IDisposable
175  {
176  public State State { get; set; }
177  public RelativeStrengthIndex RSI { get; }
178  private Symbol _symbol { get; }
179  private QCAlgorithm _algorithm;
180  private IDataConsolidator _consolidator;
181 
182  public SymbolData(QCAlgorithm algorithm, Symbol symbol, int period, Resolution resolution)
183  {
184  _algorithm = algorithm;
185  _symbol = symbol;
186  State = State.Middle;
187 
188  RSI = new RelativeStrengthIndex(period, MovingAverageType.Wilders);
189  _consolidator = _algorithm.ResolveConsolidator(symbol, resolution);
190  algorithm.RegisterIndicator(symbol, RSI, _consolidator);
191  }
192 
193  public void Update(BaseData bar)
194  {
195  _consolidator.Update(bar);
196  }
197 
198  public void Dispose()
199  {
200  _algorithm.SubscriptionManager.RemoveConsolidator(_symbol, _consolidator);
201  }
202  }
203 
204  /// <summary>
205  /// Defines the state. This is used to prevent signal spamming and aid in bounce detection.
206  /// </summary>
207  private enum State
208  {
209  TrippedLow,
210  Middle,
211  TrippedHigh
212  }
213  }
214 }