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 / 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()