mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-08 14:07:44 +00:00
060649192f
- Remove 'C# Implementation Considerations' sections from 34 indicator .md files - Delete 29 temp PowerShell scripts (_fix_mojibake.ps1, _hex_scan.ps1, etc.) - Move test files into tests/ subdirectories for consistent project structure - Add trader-focused bullet points to indicator documentation
155 lines
4.2 KiB
C#
155 lines
4.2 KiB
C#
using System;
|
|
|
|
namespace QuanTAlib.Tests;
|
|
|
|
public class MmaValidationTests
|
|
{
|
|
// Note: External library validation is not feasible for MMA:
|
|
// - MMA (Modified Moving Average) is a QuanTAlib-specific algorithm that blends SMA with
|
|
// a weighted deviation component: output = SMA + weightedSum * 6/(count*(count+1)).
|
|
// - Skender's GetSmma() / Tulip's wilders = Wilder's smoothing (SMMA), a completely different algorithm.
|
|
// - TALib, OoplesFinance: No equivalent MMA implementation.
|
|
// Validated against independent reference implementation in tests below.
|
|
|
|
[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.Batch(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.Batch(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;
|
|
}
|
|
}
|