Files
fxhnt/tests/unit/test_gauntlet.py
jgrusewski 58bbb4520c feat: fxhnt foundation — hexagonal architecture + proven gauntlet + vertical slice
Enterprise clean-rebuild (no foxhunt code). Hexagonal/ports-and-adapters: pure domain (gauntlet
math, strategies, backtest, models) | ports (DataProvider, repositories) | adapters (Yahoo data,
SQLAlchemy operational [Postgres/SQLite], DuckDB analytical) | application (ResearchService, DI) |
CLI composition root. Gauntlet-first: Deflated Sharpe (Bailey-LdP) built + falsification-tested
(kills best-of-N-on-noise, keeps real premium). Full vertical slice runs end-to-end on real data:
data -> strategy(trend) -> backtest(net of costs) -> IS/OOS gauntlet -> persistence. 4/4 tests green.
Postgres+DuckDB split, pydantic contracts, typed, DRY via one-contract-per-port. ADR + README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:21:47 +02:00

42 lines
1.7 KiB
Python

"""Falsification test for the gauntlet — the proof it's safe to scale a search on top of it.
It MUST reject a best-of-N artifact mined on noise, and KEEP a genuine N=1 premium."""
from __future__ import annotations
import numpy as np
from fxhnt.domain.gauntlet import annualized_sharpe, deflated_sharpe, evaluate
RNG = np.random.default_rng(7)
T = 2520
N_TRIALS = 1000
def test_rejects_best_of_n_on_noise() -> None:
"""Mine the best of N random strategies on pure noise; the gauntlet must reject it."""
trials = RNG.normal(0.0, 0.01, (N_TRIALS, T))
srs = trials.mean(1) / trials.std(1)
best = int(np.argmax(srs))
best_r = trials[best]
sr_var = float(srs.var())
assert annualized_sharpe(best_r) > 0.8 # looks great in-sample by luck
split = int(0.6 * T)
v = evaluate(best_r[:split], best_r[split:], n_trials=N_TRIALS, sr_variance=sr_var, has_economic_rationale=False)
assert not v.passed, "gauntlet accepted an overfit best-of-N artifact"
assert v.dsr < 0.95
def test_keeps_real_premium() -> None:
"""A genuine positive-drift series, tested as one hypothesis, must pass."""
real = RNG.normal(0.0005, 0.01, T)
split = int(0.6 * T)
v = evaluate(real[:split], real[split:], n_trials=1, sr_variance=0.0, has_economic_rationale=True)
assert v.passed, "gauntlet rejected a genuine OOS-confirmed premium"
assert v.dsr >= 0.95
def test_dsr_falls_as_trials_rise() -> None:
"""The same returns get a strictly lower deflated Sharpe as the search size grows."""
r = RNG.normal(0.0004, 0.01, T)
sr_var = 0.04
assert deflated_sharpe(r, 1, sr_var) >= deflated_sharpe(r, 100, sr_var) >= deflated_sharpe(r, 10000, sr_var)