/* * 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; using System.Collections.Generic; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.UniverseSelection; using QuantConnect.Indicators; 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 foreach (var security in changes.RemovedSecurities) { SymbolData symbolData; if (_symbolDataBySymbol.TryGetValue(security.Symbol, out symbolData)) { _symbolDataBySymbol.Remove(security.Symbol); symbolData.Dispose(); } } // initialize data for added securities var addedSymbols = new List(); foreach (var added in changes.AddedSecurities) { if (!_symbolDataBySymbol.ContainsKey(added.Symbol)) { var symbolData = new SymbolData(algorithm, added.Symbol, _period, _resolution); _symbolDataBySymbol[added.Symbol] = symbolData; addedSymbols.Add(added.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.Update(data); } }); } } /// /// 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 : IDisposable { public State State { get; set; } public RelativeStrengthIndex RSI { get; } private Symbol _symbol { get; } private QCAlgorithm _algorithm; private IDataConsolidator _consolidator; public SymbolData(QCAlgorithm algorithm, Symbol symbol, int period, Resolution resolution) { _algorithm = algorithm; _symbol = symbol; State = State.Middle; RSI = new RelativeStrengthIndex(period, MovingAverageType.Wilders); _consolidator = _algorithm.ResolveConsolidator(symbol, resolution); algorithm.RegisterIndicator(symbol, RSI, _consolidator); } public void Update(BaseData bar) { _consolidator.Update(bar); } public void Dispose() { _algorithm.SubscriptionManager.RemoveConsolidator(_symbol, _consolidator); } } /// /// Defines the state. This is used to prevent signal spamming and aid in bounce detection. /// private enum State { TrippedLow, Middle, TrippedHigh } } }