#!/usr/bin/env python """Pre-FOMC overnight drift — forward-only shadow grader. FROZEN SPEC (do not change without dating a new version; the desk rule is forward-only track records are never silently rewritten): SIGNAL On each US FOMC decision day D, go long 1 NQ at the 18:00 ET bar OPEN of the prior trading day, exit at the 09:30 ET RTH OPEN on D. (~15.5h hold, one continuous Globex session, no stop.) COST 1.0 index point round trip (desk COST_PTS, MBP-10 audit 2026-07-26). GRADED gross bps, net bps, net points, and EXCESS over the contemporaneous non-FOMC overnight baseline (the honest increment if you already hold overnight longs). STATUS SHADOW ONLY. Never sized. Never fed to the Desk Read synthesis. SEED CUTOFF 2026-08-25 (the date the study was frozen). Observations on or before it are `historical`; after it are `forward`. Only the forward series can promote the rule. PROMOTION BAR (pre-specified 2026-08-26, before any forward observation): 1. forward n >= 8 (one full FOMC year), AND 2. forward mean net bps > 0 AND forward hit rate >= 55%, AND 3. pooled t (historical + forward) still > 3.0, AND 4. forward excess over the contemporaneous non-FOMC baseline > 0. Failing any of these after n >= 8 => rule is refuted forward and is dropped. Usage: venv/bin/python prefomc_shadow.py # update + print track record venv/bin/python prefomc_shadow.py --json # machine-readable summary """ import argparse import json import sys from datetime import date from pathlib import Path import numpy as np import fundamentals_nq as F TRACK = Path("/fp-data/studies/prefomc-shadow-track.jsonl") SEED_CUTOFF = date(2026, 8, 25) COST_PTS = 1.0 FOMC_RE = r"(Fed )?Interest Rate Decision" # FMP renamed it mid-2026; match both def build(): """Recompute every gradeable FOMC observation from the archive.""" cal = F.load_calendar() # already US-only px = F.build_price() fomc = set(cal[cal["event"].str.fullmatch(FOMC_RE, case=False, na=False)]["date"]) px = px[px["date"] >= date(2016, 1, 1)] # contemporaneous non-FOMC overnight baseline, expanding (backward only) base = px[~px["date"].isin(fomc)][["date", "ret_preopen"]].dropna().sort_values("date") recs = [] for _, r in px[px["date"].isin(fomc)].dropna(subset=["ret_preopen"]).sort_values("date").iterrows(): d = r["date"] prior = base[base["date"] < d]["ret_preopen"] bl = float(prior.mean()) if len(prior) >= 50 else None gross_bps = float(r["ret_preopen"]) entry = float(r["open"]) / (1 + gross_bps / 1e4) # implied 18:00 ET entry cost_bps = COST_PTS / entry * 1e4 net_bps = gross_bps - cost_bps recs.append({ "date": str(d), "phase": "historical" if d <= SEED_CUTOFF else "forward", "entry_px": round(entry, 2), "exit_px": round(float(r["open"]), 2), "gross_bps": round(gross_bps, 2), "cost_bps": round(cost_bps, 3), "net_bps": round(net_bps, 2), "net_pts": round(float(r["open"]) - entry - COST_PTS, 2), "baseline_bps": round(bl, 2) if bl is not None else None, "excess_bps": round(gross_bps - bl, 2) if bl is not None else None, "win": bool(net_bps > 0), }) return recs def load_existing(): if not TRACK.exists(): return {} out = {} for line in TRACK.read_text().splitlines(): if line.strip(): r = json.loads(line) out[r["date"]] = r return out def stats(rows, key="net_bps"): v = np.array([r[key] for r in rows if r.get(key) is not None], float) if len(v) < 2: return dict(n=len(v), mean=float(v.mean()) if len(v) else float("nan"), t=float("nan"), hit=float("nan"), median=float("nan")) return dict(n=len(v), mean=float(v.mean()), t=float(v.mean() / (v.std(ddof=1) / np.sqrt(len(v)))), hit=float(100 * (v > 0).mean()), median=float(np.median(v))) def main(): ap = argparse.ArgumentParser() ap.add_argument("--json", action="store_true") args = ap.parse_args() recs = build() prior = load_existing() new = [r for r in recs if r["date"] not in prior] # idempotent rewrite; historical rows are recomputed but never reclassified TRACK.parent.mkdir(parents=True, exist_ok=True) with TRACK.open("w") as f: for r in sorted(recs, key=lambda x: x["date"]): f.write(json.dumps(r) + "\n") hist = [r for r in recs if r["phase"] == "historical"] fwd = [r for r in recs if r["phase"] == "forward"] sh, sf, sa = stats(hist), stats(fwd), stats(recs) ex_f = stats(fwd, "excess_bps") if args.json: print(json.dumps({"historical": sh, "forward": sf, "pooled": sa, "forward_excess": ex_f, "new_this_run": [r["date"] for r in new]}, indent=1)) return print("=" * 78) print("PRE-FOMC OVERNIGHT DRIFT — shadow track record") print(" long NQ prior-day 18:00 ET -> FOMC-day 09:30 ET open, cost 1.0 pt RT") print(f" seed cutoff {SEED_CUTOFF} · file {TRACK}") print("=" * 78) if new: print(f"\n {len(new)} new observation(s) this run: {', '.join(r['date'] for r in new)}") print(f"\n{'phase':>12} {'n':>4} {'mean_net':>10} {'t':>7} {'hit%':>7} {'median':>9}") for lab, s in [("historical", sh), ("FORWARD", sf), ("pooled", sa)]: if s["n"] == 0: print(f"{lab:>12} {0:>4} (none yet)") continue print(f"{lab:>12} {s['n']:>4} {s['mean']:>+10.2f} {s['t']:>+7.2f} {s['hit']:>7.1f} {s['median']:>+9.2f}") if ex_f["n"]: print(f"\n forward excess over non-FOMC baseline: n={ex_f['n']} " f"mean={ex_f['mean']:+.2f} bps t={ex_f['t']:+.2f}") print("\n PROMOTION BAR (frozen 2026-08-26):") c1 = sf["n"] >= 8 c2 = sf["n"] >= 2 and sf["mean"] > 0 and sf["hit"] >= 55 c3 = sa["n"] >= 2 and sa["t"] > 3.0 c4 = ex_f["n"] >= 2 and ex_f["mean"] > 0 for ok, txt in [(c1, f"forward n >= 8 (now {sf['n']})"), (c2, "forward mean > 0 and hit >= 55%"), (c3, f"pooled t > 3.0 (now {sa['t']:+.2f})"), (c4, "forward excess over baseline > 0")]: print(f" [{'x' if ok else ' '}] {txt}") print(f"\n STATUS: {'PROMOTABLE — review' if (c1 and c2 and c3 and c4) else 'accruing — shadow only, do not size'}") print(f"\n last 8 observations:") print(f" {'date':>12} {'phase':>11} {'gross':>8} {'net_bps':>8} {'net_pts':>8} {'excess':>8}") for r in sorted(recs, key=lambda x: x["date"])[-8:]: ex = f"{r['excess_bps']:+8.2f}" if r["excess_bps"] is not None else " -" print(f" {r['date']:>12} {r['phase']:>11} {r['gross_bps']:>+8.2f} {r['net_bps']:>+8.2f} " f"{r['net_pts']:>+8.2f} {ex}") if __name__ == "__main__": main()