namespace QuanTAlib; /// /// RMA: Relative Moving Average (also known as Wilder's Moving Average) /// /// /// RMA is similar to EMA but uses a different smoothing factor. /// /// Key characteristics: /// - Uses no buffer, relying only on the previous RMA value. /// - The weight of new data points (alpha) is calculated as 1 / period. /// - Provides a smoother curve compared to SMA and EMA, reacting more slowly to price changes. /// /// Calculation method: /// This implementation can use SMA for the first Period bars as a seeding value for RMA when useSma is true. /// /// Sources: /// - https://www.tradingview.com/pine-script-reference/v5/#fun_ta{dot}rma /// - https://www.investopedia.com/terms/w/wilders-smoothing.asp /// public class Rma : AbstractBase { // inherited _index // inherited _value /// /// The period for the RMA calculation. /// private readonly int _period; /// /// Circular buffer for SMA calculation. /// private CircularBuffer _sma; /// /// The last calculated RMA value. /// private double _lastRma, _p_lastRma; /// /// Compensator for early RMA values. /// private double _e, _p_e; /// /// The smoothing factor for RMA calculation. /// private readonly double _k; /// /// Flags to track initialization status. /// private bool _isInit, _p_isInit; /// /// Flag to determine whether to use SMA for initial values. /// private readonly bool _useSma; /// /// Initializes a new instance of the Rma class with a specified period. /// /// The period for RMA calculation. /// Whether to use SMA for initial values. Default is true. /// Thrown when period is less than 1. public Rma(int period, bool useSma = true) { if (period < 1) { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } _period = period; _k = 1.0 / _period; // Wilder's smoothing factor _useSma = useSma; _sma = new(period); Name = "Rma"; WarmupPeriod = _period * 2; // RMA typically needs more warmup periods Init(); } /// /// Initializes a new instance of the Rma class with a specified source and period. /// /// The source object for event subscription. /// The period for RMA calculation. /// Whether to use SMA for initial values. Default is true. public Rma(object source, int period, bool useSma = true) : this(period, useSma) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } /// /// Initializes the Rma instance. /// public override void Init() { base.Init(); _e = 1.0; _lastRma = 0; _isInit = false; _p_isInit = false; _sma = new(_period); } /// /// Manages the state of the Rma instance. /// /// Indicates whether the input is new. protected override void ManageState(bool isNew) { if (isNew) { _p_lastRma = _lastRma; _p_isInit = _isInit; _p_e = _e; _index++; } else { _lastRma = _p_lastRma; _isInit = _p_isInit; _e = _p_e; } } /// /// Performs the RMA calculation. /// /// The calculated RMA value. protected override double Calculation() { double result, _rma; ManageState(Input.IsNew); // when _UseSma == true, use SMA calculation until we have enough data points if (!_isInit && _useSma) { _sma.Add(Input.Value, Input.IsNew); _rma = _sma.Average(); result = _rma; if (_index >= _period) { _isInit = true; } } else { // compensator for early rma values _e = (_e > 1e-10) ? (1 - _k) * _e : 0; _rma = _k * Input.Value + (1 - _k) * _lastRma; // _useSma decides if we use compensator or not result = (_useSma || _e <= double.Epsilon) ? _rma : _rma / (1 - _e); } _lastRma = _rma; IsHot = _index >= WarmupPeriod; return result; } }