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 : ITValuePublisher { private readonly Ema _ema; private readonly int _period; /// /// Display name for the indicator. /// public string Name => $"Rma({_period})"; public event Action? Pub; /// /// 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)); _period = period; _ema = new Ema(1.0 / period); _ema.Pub += (item) => Pub?.Invoke(item); } /// /// 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); } /// /// Current RMA value. /// public TValue Last => _ema.Last; /// /// True if the RMA has warmed up and is providing valid results. /// public bool IsHot => _ema.IsHot; [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue Update(TValue input, bool isNew = true) { return _ema.Update(input, isNew); } public TSeries Update(TSeries source) { return _ema.Update(source); } /// /// Calculates RMA for the entire series using a new instance. /// /// Input series /// RMA period /// RMA series public static TSeries Calculate(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 Calculate(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.Calculate(source, output, alpha); } /// /// Resets the RMA state. /// public void Reset() { _ema.Reset(); } }