SPB Git

Repository Public Research preview

04c_targets_and_spanning.py 6.2 KB 87 lines python

1"""Design-review follow-ups. (a) Alternative daily targets: NS fit on CMT maturities >= 2y (2,3,5,7,10,20,30) — the segment the futures2stand in for; (b) ceiling: regress CMT factor changes on cash 2/5/10/30y yield changes (no basis error); (c) intraday spanning test:3leave-one-contract-out — fit a 3-factor model on the other contracts' minute returns and predict the held-out contract, by sampling frequency."""4import sys, json5from pathlib import Path6import numpy as np, pandas as pd7ROOT = Path(__file__).resolve().parents[1]; sys.path.insert(0, str(ROOT / "src"))8from wp47.ns import read_fred, fit_panel9from wp47.returns import ROOTS10CACHE = ROOT / "data/cache"; OUT = ROOT / "results/metrics"11FAC = ["level", "slope", "curvature"]12y = read_fred(ROOT / "data/external/fred").loc["2007-01-01":]13F2, R2 = fit_panel(y[["DGS2", "DGS3", "DGS5", "DGS7", "DGS10", "DGS20", "DGS30"]], min_obs=6)14F2.to_csv(OUT / "daily_factors_cmt_2y_plus.csv")15D = pd.read_parquet(CACHE / "returns_daily.parquet"); D.index = pd.to_datetime(D.index)16X = D[ROOTS].where(D[[f"n_{r}" for r in ROOTS]].ge(100).values)17def rolling_oos(dF, X, start_q="2011Q1"):18    panel = dF.join(X, how="inner"); preds = []19    for q in pd.period_range(start_q, panel.index[-1].to_period("Q"), freq="Q"):20        train = panel.loc[q.start_time - pd.DateOffset(years=3): q.start_time - pd.Timedelta(days=1)]; test = panel.loc[q.start_time:q.end_time]21        roots = [r for r in ROOTS if train[r].notna().mean() >= 0.85]; tr = train.dropna(subset=roots); te = test.dropna(subset=roots)22        if len(tr) < 500 or len(te) == 0: continue23        Xa = np.column_stack([np.ones(len(tr)), tr[roots].values])24        for f in FAC:25            b = np.linalg.lstsq(Xa, tr[f].values, rcond=None)[0]26            preds.append(pd.DataFrame({"date": te.index, "factor": f, "actual": te[f].values, "pred": te[roots].values @ b[1:]}))27    return pd.concat(preds)28def fid(P):29    out = {}30    for per, (a, b) in {"full": ("2011-01-01", "2026-12-31"), "2011_2015": ("2011-01-01", "2015-12-15"), "2016_2019": ("2015-12-16", "2019-12-31"), "2020_2021": ("2020-01-01", "2021-12-31"), "2022_2026": ("2022-01-01", "2026-12-31")}.items():31        s = P[(P.date >= a) & (P.date <= b)]32        out[per] = {f: {"n": int((s.factor == f).sum()), "r2_oos": round(float(1 - ((s[s.factor == f].actual - s[s.factor == f].pred) ** 2).sum() / ((s[s.factor == f].actual - s[s.factor == f].actual.mean()) ** 2).sum()), 4),33                        "slope": round(float(np.polyfit(s[s.factor == f].pred, s[s.factor == f].actual, 1)[0]), 4),34                        "rmse_bp": round(float(np.sqrt(((s[s.factor == f].actual - s[s.factor == f].pred) ** 2).mean()) * 100), 2),35                        "sd_actual_bp": round(float(s[s.factor == f].actual.std() * 100), 2)} for f in FAC}36    return out37res = {}38P2 = rolling_oos(F2.diff().dropna().loc["2008":], X); P2.to_parquet(CACHE / "mimicking_oos_daily_cmt2y.parquet")39res["cmt_2y_plus"] = fid(P2)40res["cmt_2y_plus_summary"] = {"rmse_fit_bp": float(np.sqrt(np.nanmean(R2.loc["2008":].values ** 2)) * 100), "dfactor_sd_bp": (F2.diff().loc["2008":].std() * 100).round(2).to_dict(),41                              "corr_dfactors": F2.diff().loc["2008":].corr().round(3).to_dict()}42# (b) ceiling with cash yields (in-sample, same rolling OOS protocol for comparability)43dy = y[["DGS2", "DGS5", "DGS10", "DGS30"]].diff()44Fc = pd.read_csv(OUT / "daily_factors_cmt.csv", index_col=0, parse_dates=True).diff().dropna().loc["2008":]45def rolling_oos_generic(dF, Xc, start_q="2011Q1"):46    panel = dF.join(Xc, how="inner").dropna(); preds = []47    cols = list(Xc.columns)48    for q in pd.period_range(start_q, panel.index[-1].to_period("Q"), freq="Q"):49        tr = panel.loc[q.start_time - pd.DateOffset(years=3): q.start_time - pd.Timedelta(days=1)]; te = panel.loc[q.start_time:q.end_time]50        if len(tr) < 500 or len(te) == 0: continue51        Xa = np.column_stack([np.ones(len(tr)), tr[cols].values])52        for f in FAC:53            b = np.linalg.lstsq(Xa, tr[f].values, rcond=None)[0]54            preds.append(pd.DataFrame({"date": te.index, "factor": f, "actual": te[f].values, "pred": te[cols].values @ b[1:]}))55    return pd.concat(preds)56res["ceiling_cash_2_5_10_30_for_cmt"] = fid(rolling_oos_generic(Fc, dy))57res["ceiling_cash_2_5_10_30_for_cmt2y"] = fid(rolling_oos_generic(F2.diff().dropna().loc["2008":], dy))58# (c) leave-one-contract-out spanning at minute frequency (2019–2026, six roots), by sampling frequency, in-sample PCA(3) on other five59R = pd.read_parquet(CACHE / "returns_1m.parquet").set_index("ts"); R.index = pd.to_datetime(R.index, utc=True).tz_convert("America/New_York")60R = R.loc["2019-01-01":, ROOTS]61span = {}62for k in (1, 5, 30):63    if k == 1: Rk = R.dropna()64    else:65        Rk = R.fillna(0.0).groupby([R.index.floor(f"{k}min")]).sum(min_count=1); Rk = Rk[R.notna().groupby(R.index.floor(f"{k}min")).sum().ge(1).all(axis=1)].dropna()66    # restrict to US hours 08:00–16:00 ET for liquidity, and all hours separately67    out = {}68    for lab, sel in (("08-16ET", (Rk.index.hour >= 8) & (Rk.index.hour < 16)), ("all_hours", np.ones(len(Rk), bool))):69        Z = Rk[sel]70        rec = {}71        for held in ROOTS:72            others = [r for r in ROOTS if r != held]73            Xo = Z[others].values; Xo = Xo - Xo.mean(0)74            U, S, Vt = np.linalg.svd(Xo, full_matrices=False); Fk = U[:, :3] * S[:3]75            b = np.linalg.lstsq(np.column_stack([np.ones(len(Fk)), Fk]), Z[held].values, rcond=None)[0]76            e = Z[held].values - np.column_stack([np.ones(len(Fk)), Fk]) @ b77            rec[held] = round(float(1 - e.var() / Z[held].values.var()), 4)78        # explained variance of 3 PCs on all six79        X6 = Z[ROOTS].values; X6 = X6 - X6.mean(0); s = np.linalg.svd(X6, compute_uv=False) ** 280        rec["pc_share_3"] = round(float(s[:3].sum() / s.sum()), 4); rec["pc_shares"] = (s / s.sum()).round(4).tolist(); rec["n_obs"] = int(len(Z))81        out[lab] = rec82    span[f"{k}m"] = out83res["leave_one_out_spanning_2019_2026"] = span84json.dump(res, open(OUT / "targets_and_spanning.json", "w"), indent=1)85print(json.dumps({k: {p: {f: v[f]["r2_oos"] for f in FAC} for p, v in res[k].items()} for k in ("cmt_2y_plus", "ceiling_cash_2_5_10_30_for_cmt", "ceiling_cash_2_5_10_30_for_cmt2y")}, indent=1))86print(json.dumps(span, indent=1))87
Files main
Menu

Theme