using System.Runtime.CompilerServices; namespace QuanTAlib; /// /// SRSI: Stochastic RSI /// A momentum oscillator that applies the stochastic formula to RSI values /// instead of price data. It provides a more sensitive indicator than standard /// RSI or Stochastic oscillators. /// /// /// The SRSI calculation process: /// 1. Calculate RSI /// 2. Apply Stochastic formula to RSI values: /// - Find highest high and lowest low of RSI over period /// - Calculate where current RSI is within this range /// 3. Smooth the result with SMA (signal line) /// /// Key characteristics: /// - Oscillates between 0 and 100 /// - More sensitive than standard RSI /// - Combines benefits of both RSI and Stochastic /// - Traditional overbought level at 80 /// - Traditional oversold level at 20 /// /// Formula: /// SRSI = ((RSI - Lowest RSI) / (Highest RSI - Lowest RSI)) * 100 /// Signal = SMA(SRSI, signalPeriod) /// /// Sources: /// Tushar Chande and Stanley Kroll - "The New Technical Trader" (1994) /// https://www.investopedia.com/terms/s/stochrsi.asp /// /// Note: Default periods (14,14,3,3) are commonly used values /// [SkipLocalsInit] public sealed class Srsi : AbstractBase { private readonly Rsi _rsi; private readonly CircularBuffer _rsiValues; private readonly CircularBuffer _srsiValues; private readonly Sma _signal; private readonly int _rsiPeriod; private readonly int _stochPeriod; private const int DefaultRsiPeriod = 14; private const int DefaultStochPeriod = 14; private const int DefaultSmoothK = 3; private const int DefaultSmoothD = 3; private const double ScalingFactor = 100.0; /// The RSI period (default 14). /// The Stochastic period (default 14). /// K line smoothing period (default 3). /// D line smoothing period (default 3). /// Thrown when any period is less than 1. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Srsi(int rsiPeriod = DefaultRsiPeriod, int stochPeriod = DefaultStochPeriod, int smoothK = DefaultSmoothK, int smoothD = DefaultSmoothD) { if (rsiPeriod < 1) { throw new ArgumentOutOfRangeException(nameof(rsiPeriod), "Period must be greater than 0"); } if (stochPeriod < 1) { throw new ArgumentOutOfRangeException(nameof(stochPeriod), "Period must be greater than 0"); } if (smoothK < 1) { throw new ArgumentOutOfRangeException(nameof(smoothK), "Period must be greater than 0"); } if (smoothD < 1) { throw new ArgumentOutOfRangeException(nameof(smoothD), "Period must be greater than 0"); } _rsiPeriod = rsiPeriod; _stochPeriod = stochPeriod; _rsi = new(rsiPeriod); _rsiValues = new(stochPeriod); _srsiValues = new(smoothK); _signal = new(smoothD); WarmupPeriod = rsiPeriod + stochPeriod + Math.Max(smoothK, smoothD); Name = $"SRSI({rsiPeriod},{stochPeriod},{smoothK},{smoothD})"; } /// The data source object that publishes updates. /// The RSI period. /// The Stochastic period. /// K line smoothing period. /// D line smoothing period. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Srsi(object source, int rsiPeriod = DefaultRsiPeriod, int stochPeriod = DefaultStochPeriod, int smoothK = DefaultSmoothK, int smoothD = DefaultSmoothD) : this(rsiPeriod, stochPeriod, smoothK, smoothD) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void ManageState(bool isNew) { if (isNew) _index++; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected override double Calculation() { ManageState(Input.IsNew); // Calculate RSI double rsiValue = _rsi.Calc(Input); if (Input.IsNew) _rsiValues.Add(rsiValue); // Not enough data if (_index <= _rsiPeriod) return 0; // Calculate Stochastic RSI double highest = _rsiValues.Max(); double lowest = _rsiValues.Min(); double range = highest - lowest; double srsi = range >= double.Epsilon ? ((rsiValue - lowest) / range) * ScalingFactor : 0; if (Input.IsNew) _srsiValues.Add(srsi); // Calculate signal line return _signal.Calc(new TValue(Input.Time, srsi, Input.IsNew)); } /// /// Gets the K line value (raw Stochastic RSI) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public double K() => _srsiValues[0]; /// /// Gets the D line value (signal line) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public double D() => Value; }