spb/anomaly-atlas Public License
Systematic discovery & rigorous validation of statistical anomalies in open HF market data (hfmarketdata.io) — pre-registered, artifact-null-driven, fully reproducible. Live atlas: www.anomaly-atlas.io
Python 61.4%
JavaScript 28.7%
CSS 8.6%
Shell 0.7%
Makefile 0.5%
1# =============================================================================2# Project : anomaly-atlas3# File : benchmarks/synthetic/test_gate_expc.py4# Purpose : §8.1 gate for the expC additions: EDGE spread, FDR, bootstrap p5# Author : Simon-Pierre Boucher6# Contact : contact@spboucher.ai7# Data src : hfmarketdata.io (sole data source)8# Created : 2026-08-129# Modified : 2026-08-1210# Platform : macOS / Apple Silicon (arm64)11# License : All rights reserved (research code)12# =============================================================================13"""Gate the detectors added for expC before they touch real data:1415 * EDGE spread from synthetic OHLC bars: recovers a planted Roll spread,16 reads ~0 on a spread-free random walk;17 * Benjamini-Hochberg: controls FDR on uniform nulls, finds planted signal;18 * bootstrap_pvalue: uniform-ish under the null, small under a real effect.19"""2021from __future__ import annotations2223import sys24from pathlib import Path2526import numpy as np2728sys.path.insert(0, str(Path(__file__).resolve().parent))2930from generators import random_walk, roll_bounce_prices # noqa: E4023132from anomaly_atlas.stats.bootstrap import moving_block_bootstrap33from anomaly_atlas.stats.multiple_testing import (34 benjamini_hochberg,35 bonferroni,36 bootstrap_pvalue,37)38from anomaly_atlas.stats.reversion import ac139from anomaly_atlas.validation.artifacts import edge_spread4041SEEDS = [1, 2, 3, 4, 5]424344def bars_from_ticks(log_prices: np.ndarray, per_bar: int = 30):45 """Aggregate a synthetic tick path into OHLC bars (price space)."""46 n = (len(log_prices) // per_bar) * per_bar47 p = np.exp(log_prices[:n]).reshape(-1, per_bar)48 return p[:, 0], p.max(axis=1), p.min(axis=1), p[:, -1]495051# ----------------------------------------------------------------- EDGE gate52def test_edge_recovers_planted_spread_from_bars():53 spread = 0.00454 for seed in SEEDS:55 ticks = roll_bounce_prices(120_000, spread=spread, sigma=0.0008, seed=seed)56 o, h, low, c = bars_from_ticks(ticks)57 est = edge_spread(o, h, low, c)58 assert np.isfinite(est)59 assert abs(est - spread) / spread < 0.30 # bar aggregation loses info606162def test_edge_reads_near_zero_on_spreadless_walk():63 for seed in SEEDS:64 ticks = random_walk(120_000, sigma=0.0008, seed=seed)65 o, h, low, c = bars_from_ticks(ticks)66 est = edge_spread(o, h, low, c)67 # undefined (NaN) or tiny relative to the planted case68 assert (not np.isfinite(est)) or est < 0.001697071# ------------------------------------------------------------------ FDR gate72def test_bh_controls_false_discoveries_on_pure_null():73 rng = np.random.default_rng(7)74 false_rates = []75 for _ in range(200):76 p = rng.random(100) # all null77 false_rates.append(benjamini_hochberg(p, alpha=0.05).mean())78 assert np.mean(false_rates) < 0.05 # FDR controlled798081def test_bh_finds_planted_signal_and_bonferroni_is_stricter():82 rng = np.random.default_rng(8)83 p = np.concatenate([rng.random(90), rng.random(10) * 1e-5]) # 10 real84 bh = benjamini_hochberg(p, alpha=0.05)85 bf = bonferroni(p, alpha=0.05)86 assert bh[90:].all() # all planted found87 assert bh.sum() >= bf.sum() # BH never stricter than Bonferroni88 assert bh[:90].sum() <= 5 # few false positives899091def test_bh_counts_nan_toward_m():92 p = np.array([0.001, np.nan, np.nan, np.nan])93 # m=4: threshold for rank 1 is 0.05/4=0.0125 -> still rejected94 assert benjamini_hochberg(p, alpha=0.05)[0]95 assert not benjamini_hochberg(p, alpha=0.05)[1:].any()969798# ------------------------------------------------------- bootstrap p-value gate99def test_bootstrap_pvalue_calibration():100 # null: AC1 of a random walk -> p should be comfortably non-small101 r = np.diff(random_walk(60_000, seed=3))102 boot = moving_block_bootstrap(r, ac1, block=390, n_boot=300, seed=3)103 assert bootstrap_pvalue(boot, 0.0) > 0.05104 # real effect: AC1 of a bounce series -> tiny p105 rb = np.diff(roll_bounce_prices(60_000, spread=0.003, seed=3))106 boot_b = moving_block_bootstrap(rb, ac1, block=390, n_boot=300, seed=3)107 assert bootstrap_pvalue(boot_b, 0.0) < 0.01108