"""Export small aggregated series for the paper's figures (results/figdata/). Runs on M3U96a. Never writes raw bars.""" import sys from pathlib import Path import numpy as np, pandas as pd ROOT = Path(__file__).resolve().parents[1]; sys.path.insert(0, str(ROOT / "src")) from wp47.ns import loadings, LAMBDA_DL CACHE = ROOT / "data/cache"; OUT = ROOT / "results/metrics"; FIG = ROOT / "results/figdata"; FIG.mkdir(parents=True, exist_ok=True) ET = "America/New_York" Fm = pd.read_parquet(CACHE / "factors_1m.parquet").set_index("ts") Fm.index = pd.to_datetime(Fm.index, utc=True).tz_convert(ET) Fm = Fm.dropna(subset=["dL"]) Fm["yday"] = pd.to_datetime(Fm["yday"]).dt.normalize() # 1. sample days: cumulative changes over the yield day + implied 2y/10y L2, L10 = loadings(np.array([2.0]), LAMBDA_DL)[0], loadings(np.array([10.0]), LAMBDA_DL)[0] DAYS = {"2013-06-19": "FOMC", "2022-06-15": "FOMC", "2022-11-10": "CPI", "2024-08-02": "EMP"} parts = [] for d, typ in DAYS.items(): s = Fm[Fm["yday"] == pd.Timestamp(d)][["dL", "dS", "dC"]].copy() if len(s) == 0: print("missing day", d); continue c = s.cumsum() * 100 c.columns = ["cum_L_bp", "cum_S_bp", "cum_C_bp"] c["cum_y2_bp"] = (s["dL"] * L2[0] + s["dS"] * L2[1] + s["dC"] * L2[2]).cumsum() * 100 c["cum_y10_bp"] = (s["dL"] * L10[0] + s["dS"] * L10[1] + s["dC"] * L10[2]).cumsum() * 100 c["day"] = d; c["type"] = typ c.index.name = "ts" parts.append(c.reset_index()) pd.concat(parts).to_csv(FIG / "sample_days.csv", index=False) # 4. fidelity by year for CMT (primary) and GSW target rows = [] for lab, f in (("cmt", "mimicking_oos_daily.parquet"), ("gsw", "mimicking_oos_daily_gsw.parquet")): P = pd.read_parquet(CACHE / f) pcol = "pred_oos_nocst" if "pred_oos_nocst" in P.columns else "pred" P["year"] = pd.to_datetime(P["date"]).dt.year for (y, fac), g in P.groupby(["year", "factor"]): if len(g) < 30: continue r2 = 1 - np.sum((g.actual - g[pcol]) ** 2) / np.sum((g.actual - g.actual.mean()) ** 2) rows.append({"target": lab, "year": int(y), "factor": fac, "r2_oos": float(r2), "n": int(len(g))}) pd.DataFrame(rows).to_csv(FIG / "fidelity_by_year.csv", index=False) # 7. ETF vs futures FOMC-window pairs E = pd.read_parquet(CACHE / "factors_1m_etf.parquet") E.index = pd.to_datetime(E.index, utc=True).tz_convert(ET) if E.index.tz is None or str(E.index.tz) != ET else E.index J = E.join(Fm[["dL", "dS", "dC"]], how="inner", lsuffix="_etf", rsuffix="_fut") ev = pd.read_csv(OUT / "events.csv", parse_dates=["date"]); ev["ts"] = pd.to_datetime(ev["ts"], utc=True).dt.tz_convert(ET) rows = [] for _, e in ev[(ev.type == "FOMC") & (ev.scheduled) & (ev.ts >= J.index.min())].iterrows(): s = J.loc[e.ts - pd.Timedelta(minutes=5): e.ts + pd.Timedelta(minutes=24)] if len(s) >= 24: w = s.sum() * 100 rows.append({"date": e.date.strftime("%Y-%m-%d"), **{k: float(v) for k, v in w.items()}}) pd.DataFrame(rows).to_csv(FIG / "etf_fomc_pairs.csv", index=False) print("exported", {f.name: f.stat().st_size for f in FIG.glob("*.csv")}) # ------------------------------------------------------------------------------------------------ # 8. Event heatmaps: cumulative implied 2-year yield change (bp) from -5 min, columns -30..+120 minutes, rows = events rng = np.random.default_rng(47) L2v = loadings(np.array([2.0]), LAMBDA_DL)[0] Fm["dy2"] = Fm["dL"] * L2v[0] + Fm["dS"] * L2v[1] + Fm["dC"] * L2v[2] grid = np.arange(-30, 121) for typ in ("FOMC", "CPI", "EMP"): rows = [] for _, e in ev[(ev.type == typ) & (ev.scheduled) & (ev.date >= "2011-01-01")].iterrows(): seg = Fm.loc[e.ts - pd.Timedelta(minutes=30): e.ts + pd.Timedelta(minutes=120), ["dy2", "dL", "dS", "dC"]] if len(seg) < 120: continue idx = pd.date_range(e.ts - pd.Timedelta(minutes=30), e.ts + pd.Timedelta(minutes=120), freq="1min") seg = seg.reindex(idx, fill_value=0.0) cum = seg["dy2"].cumsum().values * 100 base = cum[25] # value at -5 min rows.append([e.date.strftime("%Y-%m-%d")] + list(np.round(cum - base, 3))) pd.DataFrame(rows, columns=["date"] + [str(m) for m in grid]).to_csv(FIG / f"event_heatmap_{typ}.csv", index=False) # 9. Event paths with bootstrap bands (RMS across events; 300 resamples of events) out = [] for typ in ("FOMC", "CPI", "EMP"): mats = {c: [] for c in ("dL", "dS", "dC", "dSR", "dy10")} L10v = loadings(np.array([10.0]), LAMBDA_DL)[0] for _, e in ev[(ev.type == typ) & (ev.scheduled) & (ev.date >= "2011-01-01")].iterrows(): seg = Fm.loc[e.ts - pd.Timedelta(minutes=60): e.ts + pd.Timedelta(minutes=120), ["dL", "dS", "dC"]] if len(seg) < 150: continue idx = pd.date_range(e.ts - pd.Timedelta(minutes=60), e.ts + pd.Timedelta(minutes=120), freq="1min"); seg = seg.reindex(idx, fill_value=0.0) seg["dSR"] = seg["dL"] + seg["dS"]; seg["dy10"] = seg["dL"] * L10v[0] + seg["dS"] * L10v[1] + seg["dC"] * L10v[2] cum = seg.cumsum() * 100 for c in mats: mats[c].append(cum[c].values) for c, v in mats.items(): A = np.array(v); n = len(A) rms = np.sqrt((A ** 2).mean(0)) bs = np.array([np.sqrt((A[rng.integers(0, n, n)] ** 2).mean(0)) for _ in range(300)]) lo, hi = np.percentile(bs, 5, axis=0), np.percentile(bs, 95, axis=0) for i, m in enumerate(range(-60, 121)): out.append({"type": typ, "series": c, "minute": m, "rms": rms[i], "lo": lo[i], "hi": hi[i], "n": n}) pd.DataFrame(out).to_csv(FIG / "event_paths_boot.csv", index=False) # 10. Fidelity by year with bootstrap 90% bands (resampling days within the year, 500 draws) rows = [] for lab, f in (("cmt", "mimicking_oos_daily.parquet"), ("gsw", "mimicking_oos_daily_gsw.parquet")): P = pd.read_parquet(CACHE / f); pcol = "pred_oos_nocst" if "pred_oos_nocst" in P.columns else "pred"; P["year"] = pd.to_datetime(P["date"]).dt.year for (y, fac), g in P.groupby(["year", "factor"]): if len(g) < 30: continue a, p = g.actual.values, g[pcol].values def r2(a, p): return 1 - np.sum((a - p) ** 2) / np.sum((a - a.mean()) ** 2) bs = [r2(a[i], p[i]) for i in (rng.integers(0, len(a), len(a)) for _ in range(500))] rows.append({"target": lab, "year": int(y), "factor": fac, "r2_oos": float(r2(a, p)), "lo": float(np.percentile(bs, 5)), "hi": float(np.percentile(bs, 95)), "n": int(len(g))}) pd.DataFrame(rows).to_csv(FIG / "fidelity_by_year.csv", index=False) print("exported", {f.name: f.stat().st_size for f in FIG.glob("*.csv")})