# ============================================================================= # Project : anomaly-atlas # File : benchmarks/synthetic/test_gate_expf.py # Purpose : ยง8.1 gate for expF: Reality Check, SPA, Deflated Sharpe # Author : Simon-Pierre Boucher # Contact : contact@spboucher.ai # Data src : hfmarketdata.io (sole data source) # Created : 2026-08-12 # Modified : 2026-08-12 # Platform : macOS / Apple Silicon (arm64) # License : All rights reserved (research code) # ============================================================================= """Gate the correction battery before it touches real scan output: * a universe of PURE-NOISE rules must not survive RC/SPA (p not small), and its best Sharpe must be fully explained by selection (DSR ~ 0); * a planted genuinely profitable rule must survive all three; * the bootstrap must respect serial dependence (block structure). """ from __future__ import annotations import numpy as np from anomaly_atlas.stats.spa import deflated_sharpe, reality_check, spa_test T, N = 1500, 60 # ~6 years of days, 60 searched rules def noise_matrix(seed: int) -> np.ndarray: rng = np.random.default_rng(seed) return rng.normal(0.0, 0.01, (T, N)) def test_pure_noise_universe_does_not_survive(): ps_rc, ps_spa = [], [] for seed in (1, 2, 3, 4, 5): x = noise_matrix(seed) ps_rc.append(reality_check(x, n_boot=300, seed=seed)["p"]) ps_spa.append(spa_test(x, n_boot=300, seed=seed)["p"]) # under H0 the p-values should look uniform: none should be tiny, # and on average they must be comfortably away from 0 assert min(ps_rc) > 0.01 and np.mean(ps_rc) > 0.2 assert min(ps_spa) > 0.01 and np.mean(ps_spa) > 0.2 def test_planted_profitable_rule_survives_rc_and_spa(): for seed in (1, 2, 3): x = noise_matrix(seed) rng = np.random.default_rng(seed + 100) x[:, 7] = rng.normal(0.0015, 0.01, T) # daily SR ~ 0.15 (t ~ 5.8) rc = reality_check(x, n_boot=300, seed=seed) sp = spa_test(x, n_boot=300, seed=seed) assert rc["p"] < 0.02 and rc["best_rule"] == 7 assert sp["p"] < 0.02 and sp["best_rule"] == 7 def test_dsr_kills_noise_best_and_keeps_planted(): rng = np.random.default_rng(9) x = noise_matrix(9) srs = x.mean(axis=0) / x.std(axis=0, ddof=1) best = int(np.argmax(srs)) r = x[:, best] d_noise = deflated_sharpe( sr=float(srs[best]), t_len=T, skew=float(((r - r.mean()) ** 3).mean() / r.std() ** 3), kurt=float(((r - r.mean()) ** 4).mean() / r.std() ** 4), n_trials=N, sr_variance=float(srs.var(ddof=1)), ) assert d_noise["dsr"] < 0.6 # selection explains the best noise Sharpe # planted strong rule x[:, 3] = rng.normal(0.002, 0.01, T) srs2 = x.mean(axis=0) / x.std(axis=0, ddof=1) r2 = x[:, 3] d_real = deflated_sharpe( sr=float(srs2[3]), t_len=T, skew=float(((r2 - r2.mean()) ** 3).mean() / r2.std() ** 3), kurt=float(((r2 - r2.mean()) ** 4).mean() / r2.std() ** 4), n_trials=N, sr_variance=float(srs2.var(ddof=1)), ) assert d_real["dsr"] > 0.95 assert d_noise["dsr"] < d_real["dsr"] def test_stationary_bootstrap_preserves_dependence(): from anomaly_atlas.stats.spa import stationary_bootstrap_indices idx = stationary_bootstrap_indices(1000, mean_block=20, n_boot=50, seed=3) # consecutive indices should usually be consecutive (inside a block) consecutive = np.mean((np.diff(idx, axis=1) % 1000) == 1) assert consecutive > 0.9 # mean block 20 -> ~95% of steps are within-block assert idx.min() >= 0 and idx.max() < 1000