mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-17 01:58:06 +00:00
- Updated the Prime method signature in multiple indicators (Jma, Kama, Lsma, Mama, Mgdi, Pwma, Rma, Sma, Ssf, Super, T3, Tema, Trima, Usf, Vidya, Wma, Atr) to accept an optional TimeSpan parameter for improved flexibility. - Added unit tests for Lsma to verify Dispose functionality, ensuring proper unsubscription from the source and thread safety. - Enhanced Mama and Wma classes to handle non-finite inputs gracefully and added checks for valid parameters in constructors. - Introduced additional tests for T3 to validate constructor behavior with invalid volume factors. - Ensured all indicators maintain consistent behavior when handling edge cases, such as empty buffers and non-finite values.
153 lines
4.8 KiB
C#
153 lines
4.8 KiB
C#
using System.Runtime.CompilerServices;
|
|
|
|
namespace QuanTAlib;
|
|
|
|
/// <summary>
|
|
/// RMA: Running Moving Average (also known as Wilder's Moving Average or SMMA)
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
[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, 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;
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|