Files
QuanTAlib/lib/trends_IIR/mcnma/Mcnma.Validation.Tests.cs
T
Miha Kralj 7253f61299 Add TRAMA implementation and comprehensive tests
- Implemented the TRAMA (Trend Regularity Adaptive Moving Average) class with adaptive EMA logic.
- Added unit tests for TRAMA functionality, including constructor validation, basic calculations, state management, and robustness checks.
- Created validation tests to ensure consistency across different modes of operation (streaming, batch, and static calculations).
- Enhanced documentation for TRAMA, including performance profiles and quality metrics.
- Updated workspace configuration by removing unnecessary folder references.
2026-02-21 20:45:38 -08:00

190 lines
5.9 KiB
C#

using Xunit.Abstractions;
namespace QuanTAlib.Tests;
public sealed class McnmaValidationTests : IDisposable
{
private readonly ValidationTestData _testData;
private readonly ITestOutputHelper _output;
private bool _disposed;
public McnmaValidationTests(ITestOutputHelper output)
{
_output = output;
_testData = new ValidationTestData();
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
_disposed = true;
if (disposing)
{
_testData?.Dispose();
}
}
[Fact]
public void Validate_ManualTemaComposition_Batch()
{
// MCNMA = 2*TEMA(src) - TEMA(TEMA(src))
int[] periods = { 5, 10, 14, 20, 50 };
foreach (var period in periods)
{
var mcnma = new Mcnma(period);
var qResult = mcnma.Update(_testData.Data);
// Manual composition using two TEMA instances
var tema1 = new Tema(period);
var tema2 = new Tema(period);
var manualResults = new List<double>();
for (int i = 0; i < _testData.Data.Count; i++)
{
var item = _testData.Data[i];
var t1 = tema1.Update(item);
var t2 = tema2.Update(t1);
manualResults.Add(2.0 * t1.Value - t2.Value);
}
for (int i = 0; i < qResult.Count; i++)
{
Assert.Equal(manualResults[i], qResult[i].Value, 1e-9);
}
}
_output.WriteLine("MCNMA Batch(TSeries) validated successfully against manual TEMA composition");
}
[Fact]
public void Validate_StreamingVsBatch_Consistency()
{
int[] periods = { 5, 10, 14, 20 };
foreach (var period in periods)
{
var batchResult = Mcnma.Batch(_testData.Data, period);
var streaming = new Mcnma(period);
for (int i = 0; i < _testData.Data.Count; i++)
{
streaming.Update(_testData.Data[i]);
}
int start = Math.Max(0, _testData.Data.Count - 100);
for (int i = start; i < _testData.Data.Count; i++)
{
Assert.Equal(batchResult[i].Value, batchResult[i].Value, 1e-9);
}
}
_output.WriteLine("MCNMA Streaming vs Batch validated successfully");
}
[Fact]
public void Validate_SpanVsStreaming_Consistency()
{
int[] periods = { 5, 10, 14, 20 };
double[] sourceData = _testData.RawData.ToArray();
foreach (var period in periods)
{
double[] spanOutput = new double[sourceData.Length];
Mcnma.Batch(sourceData.AsSpan(), spanOutput.AsSpan(), period);
var streaming = new Mcnma(period);
for (int i = 0; i < sourceData.Length; i++)
{
var val = streaming.Update(new TValue(DateTime.UtcNow, sourceData[i]));
Assert.Equal(val.Value, spanOutput[i], 1e-9);
}
}
_output.WriteLine("MCNMA Span vs Streaming validated successfully");
}
[Fact]
public void Validate_ConstantInput_ConvergesToInput()
{
// With constant input, all EMAs converge to the constant.
// TEMA(const) = 3*const - 3*const + const = const
// MCNMA = 2*const - const = const
const double constantValue = 42.0;
const int period = 10;
var mcnma = new Mcnma(period);
double lastResult = 0;
for (int i = 0; i < 200; i++)
{
var result = mcnma.Update(new TValue(DateTime.UtcNow, constantValue));
lastResult = result.Value;
}
Assert.Equal(constantValue, lastResult, 1e-6);
_output.WriteLine("MCNMA constant input convergence validated successfully");
}
[Fact]
public void Validate_Against_ManualFormula()
{
// Validate the explicit formula: 2*TEMA(src,N) - TEMA(TEMA(src,N),N)
int[] periods = { 5, 10, 14, 20 };
foreach (var period in periods)
{
var mcnma = new Mcnma(period);
var tema1 = new Tema(period);
var tema2 = new Tema(period);
for (int i = 0; i < _testData.Data.Count; i++)
{
var item = _testData.Data[i];
var qVal = mcnma.Update(item);
var t1 = tema1.Update(item);
var t2 = tema2.Update(t1);
double manualVal = 2.0 * t1.Value - t2.Value;
Assert.Equal(manualVal, qVal.Value, ValidationHelper.DefaultTolerance);
}
}
_output.WriteLine("MCNMA validated successfully against manual formula (2*TEMA - TEMA(TEMA))");
}
[Fact]
public void Validate_NaN_Robustness()
{
const int period = 10;
var mcnma = new Mcnma(period);
for (int i = 0; i < 20; i++)
{
mcnma.Update(new TValue(DateTime.UtcNow, 100.0 + i));
}
var nanResult = mcnma.Update(new TValue(DateTime.UtcNow, double.NaN));
Assert.True(double.IsFinite(nanResult.Value), "MCNMA should handle NaN with last-valid substitution");
var infResult = mcnma.Update(new TValue(DateTime.UtcNow, double.PositiveInfinity));
Assert.True(double.IsFinite(infResult.Value), "MCNMA should handle Infinity with last-valid substitution");
var negInfResult = mcnma.Update(new TValue(DateTime.UtcNow, double.NegativeInfinity));
Assert.True(double.IsFinite(negInfResult.Value), "MCNMA should handle -Infinity with last-valid substitution");
var resumeResult = mcnma.Update(new TValue(DateTime.UtcNow, 125.0));
Assert.True(double.IsFinite(resumeResult.Value), "MCNMA should resume cleanly after invalid inputs");
_output.WriteLine("MCNMA NaN/Infinity robustness validated successfully");
}
}