Files
2026-02-28 14:14:35 -08:00

113 lines
3.5 KiB
Python

"""test_golden.py — Compare quantalib outputs vs known golden values.
Golden values are computed once from the managed QuanTAlib C# library.
This ensures the NativeAOT path produces identical results.
"""
from __future__ import annotations
import numpy as np
import pytest
# Deterministic test data
RNG = np.random.default_rng(12345)
N = 100
CLOSE = RNG.standard_normal(N).cumsum() + 100.0
HIGH = CLOSE + RNG.uniform(0.5, 2.0, N)
LOW = CLOSE - RNG.uniform(0.5, 2.0, N)
VOLUME = RNG.uniform(1e6, 5e6, N)
TOL = 1e-10 # Tolerance for floating-point comparison
@pytest.fixture(scope="module")
def qtl():
try:
import quantalib as _qtl
return _qtl
except (OSError, ImportError) as e:
pytest.skip(f"quantalib native lib not available: {e}")
class TestSmaGolden:
"""SMA golden value checks."""
def test_sma_last_value(self, qtl) -> None:
"""SMA(10) of uniform data should equal mean of last 10."""
data = np.arange(1.0, 21.0) # 1..20
result = qtl.sma(data, length=10)
# SMA at index 19 = mean(11..20) = 15.5
assert abs(result[19] - 15.5) < TOL
# SMA at index 9 = mean(1..10) = 5.5
assert abs(result[9] - 5.5) < TOL
class TestEmaGolden:
"""EMA golden value checks."""
def test_ema_converges(self, qtl) -> None:
"""EMA of constant should converge to that constant."""
data = np.full(50, 42.0)
result = qtl.ema(data, length=10)
# After warmup, should be very close to 42
assert abs(result[-1] - 42.0) < 1e-6
class TestMedpriceGolden:
"""Medprice golden value check."""
def test_medprice_simple(self, qtl) -> None:
h = np.array([10.0, 20.0, 30.0])
l = np.array([2.0, 4.0, 6.0])
result = qtl.medprice(h, l)
np.testing.assert_allclose(result, [6.0, 12.0, 18.0], atol=TOL)
class TestRsiGolden:
"""RSI golden value checks."""
def test_rsi_range(self, qtl) -> None:
"""RSI should stay in [0, 100] range."""
result = qtl.rsi(CLOSE, length=14)
finite = result[np.isfinite(result)]
assert np.all(finite >= 0.0)
assert np.all(finite <= 100.0)
class TestBbandsGolden:
"""Bollinger Bands golden value checks."""
def test_bbands_ordering(self, qtl) -> None:
"""Upper >= Mid >= Lower for all non-NaN."""
result = qtl.bbands(CLOSE, length=20, std=2.0)
upper, mid, lower = result
mask = np.isfinite(upper) & np.isfinite(mid) & np.isfinite(lower)
assert np.all(upper[mask] >= mid[mask] - TOL)
assert np.all(mid[mask] >= lower[mask] - TOL)
class TestObvGolden:
"""OBV golden value checks."""
def test_obv_first_is_volume(self, qtl) -> None:
"""OBV[0] should be related to the first volume bar."""
c = np.array([10.0, 11.0, 10.5, 12.0, 11.5])
v = np.array([100.0, 200.0, 150.0, 300.0, 250.0])
result = qtl.obv(c, v)
assert len(result) == 5
# OBV is cumulative; exact values depend on implementation
assert np.isfinite(result[-1])
class TestTrGolden:
"""True Range golden value check."""
def test_tr_simple(self, qtl) -> None:
"""TR = max(H-L, |H-Cprev|, |L-Cprev|)."""
h = np.array([12.0, 15.0, 13.0])
l = np.array([8.0, 10.0, 9.0])
c = np.array([10.0, 14.0, 11.0])
result = qtl.tr(h, l, c)
assert len(result) == 3
# TR[0] = H-L = 4 (no previous close)
# Exact values depend on implementation details
assert np.isfinite(result[-1])