Lean  $LEAN_TAG$
ConnorsRelativeStrengthIndex.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.Linq;
18 
20 {
21  /// <summary>
22  /// Represents the Connors Relative Strength Index (CRSI), a combination of
23  /// the traditional Relative Strength Index (RSI), a Streak RSI (SRSI), and
24  /// Percent Rank.
25  /// This index is designed to provide a more robust measure of market strength
26  /// by combining momentum, streak behavior, and price change.
27  /// </summary>
29  {
30  /// <summary>
31  /// Computes the traditional Relative Strength Index (RSI).
32  /// </summary>
33  private readonly RelativeStrengthIndex _rsi;
34 
35  /// <summary>
36  /// Computes the RSI based on consecutive price streaks (SRSI).
37  /// </summary>
38  private readonly RelativeStrengthIndex _srsi;
39 
40  /// <summary>
41  /// Stores recent price change ratios for calculating the Percent Rank.
42  /// </summary>
43  private readonly RollingWindow<decimal> _priceChangeRatios;
44 
45  /// <summary>
46  /// Tracks the current trend streak (positive or negative) of price movements.
47  /// </summary>
48  private int _trendStreak;
49 
50  /// <summary>
51  /// Stores the previous input data point.
52  /// </summary>
53  private IndicatorDataPoint _previousInput;
54 
55  /// <summary>
56  /// Initializes a new instance of the <see cref="ConnorsRelativeStrengthIndex"/> class.
57  /// </summary>
58  /// <param name="name">The name of the indicator instance.</param>
59  /// <param name="rsiPeriod">The period for the RSI calculation.</param>
60  /// <param name="rsiPeriodStreak">The period for the Streak RSI calculation.</param>
61  /// <param name="lookBackPeriod">The period for calculating the Percent Rank.</param>
62  public ConnorsRelativeStrengthIndex(string name, int rsiPeriod, int rsiPeriodStreak, int lookBackPeriod) : base(name)
63  {
64  _rsi = new RelativeStrengthIndex(rsiPeriod);
65  _srsi = new RelativeStrengthIndex(rsiPeriodStreak);
66  _priceChangeRatios = new RollingWindow<decimal>(lookBackPeriod);
67  _trendStreak = 0;
68  WarmUpPeriod = Math.Max(rsiPeriod, Math.Max(rsiPeriodStreak, lookBackPeriod));
69  }
70 
71  /// <summary>
72  /// Initializes a new instance of the ConnorsRelativeStrengthIndex with specified RSI, Streak RSI,
73  /// and lookBack periods, using a default name format based on the provided parameters.
74  /// </summary>
75  public ConnorsRelativeStrengthIndex(int rsiPeriod, int rsiPeriodStreak, int rocPeriod)
76  : this($"CRSI({rsiPeriod},{rsiPeriodStreak},{rocPeriod})", rsiPeriod, rsiPeriodStreak, rocPeriod)
77  {
78  }
79 
80  /// <summary>
81  /// Gets a value indicating whether the indicator is ready for use.
82  /// The indicator is ready when all its components (RSI, SRSI, and PriceChangeRatios) are ready.
83  /// </summary>
84  public override bool IsReady => _rsi.IsReady && _srsi.IsReady && _priceChangeRatios.IsReady;
85 
86  /// <summary>
87  /// Gets the warm-up period required for the indicator to be ready.
88  /// This is the maximum period of all components (RSI, SRSI, and PriceChangeRatios).
89  /// </summary>
90  public int WarmUpPeriod { get; }
91 
92  /// <summary>
93  /// Computes the next value for the Connors Relative Strength Index (CRSI) based on the latest input data point.
94  /// The CRSI is calculated as the average of the traditional RSI, Streak RSI, and Percent Rank.
95  /// </summary>
96  /// <param name="input">The current input data point (typically the price data for the current period).</param>
97  /// <returns>The computed CRSI value, which combines the RSI, Streak RSI, and Percent Rank into a single value.
98  /// Returns zero if the indicator is not yet ready.</returns>
99  protected override decimal ComputeNextValue(IndicatorDataPoint input)
100  {
101  //RSI
102  _rsi.Update(input);
103 
104  ComputeTrendStreak(input);
105  _srsi.Update(new IndicatorDataPoint(input.EndTime, _trendStreak));
106 
107  if (_previousInput == null || _previousInput.Value == 0)
108  {
109  _previousInput = input;
110  _priceChangeRatios.Add(0m);
111  return decimal.Zero;
112  }
113 
114  //PercentRank
115  var relativeMagnitude = 0m;
116  var priceChangeRatio = (input.Value - _previousInput.Value) / _previousInput.Value;
117  if (_priceChangeRatios.IsReady)
118  {
119  // Calculate the percentage of previous change ratios that are smaller than the current price change ratio
120  relativeMagnitude = 100m * _priceChangeRatios.Count(x => x < priceChangeRatio) / _priceChangeRatios.Count;
121  }
122  _previousInput = input;
123 
124  //CRSI
125  if (IsReady)
126  {
127  // Add the priceChangeRatio after checking if IsReady is true or false, preventing premature returns
128  _priceChangeRatios.Add(priceChangeRatio);
129  return (_rsi.Current.Value + _srsi.Current.Value + relativeMagnitude) / 3;
130  }
131  // CRSI is not ready yet, so we store the price change ratio in the rolling window and return zero
132  _priceChangeRatios.Add(priceChangeRatio);
133  return decimal.Zero;
134  }
135 
136  /// <summary>
137  /// Updates the trend streak based on the price change direction between the current and previous input.
138  /// Resets the streak if the direction changes, otherwise increments or decrements it.
139  /// </summary>
140  /// <param name="input">The current input data point with price information.</param>
141  private void ComputeTrendStreak(IndicatorDataPoint input)
142  {
143  if (_previousInput == null)
144  {
145  return;
146  }
147  var change = input.Value - _previousInput.Value;
148  // If the price changes direction (up to down or down to up), reset the trend streak
149  if ((_trendStreak > 0 && change < 0) || (_trendStreak < 0 && change > 0))
150  {
151  _trendStreak = 0;
152  }
153  // Increment or decrement the trend streak based on price change direction
154  if (change > 0)
155  {
156  _trendStreak++;
157  }
158  else if (change < 0)
159  {
160  _trendStreak--;
161  }
162  }
163 
164  /// <summary>
165  /// Resets the indicator to its initial state. This clears all internal data and resets
166  /// the RSI, Streak RSI, and PriceChangeRatios, as well as the trend streak counter.
167  /// </summary>
168  public override void Reset()
169  {
170  _rsi.Reset();
171  _srsi.Reset();
172  _priceChangeRatios.Reset();
173  _trendStreak = 0;
174  base.Reset();
175  }
176  }
177 }