Code / experiments/micro/expH_oos_stability/benchmark.py
experiments/micro/expH_oos_stability/benchmark.py
224 lines
# =============================================================================
# Project : anomaly-atlas
# File : experiments/micro/expH_oos_stability/benchmark.py
# Purpose : Out-of-sample stability on the untouched validation split
# 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)
# =============================================================================
"""Experiment H — the validation split (2016-2021), opened for the first time.
Re-evaluates, with machinery identical to expC/expD/expG (imported from
their committed benchmarks, not re-implemented): the expG pool net of
costs, the ES->SPY basis effect, and the daily reversal family. The sealed
HOLDOUT (2022->) is not touched.
"""
from __future__ import annotations
import importlib.util
import json
import sys
from datetime import UTC, datetime
from pathlib import Path
import numpy as np
REPO_ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(REPO_ROOT / "benchmarks"))
sys.path.insert(0, str(REPO_ROOT / "src"))
from hardware_manifest import collect_manifest # noqa: E402
from anomaly_atlas.data.cleaning import rth_day_grids # noqa: E402
from anomaly_atlas.data.hf_client import HFMarketDataClient # noqa: E402
from anomaly_atlas.data.universe import LIQUID_ETF, LIQUID_STOCK, VALIDATION # noqa: E402
from anomaly_atlas.stats.bootstrap import moving_block_bootstrap, percentile_ci # noqa: E402
from anomaly_atlas.stats.multiple_testing import bootstrap_pvalue # noqa: E402
from anomaly_atlas.stats.reversion import ac1, variance_ratio # noqa: E402
from anomaly_atlas.validation.artifacts import edge_spread # noqa: E402
def load_module(name: str, rel: str):
spec = importlib.util.spec_from_file_location(name, REPO_ROOT / rel)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
expg = load_module("expg_bench", "experiments/micro/expG_cost_frontier/benchmark.py")
expd = load_module("expd_bench", "experiments/micro/expD_leadlag_scan/benchmark.py")
ADJ = "adj_split"
VAL_SUBS = {"2016-2018": ("2016-01-01", "2019-01-01"),
"2019-2021": ("2019-01-01", "2022-01-01")}
KAPPA_CHECK = ("0.25", "1.0")
def eval_pool_on(client, rev_pool, ll_pool, start, end, label) -> list[dict]:
items = []
for r in rev_pool:
bars = client.get_bars(r["asset"], r["ticker"], r["timeframe"], ADJ, start, end)
stream = expg.contrarian_stream(bars, r["timeframe"])
hs = expg.half_spread(client, r["asset"], r["ticker"], ADJ, start, end)
if len(stream) < 150:
continue
item = {"rule": f"R:{r['ticker']}:{r['timeframe']}", "family": "reversion",
"window": label} | expg.sweep(stream, hs)
items.append(item)
needed = {"SPY"} | {p["leader"] for p in ll_pool} | {p["follower"] for p in ll_pool}
grids = {}
for name in sorted(needed):
if name == "ES":
g = rth_day_grids(client.get_bars("futures", "ES", "1min",
"contin_adj_ratio", start, end))
else:
asset = ("etf" if name in ("SPY", "QQQ") or name.startswith("XL") else "stock")
g = rth_day_grids(client.get_bars(asset, name, "1min", ADJ, start, end))
grids[name] = g
day_list = sorted(grids["SPY"].keys())
for p in ll_pool:
if p["leader"] not in grids or p["follower"] not in grids:
continue
stream = expg.leadlag_stream(grids[p["leader"]], grids[p["follower"]],
day_list, p["sign"])
if len(stream) < 150:
continue
traded = p["follower"]
asset = ("futures" if traded == "ES" else
"etf" if traded in ("SPY", "QQQ") or traded.startswith("XL") else "stock")
adj = "contin_adj_ratio" if traded == "ES" else ADJ
hs = expg.half_spread(client, asset, traded, adj, start, end)
item = {"rule": f"L:{p['pair']}:{'+' if p['sign'] > 0 else '-'}",
"family": "leadlag", "window": label} | expg.sweep(stream, hs)
items.append(item)
return items, grids, day_list
def main() -> None:
run_utc = datetime.now(UTC)
client = HFMarketDataClient()
rev_pool, ll_pool = expg.pool_from_committed_results()
s, e = VALIDATION
# (a,b) pool on validation
items, grids, day_list = eval_pool_on(client, rev_pool, ll_pool, s, e, "validation")
for it in items:
print(it["rule"], "kappa* =", it.get("kappa_star"))
# CKX sub-period check (Q-a robustness clause)
ckx_subs = {}
for sub, (ss, ee) in VAL_SUBS.items():
bars = client.get_bars("stock", "CKX", "1day", ADJ, ss, ee)
stream = expg.contrarian_stream(bars, "1day")
hs = expg.half_spread(client, "stock", "CKX", ADJ, ss, ee)
ckx_subs[sub] = expg.sweep(stream, hs) if len(stream) >= 100 else {"n_days": len(stream)}
# (c) ES->SPY and SPX->SPY fresh xcorr on validation
xcorr_out = {}
spy_mat = expd.day_matrix(grids["SPY"], day_list)
for name in ("ES", "SPX"):
if name == "SPX":
g = rth_day_grids(client.get_bars("index", "SPX", "1min", None, s, e))
else:
g = grids.get("ES") or rth_day_grids(
client.get_bars("futures", "ES", "1min", "contin_adj_ratio", s, e))
mat = expd.day_matrix(g, day_list)
m = expd.analyze_pair(mat[0], mat[1], spy_mat[0], spy_mat[1])
if m:
xcorr_out[f"{name}->SPY"] = {k: m[k] for k in
("fresh_xcorr", "raw_xcorr", "p_fresh_+1",
"p_fresh_-1", "n_fresh_pairs")}
# ES splice invariance
for adj in ("contin_adj_absolute", "contin_UNadj"):
g = rth_day_grids(client.get_bars("futures", "ES", "1min", adj, s, e))
mat = expd.day_matrix(g, day_list)
m = expd.analyze_pair(mat[0], mat[1], spy_mat[0], spy_mat[1])
if m:
xcorr_out[f"ES[{adj}]->SPY"] = {"fresh_-1": m["fresh_xcorr"]["-1"],
"fresh_+1": m["fresh_xcorr"]["1"]}
# (d) daily reversal family on validation vs train values
rc = expg.latest(str(REPO_ROOT / "results/expC_reversion_scan/*/results.json"))
train_daily = {c["ticker"]: c for c in rc["cells"]
if c["timeframe"] == "1day" and c["period"] == "2008-2015"}
family = []
for asset, ticker in [("stock", t) for t in LIQUID_STOCK] + \
[("etf", t) for t in LIQUID_ETF]:
bars = client.get_bars(asset, ticker, "1day", ADJ, s, e)
if len(bars) < 500:
continue
closes = np.array([b["close"] for b in bars])
r = np.diff(np.log(closes))
a = ac1(r)
vr30 = variance_ratio(r, 30)
o = np.array([b["open"] for b in bars])
h = np.array([b["high"] for b in bars])
lo = np.array([b["low"] for b in bars])
c = closes
sp = edge_spread(o, h, lo, c)
var = r.var()
bounce = -(sp**2) / 4 / var if np.isfinite(sp) and var > 0 else 0.0
boot = moving_block_bootstrap(r, ac1, block=21, n_boot=300, seed=42)
lo_ci, hi_ci = percentile_ci(boot - bounce)
tr = train_daily.get(ticker, {})
family.append({
"ticker": ticker,
"val_ac1": round(a, 5), "val_excess_ac1": round(a - bounce, 5),
"val_excess_ci95": [round(lo_ci, 5), round(hi_ci, 5)],
"val_p_excess": bootstrap_pvalue(boot - bounce, 0.0),
"val_vr30_excess": round(vr30 - (1 + 2 * a * (1 - 1 / 30)), 4),
"train_excess_ac1": tr.get("excess_ac1"),
"train_vr30_excess": round(tr["vr30"] - (1 + 2 * tr["ac1"] * (1 - 1 / 30)), 4)
if tr else None,
})
ck = {str(k): [i["rule"] for i in items if i["net"].get(str(k), {}).get("positive")
and i["net"].get(str(k), {}).get("ci95_bp", [1])[0] > 0]
for k in (0.25, 1.0)}
med_val = float(np.median([f["val_vr30_excess"] for f in family]))
med_train = float(np.median([f["train_vr30_excess"] for f in family
if f["train_vr30_excess"] is not None]))
summary = {
"pool_evaluated": len(items),
"net_pos_ci_at": ck,
"ckx_validation": next((i for i in items if i["rule"].startswith("R:CKX")), None),
"ckx_subperiods": ckx_subs,
"es_spy_val_fresh_-1": xcorr_out.get("ES->SPY", {}).get("fresh_xcorr", {}).get("-1"),
"es_spy_val_p_-1": xcorr_out.get("ES->SPY", {}).get("p_fresh_-1"),
"spx_spy_val_fresh_-1": xcorr_out.get("SPX->SPY", {}).get("fresh_xcorr", {}).get("-1"),
"daily_family_median_vr30_excess": {"train_2008_2015": round(med_train, 4),
"validation": round(med_val, 4)},
}
results = {
"experiment": "expH_oos_stability",
"run_utc": run_utc.isoformat(),
"author": "Simon-Pierre Boucher",
"contact": "contact@spboucher.ai",
"data_source": "hfmarketdata.io",
"confidence_level": "evaluates Level-1/2 claims on the validation split",
"protocol": {"validation": VALIDATION, "subs": VAL_SUBS,
"holdout_untouched": True},
"pool_items": items,
"xcorr": xcorr_out,
"daily_family": family,
"summary": summary,
"client_stats": vars(client.stats) | {"refreshes": list(client.stats.refreshes)},
"manifest": collect_manifest(),
}
out_dir = REPO_ROOT / "results" / "expH_oos_stability" / run_utc.strftime("%Y%m%dT%H%M%SZ")
out_dir.mkdir(parents=True)
(out_dir / "results.json").write_text(
json.dumps(expg.sanitize(results), indent=2, allow_nan=False) + "\n")
print(f"\nwrote {out_dir.relative_to(REPO_ROOT)}/results.json")
print(json.dumps(expg.sanitize(summary), indent=1))
if __name__ == "__main__":
main()