using System.Runtime.CompilerServices; namespace QuanTAlib; /// /// RMA: Running Moving Average (also known as Wilder's Moving Average or SMMA) /// /// /// RMA is an Exponential Moving Average (EMA) with a different smoothing factor. /// While EMA uses alpha = 2 / (period + 1), RMA uses alpha = 1 / period. /// /// Calculation: /// alpha = 1 / period /// RMA_new = RMA_old + alpha * (newest - RMA_old) /// /// This implementation wraps the EMA implementation to ensure identical behavior and performance, /// utilizing the same O(1) update complexity and zero-allocation architecture. /// [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, TValueEventArgs e) => Update(e.Value, e.IsNew); public override TValue Update(TValue input, bool isNew = true) { TValue result = _ema.Update(input, isNew); Last = result; PubEvent(Last); 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; } }