mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-20 03:28:05 +00:00
181 lines
5.4 KiB
C#
181 lines
5.4 KiB
C#
namespace QuanTAlib.Tests;
|
|
|
|
public class CoralValidationTests
|
|
{
|
|
private readonly GBM _gbm;
|
|
|
|
public CoralValidationTests()
|
|
{
|
|
_gbm = new GBM();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates that Coral output matches a manual PineScript-equivalent reference calculation.
|
|
/// The reference manually cascades 6 EMAs and applies the polynomial combination.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Coral_MatchesPineScriptReference()
|
|
{
|
|
const int period = 21;
|
|
const double cd = 0.4;
|
|
var bars = _gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
var series = bars.Close;
|
|
|
|
// QuanTAlib Coral
|
|
var coral = new Coral(period, cd);
|
|
for (int i = 0; i < series.Count; i++)
|
|
{
|
|
coral.Update(series[i]);
|
|
}
|
|
|
|
// Manual reference calculation matching coral.pine
|
|
double di = ((period - 1.0) / 2.0) + 1.0;
|
|
double c1 = 2.0 / (di + 1.0);
|
|
double c2 = 1.0 - c1;
|
|
double cd2 = cd * cd;
|
|
double cd3 = cd2 * cd;
|
|
double c3 = 3.0 * (cd2 + cd3);
|
|
double c4 = -3.0 * ((2.0 * cd2) + cd + cd3);
|
|
double c5 = (3.0 * cd) + 1.0 + cd3 + (3.0 * cd2);
|
|
|
|
double i1 = 0, i2 = 0, i3 = 0, i4 = 0, i5 = 0, i6 = 0;
|
|
double bfr = 0;
|
|
|
|
var values = series.Values.ToArray();
|
|
for (int i = 0; i < values.Length; i++)
|
|
{
|
|
double src = values[i];
|
|
i1 = (c1 * src) + (c2 * i1);
|
|
i2 = (c1 * i1) + (c2 * i2);
|
|
i3 = (c1 * i2) + (c2 * i3);
|
|
i4 = (c1 * i3) + (c2 * i4);
|
|
i5 = (c1 * i4) + (c2 * i5);
|
|
i6 = (c1 * i5) + (c2 * i6);
|
|
bfr = (-cd3 * i6) + (c3 * i5) + (c4 * i4) + (c5 * i3);
|
|
}
|
|
|
|
Assert.Equal(bfr, coral.Last.Value, 1e-9);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates unity DC gain: for constant input, c3 + c4 + c5 + (-cd³) = 1
|
|
/// means the output converges exactly to the input.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Coral_UnityDcGain_ConvergesToConstant()
|
|
{
|
|
const double constant = 42.0;
|
|
double[] cdValues = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0];
|
|
|
|
foreach (double cd in cdValues)
|
|
{
|
|
var coral = new Coral(10, cd);
|
|
for (int i = 0; i < 500; i++)
|
|
{
|
|
coral.Update(new TValue(DateTime.UtcNow.AddMinutes(i), constant));
|
|
}
|
|
Assert.Equal(constant, coral.Last.Value, 4);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates that Batch(TSeries), Batch(Span), streaming, and eventing all agree.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Coral_AllModes_Consistent()
|
|
{
|
|
const int period = 14;
|
|
const double cd = 0.5;
|
|
var bars = _gbm.Fetch(300, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
var series = bars.Close;
|
|
|
|
// Batch TSeries
|
|
var batchResult = Coral.Batch(series, period, cd);
|
|
|
|
// Batch Span
|
|
var srcArray = series.Values.ToArray();
|
|
var spanOutput = new double[srcArray.Length];
|
|
Coral.Batch(srcArray.AsSpan(), spanOutput.AsSpan(), period, cd);
|
|
|
|
// Streaming
|
|
var streaming = new Coral(period, cd);
|
|
for (int i = 0; i < series.Count; i++)
|
|
{
|
|
streaming.Update(series[i]);
|
|
}
|
|
|
|
// Eventing
|
|
var pubSource = new TSeries();
|
|
var eventing = new Coral(pubSource, period, cd);
|
|
for (int i = 0; i < series.Count; i++)
|
|
{
|
|
pubSource.Add(series[i]);
|
|
}
|
|
|
|
// Compare last values
|
|
Assert.Equal(batchResult.Last.Value, spanOutput[^1], 1e-9);
|
|
Assert.Equal(batchResult.Last.Value, streaming.Last.Value, 1e-9);
|
|
Assert.Equal(batchResult.Last.Value, eventing.Last.Value, 1e-9);
|
|
|
|
// Compare all values (batch vs span)
|
|
for (int i = 0; i < batchResult.Count; i++)
|
|
{
|
|
Assert.Equal(batchResult[i].Value, spanOutput[i], 1e-9);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates determinism: same inputs always produce same outputs.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Coral_Deterministic()
|
|
{
|
|
const int period = 10;
|
|
var bars = _gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
var series = bars.Close;
|
|
|
|
var coral1 = new Coral(period);
|
|
var coral2 = new Coral(period);
|
|
|
|
for (int i = 0; i < series.Count; i++)
|
|
{
|
|
coral1.Update(series[i]);
|
|
coral2.Update(series[i]);
|
|
}
|
|
|
|
Assert.Equal(coral1.Last.Value, coral2.Last.Value, 15);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates that different periods produce different outputs.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Coral_DifferentPeriods_ProduceDifferentOutputs()
|
|
{
|
|
var bars = _gbm.Fetch(300, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
var series = bars.Close;
|
|
|
|
var coral5 = new Coral(5);
|
|
var coral50 = new Coral(50);
|
|
|
|
int differenceCount = 0;
|
|
for (int i = 0; i < series.Count; i++)
|
|
{
|
|
coral5.Update(series[i]);
|
|
coral50.Update(series[i]);
|
|
|
|
if (i > 50) // After warmup
|
|
{
|
|
double diff = Math.Abs(coral5.Last.Value - coral50.Last.Value);
|
|
if (diff > 1e-10)
|
|
{
|
|
differenceCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Different periods must produce meaningfully different outputs
|
|
Assert.True(differenceCount > 100, $"Expected >100 different values, got {differenceCount}");
|
|
}
|
|
}
|