mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-17 01:58:06 +00:00
- 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
200 lines
6.6 KiB
C#
200 lines
6.6 KiB
C#
using Xunit.Abstractions;
|
||
|
||
namespace QuanTAlib.Tests;
|
||
|
||
public sealed class NyqmaValidationTests : IDisposable
|
||
{
|
||
private const int DefaultPeriod = 10;
|
||
private const int DefaultNyquistPeriod = 4;
|
||
private const double ValidationTolerance = 1e-9;
|
||
private readonly ValidationTestData _testData;
|
||
private readonly ITestOutputHelper _output;
|
||
|
||
public NyqmaValidationTests(ITestOutputHelper output)
|
||
{
|
||
_output = output;
|
||
_testData = new ValidationTestData();
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
Dispose(true);
|
||
}
|
||
|
||
private void Dispose(bool disposing)
|
||
{
|
||
if (disposing)
|
||
{
|
||
_testData.Dispose();
|
||
}
|
||
}
|
||
|
||
// NYQMA has no direct external library equivalent, so we validate
|
||
// by verifying NYQMA = (1+α)·WMA(src,N1) − α·WMA(WMA(src,N1),N2) (component consistency)
|
||
|
||
[Fact]
|
||
public void Validate_NyqmaEqualsComponentFormula_Batch()
|
||
{
|
||
int period = DefaultPeriod;
|
||
int nyquistPeriod = DefaultNyquistPeriod;
|
||
double alpha = (double)nyquistPeriod / (period - nyquistPeriod);
|
||
var source = _testData.Data;
|
||
|
||
// Compute WMA1 and WMA2(WMA1) separately
|
||
var wma1Result = Wma.Batch(source, period);
|
||
var wma2Result = Wma.Batch(wma1Result, nyquistPeriod);
|
||
|
||
// Compute NYQMA
|
||
var nyqmaResult = Nyqma.Batch(source, period, nyquistPeriod);
|
||
|
||
// NYQMA = (1+α)·WMA1 − α·WMA2
|
||
int count = nyqmaResult.Count;
|
||
int warmup = period + nyquistPeriod - 1;
|
||
for (int i = warmup; i < count; i++)
|
||
{
|
||
double expected = Math.FusedMultiplyAdd(1.0 + alpha, wma1Result[i].Value, -alpha * wma2Result[i].Value);
|
||
Assert.Equal(expected, nyqmaResult[i].Value, ValidationTolerance);
|
||
}
|
||
|
||
_output.WriteLine($"NYQMA({period},{nyquistPeriod}) component consistency: PASS ({count - warmup} bars validated at {ValidationTolerance})");
|
||
}
|
||
|
||
[Fact]
|
||
public void Validate_NyqmaEqualsComponentFormula_Streaming()
|
||
{
|
||
int period = DefaultPeriod;
|
||
int nyquistPeriod = DefaultNyquistPeriod;
|
||
double alpha = (double)nyquistPeriod / (period - nyquistPeriod);
|
||
var source = _testData.Data;
|
||
|
||
var wma1 = new Wma(period);
|
||
var wma2 = new Wma(nyquistPeriod);
|
||
var nyqma = new Nyqma(period, nyquistPeriod);
|
||
|
||
int warmup = period + nyquistPeriod - 1;
|
||
int validated = 0;
|
||
|
||
for (int i = 0; i < source.Count; i++)
|
||
{
|
||
var w1Val = wma1.Update(source[i]);
|
||
var w2Val = wma2.Update(w1Val);
|
||
var nyqmaVal = nyqma.Update(source[i]);
|
||
|
||
if (i >= warmup)
|
||
{
|
||
double expected = Math.FusedMultiplyAdd(1.0 + alpha, w1Val.Value, -alpha * w2Val.Value);
|
||
Assert.Equal(expected, nyqmaVal.Value, ValidationTolerance);
|
||
validated++;
|
||
}
|
||
}
|
||
|
||
_output.WriteLine($"NYQMA({period},{nyquistPeriod}) streaming component consistency: PASS ({validated} bars validated)");
|
||
}
|
||
|
||
[Fact]
|
||
public void Validate_NyqmaEqualsComponentFormula_Span()
|
||
{
|
||
int period = DefaultPeriod;
|
||
int nyquistPeriod = DefaultNyquistPeriod;
|
||
double alpha = (double)nyquistPeriod / (period - nyquistPeriod);
|
||
var rawData = _testData.RawData;
|
||
|
||
var wma1Output = new double[rawData.Length];
|
||
var wma2Output = new double[rawData.Length];
|
||
var nyqmaOutput = new double[rawData.Length];
|
||
|
||
Wma.Batch(rawData.Span, wma1Output.AsSpan(), period);
|
||
Wma.Batch(wma1Output.AsSpan(), wma2Output.AsSpan(), nyquistPeriod);
|
||
Nyqma.Batch(rawData.Span, nyqmaOutput.AsSpan(), period, nyquistPeriod);
|
||
|
||
int warmup = period + nyquistPeriod - 1;
|
||
int validated = 0;
|
||
|
||
for (int i = warmup; i < rawData.Length; i++)
|
||
{
|
||
double expected = Math.FusedMultiplyAdd(1.0 + alpha, wma1Output[i], -alpha * wma2Output[i]);
|
||
Assert.Equal(expected, nyqmaOutput[i], ValidationTolerance);
|
||
validated++;
|
||
}
|
||
|
||
_output.WriteLine($"NYQMA({period},{nyquistPeriod}) span component consistency: PASS ({validated} bars validated)");
|
||
}
|
||
|
||
[Fact]
|
||
public void Validate_BatchAndStreamingMatch()
|
||
{
|
||
int period = DefaultPeriod;
|
||
int nyquistPeriod = DefaultNyquistPeriod;
|
||
var source = _testData.Data;
|
||
|
||
// Batch
|
||
var batchResult = Nyqma.Batch(source, period, nyquistPeriod);
|
||
|
||
// Streaming
|
||
var streaming = new Nyqma(period, nyquistPeriod);
|
||
for (int i = 0; i < source.Count; i++)
|
||
{
|
||
streaming.Update(source[i]);
|
||
}
|
||
|
||
Assert.Equal(batchResult.Last.Value, streaming.Last.Value, 1e-6);
|
||
_output.WriteLine($"NYQMA({period},{nyquistPeriod}) batch/streaming match: PASS");
|
||
}
|
||
|
||
[Fact]
|
||
public void Validate_BatchAndSpanMatch()
|
||
{
|
||
int period = DefaultPeriod;
|
||
int nyquistPeriod = DefaultNyquistPeriod;
|
||
var source = _testData.Data;
|
||
var rawData = _testData.RawData;
|
||
|
||
var batchResult = Nyqma.Batch(source, period, nyquistPeriod);
|
||
|
||
var spanOutput = new double[rawData.Length];
|
||
Nyqma.Batch(rawData.Span, spanOutput.AsSpan(), period, nyquistPeriod);
|
||
|
||
Assert.Equal(batchResult.Last.Value, spanOutput[^1], ValidationTolerance);
|
||
_output.WriteLine($"NYQMA({period},{nyquistPeriod}) batch/span match: PASS");
|
||
}
|
||
|
||
[Theory]
|
||
[InlineData(5, 2)]
|
||
[InlineData(10, 3)]
|
||
[InlineData(21, 8)]
|
||
[InlineData(89, 21)]
|
||
public void Validate_DifferentPeriods_ConstantInputConverges(int period, int nyquistPeriod)
|
||
{
|
||
double constant = 55.0;
|
||
var nyqma = new Nyqma(period, nyquistPeriod);
|
||
|
||
double last = 0;
|
||
for (int i = 0; i < period * 3; i++)
|
||
{
|
||
last = nyqma.Update(new TValue(DateTime.UtcNow, constant)).Value;
|
||
}
|
||
|
||
Assert.Equal(constant, last, 1e-6);
|
||
_output.WriteLine($"NYQMA({period},{nyquistPeriod}) constant convergence: PASS (converged to {last:F9})");
|
||
}
|
||
|
||
[Fact]
|
||
public void Validate_NyquistClamping_AlphaBoundsCorrect()
|
||
{
|
||
// When nyquistPeriod is at maximum (period/2), alpha = (N1/2)/(N1 - N1/2) = 1
|
||
var nyqma = new Nyqma(10, 5);
|
||
// alpha = 5/(10-5) = 1.0
|
||
// NYQMA = 2*WMA1 - WMA2 (same as PMA when nyquistPeriod = period)
|
||
|
||
double constant = 100.0;
|
||
double last = 0;
|
||
for (int i = 0; i < 50; i++)
|
||
{
|
||
last = nyqma.Update(new TValue(DateTime.UtcNow, constant)).Value;
|
||
}
|
||
|
||
Assert.Equal(constant, last, 1e-9);
|
||
_output.WriteLine("NYQMA(10,5) alpha=1.0 convergence: PASS");
|
||
}
|
||
}
|