mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-13 16:18:05 +00:00
- Updated class count in classes.svg from 938 to 1078. - Adjusted comments percentage in comments.svg from 33.06 to 33.02. - Revised average cyclomatic complexity in complexity.svg from 2.19 to 2.12. - Increased source files count in files.svg from 1099 to 1275. - Updated lines of code in loc.svg from 114549 to 129859. - Increased methods count in methods.svg from 12035 to 14066. - Updated public types count in public-api.svg from 1086 to 1225. - Revised missing indicators report with updated counts and categories, reflecting recent implementations and planned additions.
183 lines
5.2 KiB
C#
183 lines
5.2 KiB
C#
namespace QuanTAlib.Tests;
|
|
|
|
/// <summary>
|
|
/// Harmean Validation Tests - Self-consistency validation.
|
|
/// No external TA library implements rolling harmonic mean, so we validate
|
|
/// against mathematical properties and internal consistency.
|
|
/// </summary>
|
|
public sealed class HarmeanValidationTests
|
|
{
|
|
private static TSeries CreateGbmSeries(int count = 500, int seed = 42)
|
|
{
|
|
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: seed);
|
|
var times = new List<long>(count);
|
|
var values = new List<double>(count);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var bar = gbm.Next(isNew: true);
|
|
times.Add(bar.Time);
|
|
values.Add(bar.Close);
|
|
}
|
|
return new TSeries(times, values);
|
|
}
|
|
|
|
[Fact]
|
|
public void ConstantInput_ReturnsConstant()
|
|
{
|
|
// HM of identical values = that value
|
|
var h = new Harmean(20);
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
h.Update(new TValue(DateTime.UtcNow, 42.0));
|
|
}
|
|
Assert.Equal(42.0, h.Last.Value, 10);
|
|
}
|
|
|
|
[Fact]
|
|
public void HarmeanLeqGeometricMean()
|
|
{
|
|
// HM-GM inequality: HM ≤ GM for all positive values
|
|
var series = CreateGbmSeries();
|
|
int period = 20;
|
|
var h = new Harmean(period);
|
|
var g = new Geomean(period);
|
|
|
|
for (int i = 0; i < series.Count; i++)
|
|
{
|
|
h.Update(series[i]);
|
|
g.Update(series[i]);
|
|
if (h.IsHot && g.IsHot)
|
|
{
|
|
Assert.True(h.Last.Value <= g.Last.Value + 1e-10,
|
|
$"HM-GM violated at bar {i}: HM={h.Last.Value}, GM={g.Last.Value}");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void HarmeanLeqArithmeticMean()
|
|
{
|
|
// HM ≤ AM for all positive values
|
|
var series = CreateGbmSeries();
|
|
int period = 20;
|
|
var h = new Harmean(period);
|
|
var sma = new Sma(period);
|
|
|
|
for (int i = 0; i < series.Count; i++)
|
|
{
|
|
h.Update(series[i]);
|
|
sma.Update(series[i]);
|
|
if (h.IsHot)
|
|
{
|
|
Assert.True(h.Last.Value <= sma.Last.Value + 1e-10,
|
|
$"HM-AM violated at bar {i}: HM={h.Last.Value}, AM={sma.Last.Value}");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void BatchAndStreaming_Match()
|
|
{
|
|
var series = CreateGbmSeries();
|
|
int period = 14;
|
|
|
|
// Streaming
|
|
var hStream = new Harmean(period);
|
|
var streamResults = new double[series.Count];
|
|
for (int i = 0; i < series.Count; i++)
|
|
{
|
|
hStream.Update(series[i]);
|
|
streamResults[i] = hStream.Last.Value;
|
|
}
|
|
|
|
// Batch
|
|
var batchResult = Harmean.Batch(series, period);
|
|
for (int i = 0; i < series.Count; i++)
|
|
{
|
|
Assert.Equal(streamResults[i], batchResult[i].Value, 8);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void OutputIsPositive()
|
|
{
|
|
var series = CreateGbmSeries();
|
|
var h = new Harmean(14);
|
|
for (int i = 0; i < series.Count; i++)
|
|
{
|
|
h.Update(series[i]);
|
|
Assert.True(h.Last.Value > 0, $"Output not positive at bar {i}: {h.Last.Value}");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Calculate_ReturnsCorrectResults()
|
|
{
|
|
var series = CreateGbmSeries(100);
|
|
var (results, indicator) = Harmean.Calculate(series, 14);
|
|
Assert.True(indicator.IsHot);
|
|
Assert.Equal(100, results.Count);
|
|
Assert.True(double.IsFinite(results[^1].Value));
|
|
}
|
|
|
|
[Fact]
|
|
public void NearConstant_NearConstant()
|
|
{
|
|
// Values very close together → HM ≈ AM ≈ the value
|
|
var h = new Harmean(10);
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
h.Update(new TValue(DateTime.UtcNow, 100.0 + i * 0.001));
|
|
}
|
|
Assert.True(Math.Abs(h.Last.Value - 100.01) < 0.1,
|
|
$"Expected near 100.01, got {h.Last.Value}");
|
|
}
|
|
|
|
[Fact]
|
|
public void SpanBatch_MatchesTSeriesBatch()
|
|
{
|
|
var series = CreateGbmSeries(200);
|
|
int period = 14;
|
|
|
|
var batchResult = Harmean.Batch(series, period);
|
|
|
|
var src = series.Values;
|
|
Span<double> output = new double[200];
|
|
Harmean.Batch(src, output, period);
|
|
|
|
for (int i = 0; i < 200; i++)
|
|
{
|
|
Assert.Equal(batchResult[i].Value, output[i], 8);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void MeanInequality_HM_LE_GM_LE_AM()
|
|
{
|
|
// Full mean inequality chain: HM ≤ GM ≤ AM
|
|
var series = CreateGbmSeries(200);
|
|
int period = 14;
|
|
var h = new Harmean(period);
|
|
var g = new Geomean(period);
|
|
var sma = new Sma(period);
|
|
|
|
for (int i = 0; i < series.Count; i++)
|
|
{
|
|
h.Update(series[i]);
|
|
g.Update(series[i]);
|
|
sma.Update(series[i]);
|
|
if (h.IsHot && g.IsHot)
|
|
{
|
|
double hm = h.Last.Value;
|
|
double gm = g.Last.Value;
|
|
double am = sma.Last.Value;
|
|
|
|
Assert.True(hm <= gm + 1e-10,
|
|
$"HM > GM at bar {i}: HM={hm}, GM={gm}");
|
|
Assert.True(gm <= am + 1e-10,
|
|
$"GM > AM at bar {i}: GM={gm}, AM={am}");
|
|
}
|
|
}
|
|
}
|
|
}
|