/* * 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.Indicators; using QuantConnect.Util; namespace QuantConnect.Algorithm.Framework.Alphas { /// /// Uses Wilder's RSI to create insights. Using default settings, a cross over below 30 or above 70 will /// trigger a new insight. /// public class RsiAlphaModel : AlphaModel { private readonly Dictionary _symbolDataBySymbol = new Dictionary(); private readonly int _period; private readonly Resolution _resolution; /// /// Initializes a new instance of the class /// /// The RSI indicator period /// The resolution of data sent into the RSI indicator public RsiAlphaModel( int period = 14, Resolution resolution = Resolution.Daily ) { _period = period; _resolution = resolution; Name = $"{nameof(RsiAlphaModel)}({_period},{_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 kvp in _symbolDataBySymbol) { var symbol = kvp.Key; var rsi = kvp.Value.RSI; var previousState = kvp.Value.State; var state = GetState(rsi, previousState); if (state != previousState && rsi.IsReady) { var insightPeriod = _resolution.ToTimeSpan().Multiply(_period); switch (state) { case State.TrippedLow: insights.Add(Insight.Price(symbol, insightPeriod, InsightDirection.Up)); break; case State.TrippedHigh: insights.Add(Insight.Price(symbol, insightPeriod, InsightDirection.Down)); break; } } kvp.Value.State = state; } return insights; } /// /// Cleans out old security data and initializes the RSI for any newly added securities. /// This functional also seeds any new indicators using a history request. /// /// 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) { // clean up data for removed securities if (changes.RemovedSecurities.Count > 0) { var removed = changes.RemovedSecurities.ToHashSet(x => x.Symbol); foreach (var subscription in algorithm.SubscriptionManager.Subscriptions) { if (removed.Contains(subscription.Symbol)) { _symbolDataBySymbol.Remove(subscription.Symbol); subscription.Consolidators.Clear(); } } } // initialize data for added securities var addedSymbols = new List(); foreach (var added in changes.AddedSecurities) { if (!_symbolDataBySymbol.ContainsKey(added.Symbol)) { var rsi = algorithm.RSI(added.Symbol, _period, MovingAverageType.Wilders, _resolution); var symbolData = new SymbolData(added.Symbol, rsi); _symbolDataBySymbol[added.Symbol] = symbolData; addedSymbols.Add(symbolData.Symbol); } } if (addedSymbols.Count > 0) { // warmup our indicators by pushing history through the consolidators algorithm.History(addedSymbols, _period, _resolution) .PushThrough(data => { SymbolData symbolData; if (_symbolDataBySymbol.TryGetValue(data.Symbol, out symbolData)) { symbolData.RSI.Update(data.EndTime, data.Value); } }); } } /// /// Determines the new state. This is basically cross-over detection logic that /// includes considerations for bouncing using the configured bounce tolerance. /// private State GetState(RelativeStrengthIndex rsi, State previous) { if (rsi > 70m) { return State.TrippedHigh; } if (rsi < 30m) { return State.TrippedLow; } if (previous == State.TrippedLow) { if (rsi > 35m) { return State.Middle; } } if (previous == State.TrippedHigh) { if (rsi < 65m) { return State.Middle; } } return previous; } /// /// Contains data specific to a symbol required by this model /// private class SymbolData { public Symbol Symbol { get; } public State State { get; set; } public RelativeStrengthIndex RSI { get; } public SymbolData(Symbol symbol, RelativeStrengthIndex rsi) { Symbol = symbol; RSI = rsi; State = State.Middle; } } /// /// Defines the state. This is used to prevent signal spamming and aid in bounce detection. /// private enum State { TrippedLow, Middle, TrippedHigh } } }