Files
QuanTAlib/lib/reversals/pivotcam/tests/Pivotcam.Validation.Tests.cs
Miha Kralj 060649192f docs: remove C# Implementation Considerations sections, clean up temp scripts, reorganize test files
- 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
2026-03-12 12:34:16 -07:00

277 lines
8.8 KiB
C#

// PIVOTCAM Validation Tests - Camarilla Pivot Points
// Self-consistency validation across all API modes.
//
// Note: No external library (Skender, TA-Lib, Tulip, Ooples) implements
// Camarilla Pivot Points. Validation focuses on mathematical correctness
// and mode consistency.
namespace QuanTAlib.Tests;
public sealed class PivotcamValidationTests
{
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_EqualsHLC_Over3()
{
var bars = CreateGbmBars(count: 100);
var p = new Pivotcam();
for (int i = 0; i < bars.Count; i++)
{
_ = p.Update(bars[i], isNew: true);
if (i >= 1)
{
double prevH = bars[i - 1].High;
double prevL = bars[i - 1].Low;
double prevC = bars[i - 1].Close;
double expectedPP = (prevH + prevL + prevC) / 3.0;
Assert.Equal(expectedPP, p.PP, precision: 10);
}
}
}
[Fact]
public void MathCorrectness_AllLevels_MatchCamarillaFormula()
{
var bars = CreateGbmBars(count: 100);
var p = new Pivotcam();
for (int i = 0; i < bars.Count; i++)
{
_ = p.Update(bars[i], isNew: true);
if (i >= 1)
{
double pH = bars[i - 1].High;
double pL = bars[i - 1].Low;
double pC = bars[i - 1].Close;
double pp = (pH + pL + pC) / 3.0;
double range = pH - pL;
Assert.Equal(pp, p.PP, precision: 10);
Assert.Equal(pC + range * 1.0833 / 12.0, p.R1, precision: 4);
Assert.Equal(pC - range * 1.0833 / 12.0, p.S1, precision: 4);
Assert.Equal(pC + range * 1.1666 / 12.0, p.R2, precision: 4);
Assert.Equal(pC - range * 1.1666 / 12.0, p.S2, precision: 4);
Assert.Equal(pC + range * 1.25 / 12.0, p.R3, precision: 4);
Assert.Equal(pC - range * 1.25 / 12.0, p.S3, precision: 4);
Assert.Equal(pC + range * 1.5 / 12.0, p.R4, precision: 4);
Assert.Equal(pC - range * 1.5 / 12.0, p.S4, precision: 4);
}
}
}
// -- Self-Consistency: Streaming == Batch --------------------------------------
[Fact]
public void StreamingMatchesBatch_PP()
{
var bars = CreateGbmBars();
// Streaming
var streaming = new Pivotcam();
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 = Pivotcam.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 Pivotcam();
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];
Pivotcam.Batch(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 9 levels) --------------------
[Fact]
public void StreamingMatchesBatchAll_AllLevels()
{
var bars = CreateGbmBars(count: 300);
// Streaming
var streaming = new Pivotcam();
var sPP = new double[bars.Count];
var sR1 = new double[bars.Count];
var sS1 = new double[bars.Count];
var sR2 = new double[bars.Count];
var sS2 = new double[bars.Count];
var sR3 = new double[bars.Count];
var sS3 = new double[bars.Count];
var sR4 = new double[bars.Count];
var sS4 = 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;
sR2[i] = streaming.R2;
sS2[i] = streaming.S2;
sR3[i] = streaming.R3;
sS3[i] = streaming.S3;
sR4[i] = streaming.R4;
sS4[i] = streaming.S4;
}
// BatchAll
var bPP = new double[bars.Count];
var bR1 = new double[bars.Count];
var bS1 = new double[bars.Count];
var bR2 = new double[bars.Count];
var bS2 = new double[bars.Count];
var bR3 = new double[bars.Count];
var bS3 = new double[bars.Count];
var bR4 = new double[bars.Count];
var bS4 = new double[bars.Count];
Pivotcam.BatchAll(bars.HighValues, bars.LowValues, bars.CloseValues,
bPP, bR1, bS1, bR2, bS2, bR3, bS3, bR4, bS4);
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);
Assert.Equal(sR2[i], bR2[i], precision: 10);
Assert.Equal(sS2[i], bS2[i], precision: 10);
Assert.Equal(sR3[i], bR3[i], precision: 10);
Assert.Equal(sS3[i], bS3[i], precision: 10);
Assert.Equal(sR4[i], bR4[i], precision: 10);
Assert.Equal(sS4[i], bS4[i], precision: 10);
}
}
// -- Determinism ---------------------------------------------------------------
[Fact]
public void SameInput_ProducesSameOutput()
{
var bars = CreateGbmBars(count: 200, seed: 123);
var p1 = new Pivotcam();
var p2 = new Pivotcam();
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);
Assert.Equal(p1.R2, p2.R2);
Assert.Equal(p1.S2, p2.S2);
Assert.Equal(p1.R3, p2.R3);
Assert.Equal(p1.S3, p2.S3);
Assert.Equal(p1.R4, p2.R4);
Assert.Equal(p1.S4, p2.S4);
}
// -- Calculate Returns Valid Indicator -----------------------------------------
[Fact]
public void Calculate_ReturnsValidIndicatorAndResults()
{
var bars = CreateGbmBars(count: 100);
var (results, indicator) = Pivotcam.Calculate(bars);
Assert.NotNull(results);
Assert.Equal(bars.Count, results.Count);
Assert.True(indicator.IsHot);
}
// -- Level Ordering Invariant --------------------------------------------------
[Fact]
public void AllBars_SupportResistanceLevelsOrdered()
{
// Camarilla: S4 < S3 < S2 < S1 < Close-based < R1 < R2 < R3 < R4
// Note: PP is based on HLC/3 and may be above or below close,
// but resistance levels are always ordered R1 < R2 < R3 < R4
// and support levels are always ordered S4 < S3 < S2 < S1
var bars = CreateGbmBars(count: 200);
var p = new Pivotcam();
for (int i = 0; i < bars.Count; i++)
{
_ = p.Update(bars[i], isNew: true);
if (p.IsHot)
{
Assert.True(p.S4 <= p.S3, $"S4 > S3 at bar {i}");
Assert.True(p.S3 <= p.S2, $"S3 > S2 at bar {i}");
Assert.True(p.S2 <= p.S1, $"S2 > S1 at bar {i}");
Assert.True(p.R1 <= p.R2, $"R1 > R2 at bar {i}");
Assert.True(p.R2 <= p.R3, $"R2 > R3 at bar {i}");
Assert.True(p.R3 <= p.R4, $"R3 > R4 at bar {i}");
}
}
}
}