namespace QuanTAlib; /// /// Represents a Relative Volatility Index (RVI) calculator, which measures the direction /// of volatility in relation to price movements. /// /// /// The RVI was introduced by Donald Dorsey in the 1993 issue of Technical Analysis /// of Stocks & Commodities Magazine. It focuses on the direction of price movements /// in relation to volatility. The indicator uses standard deviation calculations /// to determine whether volatility is increasing more in up moves or down moves. /// /// This implementation uses a combination of Standard Deviation and Simple Moving Average /// calculations to compute the RVI. /// public class Rvi : AbstractBase { private readonly int Period; private Stddev _upStdDev, _downStdDev; private Sma _upSma, _downSma; private double _previousClose; /// /// Initializes a new instance of the Rvi class with the specified period. /// /// The period over which to calculate the RVI. /// /// Thrown when period is less than 2. /// public Rvi(int period) : base() { if (period < 2) { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 2."); } Period = period; WarmupPeriod = period; Name = $"RVI(period={period})"; _upStdDev = new Stddev(Period); _downStdDev = new Stddev(Period); _upSma = new(Period); _downSma = new(Period); Init(); } /// /// Initializes a new instance of the Rvi class with the specified source and period. /// /// The source object to subscribe to for value updates. /// The period over which to calculate the RVI. public Rvi(object source, int period) : this(period) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } /// /// Initializes the Rvi instance by setting up the initial state. /// public override void Init() { base.Init(); _previousClose = 0; } /// /// Manages the state of the Rvi instance based on whether a new value is being processed. /// /// Indicates whether the current input is a new value. protected override void ManageState(bool isNew) { if (isNew) { _lastValidValue = Value; _index++; } } /// /// Performs the RVI calculation for the current input. /// /// /// The calculated RVI value for the current input. /// /// /// This method calculates the RVI using the following steps: /// 1. Calculate the change in price from the previous close. /// 2. Determine the up move and down move based on the change. /// 3. Calculate standard deviations of up and down moves. /// 4. Apply a simple moving average to the standard deviations. /// 5. Compute the RVI as a percentage of up volatility to total volatility. /// The method returns 0 if the sum of up and down volatility is zero. /// protected override double Calculation() { ManageState(Input.IsNew); double close = Input.Value; double change = close - _previousClose; double upMove = Math.Max(change, 0); double downMove = Math.Max(-change, 0); _upSma.Calc(_upStdDev.Calc(new TValue(Input.Time, upMove, Input.IsNew))); _downSma.Calc(_downStdDev.Calc(new TValue(Input.Time, downMove, Input.IsNew))); double rvi; if (_upSma.Value + _downSma.Value != 0) { rvi = 100 * _upSma.Value / (_upSma.Value + _downSma.Value); } else { rvi = 0; } _previousClose = close; IsHot = _index >= WarmupPeriod; return rvi; } }