Files
QuanTAlib/lib/statistics/percentile/tests/Percentile.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

99 lines
2.9 KiB
C#

namespace QuanTAlib.Validation;
/// <summary>
/// Percentile validation tests — self-consistency and cross-indicator validation.
/// Percentile(p=50) must match Median indicator exactly.
/// </summary>
public sealed class PercentileValidationTests
{
[Fact]
public void Percentile50_Matches_MedianIndicator()
{
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123);
var source = new TSeries();
for (int i = 0; i < 100; i++)
{
source.Add(gbm.Next());
}
int period = 14;
// Percentile at 50%
var percentile = new Percentile(period, 50.0);
var pResults = new double[source.Count];
// Median
var median = new Median(period);
var mResults = new double[source.Count];
for (int i = 0; i < source.Count; i++)
{
var tv = new TValue(source.Times[i], source.Values[i]);
pResults[i] = percentile.Update(tv).Value;
mResults[i] = median.Update(tv).Value;
}
for (int i = 0; i < source.Count; i++)
{
Assert.Equal(mResults[i], pResults[i], precision: 10);
}
}
[Fact]
public void Percentile_BatchAndStreaming_Match()
{
double[] data = [10, 20, 15, 30, 25, 40, 35, 50, 45, 60, 55, 70, 65, 80, 75];
int period = 5;
double percent = 25.0;
// Streaming
var p = new Percentile(period, percent);
var streamingResults = new double[data.Length];
for (int i = 0; i < data.Length; i++)
{
streamingResults[i] = p.Update(new TValue(DateTime.UtcNow, data[i])).Value;
}
// Batch via spans
var spanOutput = new double[data.Length];
Percentile.Batch(data.AsSpan(), spanOutput.AsSpan(), period, percent);
for (int i = 0; i < data.Length; i++)
{
Assert.Equal(streamingResults[i], spanOutput[i], precision: 10);
}
}
[Fact]
public void Percentile_KnownValues()
{
// {10, 20, 30, 40, 50} sorted, p=25 → rank = 0.25*4 = 1.0 → sorted[1] = 20
var p = new Percentile(5, 25.0);
p.Update(new TValue(DateTime.UtcNow, 10));
p.Update(new TValue(DateTime.UtcNow, 20));
p.Update(new TValue(DateTime.UtcNow, 30));
p.Update(new TValue(DateTime.UtcNow, 40));
var result = p.Update(new TValue(DateTime.UtcNow, 50));
Assert.Equal(20.0, result.Value);
}
[Fact]
public void Percentile_BoundaryValues()
{
// p=0 → minimum, p=100 → maximum
var p0 = new Percentile(5, 0.0);
var p100 = new Percentile(5, 100.0);
double[] data = { 30, 10, 50, 20, 40 };
for (int i = 0; i < data.Length; i++)
{
var tv = new TValue(DateTime.UtcNow, data[i]);
p0.Update(tv);
p100.Update(tv);
}
Assert.Equal(10.0, p0.Last.Value);
Assert.Equal(50.0, p100.Last.Value);
}
}