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) { source.Pub += (item) => Update(item); } /// /// Creates RMA with specified source and period. /// /// Source series /// Period for RMA calculation public Rma(TSeries source, int period) : this(period) { Prime(source.Values); if (source.Count > 0) { Last = new TValue(source.LastTime, Last.Value); } source.Pub += (item) => Update(item); } /// /// 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) { _ema.Prime(source); Last = _ema.Last; } [MethodImpl(MethodImplOptions.AggressiveInlining)] 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 /// RMA period /// RMA series public static TSeries Batch(TSeries source, int period) { 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 /// Output span (must be same length as source) /// RMA period (must be > 0) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Batch(ReadOnlySpan source, Span output, int period) { if (period <= 0) throw new ArgumentException("Period must be greater than 0", nameof(period)); 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) { 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; } }