Files
QuanTAlib/lib/trends_IIR/mma/Mma.Validation.Tests.cs
T
86fe32a682 SIMD Refactor: Merge simd-dev into dev (#55)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: aider (openrouter/anthropic/claude-sonnet-4) <aider@aider.chat>
Co-authored-by: Warp <agent@warp.dev>
2026-01-18 19:02:03 -08:00

134 lines
3.5 KiB
C#

using System;
namespace QuanTAlib.Tests;
public class MmaValidationTests
{
[Fact]
public void Mma_Streaming_MatchesReference()
{
int period = 20;
TSeries series = BuildSeries(300, seed: 5);
double[] reference = new double[series.Count];
ReferenceMma(series.Values, reference, period);
var mma = new Mma(period);
for (int i = 0; i < series.Count; i++)
{
double actual = mma.Update(series[i]).Value;
Assert.Equal(reference[i], actual, precision: 10);
}
}
[Fact]
public void Mma_Batch_MatchesReference()
{
int period = 14;
TSeries series = BuildSeries(250, seed: 9);
double[] reference = new double[series.Count];
ReferenceMma(series.Values, reference, period);
TSeries batch = Mma.Calculate(series, period);
for (int i = 0; i < series.Count; i++)
{
Assert.Equal(reference[i], batch[i].Value, precision: 10);
}
}
[Fact]
public void Mma_Span_MatchesReference()
{
int period = 30;
TSeries series = BuildSeries(200, seed: 12);
double[] values = series.Values.ToArray();
var output = new double[values.Length];
var reference = new double[values.Length];
ReferenceMma(values, reference, period);
Mma.Calculate(values, output, period);
for (int i = 0; i < values.Length; i++)
{
Assert.Equal(reference[i], output[i], precision: 10);
}
}
private static void ReferenceMma(ReadOnlySpan<double> source, Span<double> output, int period)
{
int window = Math.Min(Math.Max(2, period), 4000);
double[] buffer = new double[window];
int head = 0;
int count = 0;
double sum = 0.0;
double lastValid = double.NaN;
for (int i = 0; i < source.Length; i++)
{
double val = source[i];
if (double.IsFinite(val))
lastValid = val;
else
val = lastValid;
if (double.IsNaN(val))
{
output[i] = double.NaN;
continue;
}
if (count < window)
count++;
else
sum -= buffer[head];
buffer[head] = val;
sum += val;
head++;
if (head == window)
head = 0;
double sma = sum / count;
double weightedSum = ComputeWeightedSum(buffer, head, count);
double denom = (count + 1.0) * count;
output[i] = Math.FusedMultiplyAdd(weightedSum, 6.0 / denom, sma);
}
}
private static double ComputeWeightedSum(double[] buffer, int head, int count)
{
int idx = head - 1;
if (idx < 0)
idx = count - 1;
double weightedSum = 0.0;
for (int i = 0; i < count; i++)
{
double weight = (count - ((2 * i) + 1)) * 0.5;
weightedSum = Math.FusedMultiplyAdd(buffer[idx], weight, weightedSum);
idx--;
if (idx < 0)
idx = count - 1;
}
return weightedSum;
}
private static TSeries BuildSeries(int count, int seed)
{
var series = new TSeries();
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.1, seed: seed);
for (int i = 0; i < count; i++)
{
var bar = gbm.Next(isNew: true);
series.Add(bar.Time, bar.Close);
}
return series;
}
}