mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-06 21:17: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
244 lines
7.0 KiB
C#
244 lines
7.0 KiB
C#
|
|
// PIVOTDEM Validation Tests - DeMark Pivot Points
|
|
// Self-consistency validation across all API modes.
|
|
//
|
|
// Note: No external libraries (Skender, TA-Lib, Tulip, Ooples) implement
|
|
// DeMark pivot points. Validation focuses on mathematical correctness,
|
|
// conditional logic verification, and mode consistency.
|
|
|
|
namespace QuanTAlib.Tests;
|
|
|
|
public sealed class PivotdemValidationTests
|
|
{
|
|
private static TBarSeries CreateGbmBars(int count = 500, int seed = 42)
|
|
{
|
|
var gbm = new GBM(startPrice: 100.0, mu: 0.05, sigma: 0.20, seed: seed);
|
|
return gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
|
}
|
|
|
|
// -- Mathematical Correctness -------------------------------------------------
|
|
|
|
[Fact]
|
|
public void MathCorrectness_PP_MatchesConditionalFormula()
|
|
{
|
|
var bars = CreateGbmBars(count: 100);
|
|
var p = new Pivotdem();
|
|
|
|
for (int i = 0; i < bars.Count; i++)
|
|
{
|
|
_ = p.Update(bars[i], isNew: true);
|
|
|
|
if (i >= 1) // Need previous bar
|
|
{
|
|
double pO = bars[i - 1].Open;
|
|
double pH = bars[i - 1].High;
|
|
double pL = bars[i - 1].Low;
|
|
double pC = bars[i - 1].Close;
|
|
|
|
double x;
|
|
if (pC < pO) { x = pH + 2.0 * pL + pC; }
|
|
else if (pC > pO) { x = 2.0 * pH + pL + pC; }
|
|
else { x = pH + pL + 2.0 * pC; }
|
|
|
|
double expectedPP = x * 0.25;
|
|
Assert.Equal(expectedPP, p.PP, precision: 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void MathCorrectness_AllLevels_MatchFormula()
|
|
{
|
|
var bars = CreateGbmBars(count: 100);
|
|
var p = new Pivotdem();
|
|
|
|
for (int i = 0; i < bars.Count; i++)
|
|
{
|
|
_ = p.Update(bars[i], isNew: true);
|
|
|
|
if (i >= 1)
|
|
{
|
|
double pO = bars[i - 1].Open;
|
|
double pH = bars[i - 1].High;
|
|
double pL = bars[i - 1].Low;
|
|
double pC = bars[i - 1].Close;
|
|
|
|
double x;
|
|
if (pC < pO) { x = pH + 2.0 * pL + pC; }
|
|
else if (pC > pO) { x = 2.0 * pH + pL + pC; }
|
|
else { x = pH + pL + 2.0 * pC; }
|
|
|
|
double halfX = x * 0.5;
|
|
Assert.Equal(x * 0.25, p.PP, precision: 10);
|
|
Assert.Equal(halfX - pL, p.R1, precision: 10);
|
|
Assert.Equal(halfX - pH, p.S1, precision: 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
// -- Self-Consistency: Streaming == Batch --------------------------------------
|
|
|
|
[Fact]
|
|
public void StreamingMatchesBatch_PP()
|
|
{
|
|
var bars = CreateGbmBars();
|
|
|
|
// Streaming
|
|
var streaming = new Pivotdem();
|
|
var streamPP = new double[bars.Count];
|
|
for (int i = 0; i < bars.Count; i++)
|
|
{
|
|
_ = streaming.Update(bars[i], isNew: true);
|
|
streamPP[i] = streaming.PP;
|
|
}
|
|
|
|
// Batch
|
|
var batchResults = Pivotdem.Batch(bars);
|
|
|
|
for (int i = 1; i < bars.Count; i++)
|
|
{
|
|
if (double.IsNaN(streamPP[i]))
|
|
{
|
|
Assert.True(double.IsNaN(batchResults[i].Value),
|
|
$"Mismatch at {i}: streaming=NaN, batch={batchResults[i].Value}");
|
|
}
|
|
else
|
|
{
|
|
Assert.Equal(streamPP[i], batchResults[i].Value, precision: 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
// -- Self-Consistency: Streaming == Span ---------------------------------------
|
|
|
|
[Fact]
|
|
public void StreamingMatchesSpan_PP()
|
|
{
|
|
var bars = CreateGbmBars();
|
|
|
|
// Streaming
|
|
var streaming = new Pivotdem();
|
|
var streamPP = new double[bars.Count];
|
|
for (int i = 0; i < bars.Count; i++)
|
|
{
|
|
_ = streaming.Update(bars[i], isNew: true);
|
|
streamPP[i] = streaming.PP;
|
|
}
|
|
|
|
// Span
|
|
var spanPP = new double[bars.Count];
|
|
Pivotdem.Batch(bars.OpenValues, bars.HighValues, bars.LowValues, bars.CloseValues, spanPP);
|
|
|
|
for (int i = 1; i < bars.Count; i++)
|
|
{
|
|
if (double.IsNaN(streamPP[i]))
|
|
{
|
|
Assert.True(double.IsNaN(spanPP[i]));
|
|
}
|
|
else
|
|
{
|
|
Assert.Equal(streamPP[i], spanPP[i], precision: 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
// -- Self-Consistency: Streaming == BatchAll (all 3 levels) --------------------
|
|
|
|
[Fact]
|
|
public void StreamingMatchesBatchAll_AllLevels()
|
|
{
|
|
var bars = CreateGbmBars(count: 300);
|
|
|
|
// Streaming
|
|
var streaming = new Pivotdem();
|
|
var sPP = new double[bars.Count];
|
|
var sR1 = new double[bars.Count];
|
|
var sS1 = new double[bars.Count];
|
|
|
|
for (int i = 0; i < bars.Count; i++)
|
|
{
|
|
_ = streaming.Update(bars[i], isNew: true);
|
|
sPP[i] = streaming.PP;
|
|
sR1[i] = streaming.R1;
|
|
sS1[i] = streaming.S1;
|
|
}
|
|
|
|
// BatchAll
|
|
var bPP = new double[bars.Count];
|
|
var bR1 = new double[bars.Count];
|
|
var bS1 = new double[bars.Count];
|
|
|
|
Pivotdem.BatchAll(bars.OpenValues, bars.HighValues, bars.LowValues, bars.CloseValues,
|
|
bPP, bR1, bS1);
|
|
|
|
for (int i = 1; i < bars.Count; i++)
|
|
{
|
|
if (double.IsNaN(sPP[i]))
|
|
{
|
|
Assert.True(double.IsNaN(bPP[i]));
|
|
continue;
|
|
}
|
|
|
|
Assert.Equal(sPP[i], bPP[i], precision: 10);
|
|
Assert.Equal(sR1[i], bR1[i], precision: 10);
|
|
Assert.Equal(sS1[i], bS1[i], precision: 10);
|
|
}
|
|
}
|
|
|
|
// -- Determinism ---------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void SameInput_ProducesSameOutput()
|
|
{
|
|
var bars = CreateGbmBars(count: 200, seed: 123);
|
|
|
|
var p1 = new Pivotdem();
|
|
var p2 = new Pivotdem();
|
|
|
|
for (int i = 0; i < bars.Count; i++)
|
|
{
|
|
_ = p1.Update(bars[i], isNew: true);
|
|
_ = p2.Update(bars[i], isNew: true);
|
|
}
|
|
|
|
Assert.Equal(p1.PP, p2.PP);
|
|
Assert.Equal(p1.R1, p2.R1);
|
|
Assert.Equal(p1.S1, p2.S1);
|
|
}
|
|
|
|
// -- Calculate Returns Valid Indicator -----------------------------------------
|
|
|
|
[Fact]
|
|
public void Calculate_ReturnsValidIndicatorAndResults()
|
|
{
|
|
var bars = CreateGbmBars(count: 100);
|
|
|
|
var (results, indicator) = Pivotdem.Calculate(bars);
|
|
|
|
Assert.NotNull(results);
|
|
Assert.Equal(bars.Count, results.Count);
|
|
Assert.True(indicator.IsHot);
|
|
}
|
|
|
|
// -- Level Ordering Invariant --------------------------------------------------
|
|
|
|
[Fact]
|
|
public void AllBars_LevelsOrdered_S1_PP_R1()
|
|
{
|
|
var bars = CreateGbmBars(count: 200);
|
|
var p = new Pivotdem();
|
|
|
|
for (int i = 0; i < bars.Count; i++)
|
|
{
|
|
_ = p.Update(bars[i], isNew: true);
|
|
|
|
if (p.IsHot)
|
|
{
|
|
Assert.True(p.S1 <= p.PP, $"S1 > PP at bar {i}");
|
|
Assert.True(p.PP <= p.R1, $"PP > R1 at bar {i}");
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|