Files
Miha Kralj 5ed4b6c0fc pine files
2026-01-31 14:05:53 -08:00

157 lines
4.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// RMA: Running Moving Average (Wilder's Moving Average)
/// </summary>
/// <remarks>
/// 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: <c>RMA_t = α×Price + (1-α)×RMA_{t-1}</c>, where <c>α = 1/period</c>.
/// </remarks>
/// <seealso href="Rma.md">Detailed documentation</seealso>
/// <seealso href="rma.pine">Reference Pine Script implementation</seealso>
[SkipLocalsInit]
public sealed class Rma : AbstractBase
{
private readonly Ema _ema;
/// <summary>
/// Creates RMA with specified period.
/// Alpha = 1 / period
/// </summary>
/// <param name="period">Period for RMA calculation (must be > 0)</param>
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;
}
/// <summary>
/// Creates RMA with specified source and period.
/// Subscribes to source.Pub event.
/// </summary>
/// <param name="source">Source to subscribe to</param>
/// <param name="period">Period for RMA calculation</param>
public Rma(ITValuePublisher source, int period) : this(period)
{
ArgumentNullException.ThrowIfNull(source);
source.Pub += Handle;
}
/// <summary>
/// Creates RMA with specified source and period.
/// </summary>
/// <param name="source">Source series</param>
/// <param name="period">Period for RMA calculation (must be > 0)</param>
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;
}
/// <summary>
/// True if the RMA has warmed up and is providing valid results.
/// </summary>
public override bool IsHot => _ema.IsHot;
/// <summary>
/// Initializes the indicator state using the provided history.
/// </summary>
/// <param name="source">Historical data</param>
public override void Prime(ReadOnlySpan<double> 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;
}
/// <summary>
/// Calculates RMA for the entire series using a new instance.
/// </summary>
/// <param name="source">Input series</param>
public static TSeries Batch(TSeries source, int period)
{
ArgumentNullException.ThrowIfNull(source);
var rma = new Rma(period);
return rma.Update(source);
}
/// <summary>
/// Calculates RMA in-place using period, writing results to pre-allocated output span.
/// Zero-allocation method for maximum performance.
/// Alpha = 1 / period
/// </summary>
/// <param name="source">Input values</param>
public static void Batch(ReadOnlySpan<double> source, Span<double> 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);
}
/// <summary>
/// Runs a high-performance batch calculation on history and returns
/// a "Hot" Rma instance ready to process the next tick immediately.
/// </summary>
/// <param name="source">Historical time series</param>
/// <param name="period">RMA Period</param>
/// <returns>A tuple containing the full calculation results and the hot indicator instance</returns>
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);
}
/// <summary>
/// Resets the RMA state.
/// </summary>
public override void Reset()
{
_ema.Reset();
Last = default;
}
}