"""Gamma-wall migration study — pre-reg studies/2026-09-09-wall-migration-prereg.md.""" import gzip, json, glob, os, numpy as np, pandas as pd, warnings from scipy import stats warnings.filterwarnings("ignore") src=open("crown_policies.py").read().split("\ndf=run()")[0]; cp={}; exec(src, cp) gp={}; exec(open("gamma_policy.py").read().split("\nsnaps={}")[0], gp); compute_gamma=gp["compute_gamma"]; f=gp["f"] FP=cp["FP"]; d=cp["load_nq"](); QUAD=cp["QUAD"] hist={} for p in sorted(glob.glob(f"{FP}/gex/history/*.json.gz")): j=json.load(gzip.open(p)); hist[pd.Timestamp(j["date"]).date()]=dict(rows=j["qqq"],close=j["qqq_close"]) sessions=sorted(d["sday"].unique()); prev_of={sessions[i]:sessions[i-1] for i in range(1,len(sessions))} px={} for sday,g in d.groupby("sday"): a=g[g["min"]<=550]; b=g[g["min"]<=610]; r=g[(g["min"]>=570)&(g["min"]<960)] px[sday]=dict(nq0910=float(a.iloc[-1]["close"]) if len(a) else None, nq1010=float(b.iloc[-1]["close"]) if len(b) else None, close=float(r.iloc[-1]["close"]) if len(r) else None) rows=[] for sday,g in d.groupby("sday"): p=prev_of.get(sday); rth=g[(g["min"]>=570)&(g["min"]<960)].reset_index(drop=True) if not p or p not in hist or sday not in hist or len(rth)<300 or sday in QUAD or not px[p]["close"] or not px[sday]["nq1010"]: continue pc=hist[p]["close"]; s0=pc*px[sday]["nq0910"]/px[p]["close"]; s1=pc*px[sday]["nq1010"]/px[p]["close"] old=compute_gamma(hist[p]["rows"],s0); new=compute_gamma(hist[sday]["rows"],s1) if not old or not new: continue dcw=(new["call"]-old["call"])/s1*100; dpw=(new["put"]-old["put"])/s1*100; dmid=(dcw+dpw)/2; dtilt=new["tilt"]-old["tilt"] fcross=np.sign(s1-old["flip"])!=np.sign(s1-new["flip"]) k=rth.iloc[0]["open"]/s0; kn=px[sday]["nq1010"]/s1 sides={"M1_wall_drift": (1 if dcw>0 and dpw>0 else (-1 if dcw<0 and dpw<0 else 0)), "M2_tilt_shift": (1 if dtilt>=0.08 else (-1 if dtilt<=-0.08 else 0)), "M3_flip_cross": (int(np.sign(s1-new["flip"])) if fcross else 0)} f5=cp["five_min"](rth); i5=int(np.searchsorted(f5.endmin.values, 611)) # first 5m bar ending after 10:10 base=dict(date=sday,regime=new["regime"],dcw=round(dcw,3),dpw=round(dpw,3),dmid=round(dmid,3),dtilt=round(dtilt,3),fcross=bool(fcross), ret_1010_close=(px[sday]["close"]/px[sday]["nq1010"]-1)*100, cage_new=new["put"]*kn<=rth.iloc[-1]["close"]<=new["call"]*kn, cage_old=old["put"]*k<=rth.iloc[-1]["close"]<=old["call"]*k, cw_moved=new["call"]!=old["call"], pw_moved=new["put"]!=old["put"]) for pol,side in sides.items(): w=cp["walk"](rth,611,side,f5,min(i5,len(f5)-1)) if side else None rows.append(dict(pol=pol,side=side,trig=w is not None,**base,**(w or {}))) df=pd.DataFrame(rows); os.makedirs("data",exist_ok=True); df.to_parquet("data/wall_migration.parquet") mid=pd.Timestamp("2025-03-15").date(); S=df[df.pol=="M1_wall_drift"] L=[f"# Gamma-wall migration — results · {pd.Timestamp.now():%Y-%m-%d %H:%M}\n", f"Pre-registration: `2026-09-09-wall-migration-prereg.md`. Sessions {S.date.nunique()} ({S.date.min()} → {S.date.max()}), quad-witching excluded.\n", "## Descriptive", f"- Wall moved at the 10:10 refresh: call {S.cw_moved.mean()*100:.0f}%, put {S.pw_moved.mean()*100:.0f}%; both same direction {((S.dcw>0)&(S.dpw>0)|(S.dcw<0)&(S.dpw<0)).mean()*100:.0f}%; |Δtilt| ≥ 0.08 {(S.dtilt.abs()>=0.08).mean()*100:.0f}%; flip crossed spot {S.fcross.mean()*100:.0f}%.", f"- Cage held (RTH close inside walls): new walls {S.cage_new.mean()*100:.0f}% vs old walls {S.cage_old.mean()*100:.0f}%."] S=S.assign(terc=pd.qcut(S.dmid.rank(method="first"),3,labels=["down","flat","up"])) L.append("- NQ 10:10→16:00 return by Δmid tercile: "+", ".join(f"{k} {x.ret_1010_close.mean():+.3f}% (up-share {(x.ret_1010_close>0).mean()*100:.0f}%, n={len(x)})" for k,x in S.groupby("terc"))+".") L.append("\n## Policies (Part A fill rules from 10:10)") for pol,g in df.groupby("pol"): t=g[g.trig] if len(t)<20: L.append(f"- **{pol}**: triggers {len(t)} — n too small."); continue h1=t[t.date=mid]; p=stats.ttest_1samp(t.r,0,alternative="greater").pvalue ok=t.r.mean()>0 and p<0.0167 and h1.r.mean()>0 and h2.r.mean()>0 and len(t)>=100 L.append(f"- **{pol}**: triggers {len(t)} (long {(t.side>0).sum()}/short {(t.side<0).sum()}), W/L/T {(t.res=='win').sum()}/{(t.res=='loss').sum()}/{(t.res=='timeout').sum()}, R/trigger **{t.r.mean():+.3f}** (p={p:.3f}), halves {h1.r.mean():+.3f} (n={len(h1)}) / {h2.r.mean():+.3f} (n={len(h2)}), by side long {t[t.side>0].r.mean():+.3f} / short {t[t.side<0].r.mean():+.3f}, $ at 1 NQ {t.pts.sum()*20:+,.0f} → **{'VALIDATED' if ok else 'REFUTED'}**.") res="\n".join(L); open(f"{FP}/studies/2026-09-09-wall-migration-results.md","w").write(res); print(res)