Honesty doctrine. Every candidate anomaly is an artifact until proven otherwise; in-sample results are never findings; past statistical regularity does not imply future returns. This is research on statistical properties of market data — not investment advice, not a trading system.

Code / benchmarks/synthetic/test_gate_expf.py

benchmarks/synthetic/test_gate_expf.py 93 lines
# =============================================================================
#  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