using System.Runtime.CompilerServices; namespace QuanTAlib; /// /// RMA: Running Moving Average (Wilder's Moving Average) /// /// /// EMA variant using α=1/period for smoother, slower response than standard EMA. /// Commonly used in ATR and RSI calculations per Wilder's original methodology. /// /// Calculation: RMA_t = α×Price + (1-α)×RMA_{t-1}, where α = 1/period. /// /// Detailed documentation /// Reference Pine Script implementation [SkipLocalsInit] public sealed class Rma : AbstractBase { private readonly Ema _ema; /// /// Creates RMA with specified period. /// Alpha = 1 / period /// /// Period for RMA calculation (must be > 0) public Rma(int period) { if (period <= 0) { throw new ArgumentException("Period must be greater than 0", nameof(period)); } _ema = new Ema(1.0 / period); Name = $"Rma({period})"; WarmupPeriod = _ema.WarmupPeriod; } /// /// Creates RMA with specified source and period. /// Subscribes to source.Pub event. /// /// Source to subscribe to /// Period for RMA calculation public Rma(ITValuePublisher source, int period) : this(period) { ArgumentNullException.ThrowIfNull(source); source.Pub += Handle; } /// /// Creates RMA with specified source and period. /// /// Source series /// Period for RMA calculation (must be > 0) public Rma(TSeries source, int period) : this(period) { ArgumentNullException.ThrowIfNull(source); Prime(source.Values); if (source.Count > 0) { Last = new TValue(source.LastTime, Last.Value); } source.Pub += Handle; } /// /// True if the RMA has warmed up and is providing valid results. /// public override bool IsHot => _ema.IsHot; /// /// Initializes the indicator state using the provided history. /// /// Historical data public override void Prime(ReadOnlySpan source, TimeSpan? step = null) { _ema.Prime(source); Last = _ema.Last; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Handle(object? sender, in TValueEventArgs e) => Update(e.Value, e.IsNew); [MethodImpl(MethodImplOptions.AggressiveInlining)] public override TValue Update(TValue input, bool isNew = true) { TValue result = _ema.Update(input, isNew); Last = result; PubEvent(Last, isNew); return result; } public override TSeries Update(TSeries source) { TSeries result = _ema.Update(source); Last = _ema.Last; return result; } /// /// Calculates RMA for the entire series using a new instance. /// /// Input series public static TSeries Batch(TSeries source, int period) { ArgumentNullException.ThrowIfNull(source); var rma = new Rma(period); return rma.Update(source); } /// /// Calculates RMA in-place using period, writing results to pre-allocated output span. /// Zero-allocation method for maximum performance. /// Alpha = 1 / period /// /// Input values public static void Batch(ReadOnlySpan source, Span output, int period) { if (period <= 0) { throw new ArgumentException("Period must be greater than 0", nameof(period)); } if (output.Length < source.Length) { throw new ArgumentException("Output span must be at least as long as source span", nameof(output)); } double alpha = 1.0 / period; Ema.Batch(source, output, alpha); } /// /// Runs a high-performance batch calculation on history and returns /// a "Hot" Rma instance ready to process the next tick immediately. /// /// Historical time series /// RMA Period /// A tuple containing the full calculation results and the hot indicator instance public static (TSeries Results, Rma Indicator) Calculate(TSeries source, int period) { ArgumentNullException.ThrowIfNull(source); var rma = new Rma(period); TSeries results = rma.Update(source); return (results, rma); } /// /// Resets the RMA state. /// public override void Reset() { _ema.Reset(); Last = default; } }