/*
* 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.Linq;
namespace QuantConnect.Indicators
{
///
/// Represents the Connors Relative Strength Index (CRSI), a combination of
/// the traditional Relative Strength Index (RSI), a Streak RSI (SRSI), and
/// Percent Rank.
/// This index is designed to provide a more robust measure of market strength
/// by combining momentum, streak behavior, and price change.
///
public class ConnorsRelativeStrengthIndex : Indicator, IIndicatorWarmUpPeriodProvider
{
///
/// Computes the traditional Relative Strength Index (RSI).
///
private readonly RelativeStrengthIndex _rsi;
///
/// Computes the RSI based on consecutive price streaks (SRSI).
///
private readonly RelativeStrengthIndex _srsi;
///
/// Stores recent price change ratios for calculating the Percent Rank.
///
private readonly RollingWindow _priceChangeRatios;
///
/// Tracks the current trend streak (positive or negative) of price movements.
///
private int _trendStreak;
///
/// Stores the previous input data point.
///
private IndicatorDataPoint _previousInput;
///
/// Initializes a new instance of the class.
///
/// The name of the indicator instance.
/// The period for the RSI calculation.
/// The period for the Streak RSI calculation.
/// The period for calculating the Percent Rank.
public ConnorsRelativeStrengthIndex(string name, int rsiPeriod, int rsiPeriodStreak, int lookBackPeriod) : base(name)
{
_rsi = new RelativeStrengthIndex(rsiPeriod);
_srsi = new RelativeStrengthIndex(rsiPeriodStreak);
_priceChangeRatios = new RollingWindow(lookBackPeriod);
_trendStreak = 0;
WarmUpPeriod = Math.Max(lookBackPeriod, Math.Max(_rsi.WarmUpPeriod, _srsi.WarmUpPeriod));
}
///
/// Initializes a new instance of the ConnorsRelativeStrengthIndex with specified RSI, Streak RSI,
/// and lookBack periods, using a default name format based on the provided parameters.
///
public ConnorsRelativeStrengthIndex(int rsiPeriod, int rsiPeriodStreak, int rocPeriod)
: this($"CRSI({rsiPeriod},{rsiPeriodStreak},{rocPeriod})", rsiPeriod, rsiPeriodStreak, rocPeriod)
{
}
///
/// Gets a value indicating whether the indicator is ready for use.
/// The indicator is ready when all its components (RSI, SRSI, and PriceChangeRatios) are ready.
///
public override bool IsReady => _rsi.IsReady && _srsi.IsReady && _priceChangeRatios.IsReady;
///
/// Gets the warm-up period required for the indicator to be ready.
/// This is the maximum period of all components (RSI, SRSI, and PriceChangeRatios).
///
public int WarmUpPeriod { get; }
///
/// Computes the next value for the Connors Relative Strength Index (CRSI) based on the latest input data point.
/// The CRSI is calculated as the average of the traditional RSI, Streak RSI, and Percent Rank.
///
/// The current input data point (typically the price data for the current period).
/// The computed CRSI value, which combines the RSI, Streak RSI, and Percent Rank into a single value.
/// Returns zero if the indicator is not yet ready.
protected override decimal ComputeNextValue(IndicatorDataPoint input)
{
// RSI
_rsi.Update(input);
ComputeTrendStreak(input);
_srsi.Update(new IndicatorDataPoint(input.EndTime, _trendStreak));
if (_previousInput == null || _previousInput.Value == 0)
{
_previousInput = input;
_priceChangeRatios.Add(0m);
return decimal.Zero;
}
// PercentRank
var relativeMagnitude = 0m;
var priceChangeRatio = (input.Value - _previousInput.Value) / _previousInput.Value;
// Calculate PercentRank using only the previous values (exclude the current priceChangeRatio)
if (_priceChangeRatios.IsReady)
{
relativeMagnitude = 100m * _priceChangeRatios.Count(x => x < priceChangeRatio) / _priceChangeRatios.Count;
}
// Add the current priceChangeRatio to the rolling window for future calculations
_priceChangeRatios.Add(priceChangeRatio);
_previousInput = input;
// CRSI
if (IsReady)
{
// Calculate the CRSI only if all components are ready
return (_rsi.Current.Value + _srsi.Current.Value + relativeMagnitude) / 3;
}
// If not ready, return 0
return decimal.Zero;
}
///
/// Updates the trend streak based on the price change direction between the current and previous input.
/// Resets the streak if the direction changes, otherwise increments or decrements it.
///
/// The current input data point with price information.
private void ComputeTrendStreak(IndicatorDataPoint input)
{
if (_previousInput == null)
{
return;
}
var change = input.Value - _previousInput.Value;
// If the price changes direction (up to down or down to up), reset the trend streak
if ((_trendStreak > 0 && change < 0) || (_trendStreak < 0 && change > 0))
{
_trendStreak = 0;
}
// Increment or decrement the trend streak based on price change direction
if (change > 0)
{
_trendStreak++;
}
else if (change < 0)
{
_trendStreak--;
}
}
///
/// Resets the indicator to its initial state. This clears all internal data and resets
/// the RSI, Streak RSI, and PriceChangeRatios, as well as the trend streak counter.
///
public override void Reset()
{
_rsi.Reset();
_srsi.Reset();
_priceChangeRatios.Reset();
_trendStreak = 0;
base.Reset();
}
}
}