SPB Git

Repository Public Research preview

11_tables.py 41.5 KB 332 lines python

1"""Build LaTeX tables (paper/tables/*.tex), numeric macros (paper/numbers.tex) and the macro index (docs/NUMBERS_INDEX.md)2from results/metrics. Laptop-side, deterministic, no data access. Run: python3 -I scripts/11_tables.py"""3from __future__ import annotations4import json, re, math5from decimal import Decimal, ROUND_HALF_UP6from pathlib import Path7import csv8from collections import Counter, defaultdict9ROOT = Path(__file__).resolve().parents[1]; M = ROOT / "results/metrics"; T = ROOT / "paper/tables"; T.mkdir(parents=True, exist_ok=True)10J = lambda n: json.load(open(M / n))11def r(x, d=2):12    """half-up rounding to d decimals, returned as string."""13    if x is None or (isinstance(x, float) and math.isnan(x)): return "--"14    q = Decimal(1).scaleb(-d)15    s = str(Decimal(str(x)).quantize(q, rounding=ROUND_HALF_UP))16    if s.startswith("-"): s = "$-$" + s[1:]          # typographic minus sign (text and tables)17    return s18def ith(x): return f"{int(Decimal(str(x)).quantize(Decimal(1), rounding=ROUND_HALF_UP)):,}"   # integer with thousands separator19def pct(x, d=1): return r(100 * x, d) + r"\%"20def ci(v, d=2): return f"[{r(v[0], d)}, {r(v[1], d)}]"21ROOTS = ["ZT", "ZF", "ZN", "TN", "US", "UB"]; FAC = ["level", "slope", "curvature"]; FL = {"level": "Level", "slope": "Slope", "curvature": "Curvature"}; MN = {"level": "Level", "slope": "Slope", "curvature": "Curv"}22PER = [("full_2011_2026", "2011--2026"), ("2011_2015_ZLB", "2011--2015"), ("2016_2019", "2016--2019"), ("2020_2021", "2020--2021"), ("2022_2026", "2022--2026")]23PER2 = [("full", "2011--2026"), ("2011_2015", "2011--2015"), ("2016_2019", "2016--2019"), ("2020_2021", "2020--2021"), ("2022_2026", "2022--2026")]24NUM = {}   # macro name -> (value, source)25def mac(name, value, source):26    assert re.fullmatch(r"[A-Za-z]+", name), name27    NUM[name] = (value, source)28def table(fname, label, caption, header, body, notes, colspec, size=r"\footnotesize", wide=True, colsep="3pt"):29    """All tables share one font size (footnotesize); column separation is 3 pt (2 pt for the two widest tables)."""30    s = ["\\begin{table}[tp]", "\\centering", size, "\\begin{threeparttable}", f"\\caption{{{caption}}}", f"\\label{{{label}}}",31         f"\\setlength{{\\tabcolsep}}{{{colsep}}}",32         f"\\begin{{tabular}}{{{colspec}}}", "\\toprule", header, "\\midrule", body, "\\bottomrule", "\\end{tabular}",33         "\\begin{tablenotes}[flushleft]\\footnotesize", f"\\item \\textit{{Notes:}} {notes}", "\\end{tablenotes}", "\\end{threeparttable}", "\\end{table}", ""]34    (T / fname).write_text("\n".join(s))3536# ---------------- 1. data coverage ----------------37rd = J("returns_diagnostics.json"); P = rd["panel"]38ev = list(csv.DictReader(open(M / "events.csv")))39nF_s = sum(1 for e in ev if e["type"] == "FOMC" and e["scheduled"] == "True"); nF_u = sum(1 for e in ev if e["type"] == "FOMC" and e["scheduled"] == "False")40nCPI = sum(1 for e in ev if e["type"] == "CPI"); nEMP = sum(1 for e in ev if e["type"] == "EMP")41names = {"ZT": "2-Year Note", "ZF": "5-Year Note", "ZN": "10-Year Note", "TN": "Ultra 10-Year Note", "US": "Treasury Bond", "UB": "Ultra Treasury Bond"}42rows = []43for k in ROOTS:44    d = rd[k]45    rows.append(f"{k} & {names[k]} & {d['first'][:10]} & {r(d['bars'] / 1e6, 2)} & {ith(P['median_minutes_per_yday'][k])} & {r(d['sd_1m_bp'], 2)} & {r(P['daily_sd_pct'][k], 3)} & {d.get('contract_switches', d.get('roll_days', ''))} \\\\")46    mac(f"bars{k}", r(d['bars'] / 1e6, 2), "returns_diagnostics.json bars/1e6"); mac(f"sdOneMin{k}", r(d['sd_1m_bp'], 2), "returns_diagnostics.json sd_1m_bp")47table("tab_data.tex", "tab:data", "Treasury futures sample: six CME contracts, one-minute bars, 2008--2026",48      r"Root & Contract & First bar & Bars (M) & Minutes/day & SD 1-min (bp) & SD daily (\%) & Contract switches \\",49      "\n".join(rows),50      f"One-minute bars of the individual contracts from FinData OS (FirstRate Data), most-traded contract of each CME session, no splice or adjustment, {P['first'][:10]} to {P['last'][:10]}, UTC-stamped bars converted to Eastern time; a yield day runs from 15:30 ET to 15:30 ET. "51      f"``Bars (M)'' is the number of one-minute bars in millions, per root, from its first bar; across roots there are {r(P['minutes']/1e6,2)} million calendar minutes with at least one traded contract and {P['yield_days']:,} yield days. "52      "``Minutes/day'' is the median number of one-minute returns per yield day; ``SD 1-min'' is the standard deviation of one-minute log returns in basis points of price and ``SD daily'' that of the yield-day log return in percent, both over each root's full sample; ``Contract switches'' counts sessions on which the raw and the ratio-adjusted daily series disagree (contract splices). "53      f"Announcement calendar (not shown as rows), 2008--2026: {nF_s} scheduled FOMC statements and {nF_u} unscheduled policy statements (Federal Reserve), {nCPI} CPI and {nEMP} Employment Situation releases (ALFRED release dates, 08:30 ET). The 3-month SOFR future (SR3) is used only in the short-end extension from 2021. Entries are sample statistics without standard errors.",54      "llcrrrrr")55mac("nMinutes", r(P['minutes'] / 1e6, 2), "returns_diagnostics.json panel.minutes/1e6 (millions)"); mac("nYieldDays", f"{P['yield_days']:,}", "returns_diagnostics.json panel.yield_days")56mac("sampleFirst", P['first'][:10], "panel.first"); mac("sampleLast", P['last'][:10], "panel.last")57mac("nFomcScheduled", str(nF_s), "events.csv"); mac("nFomcUnscheduled", str(nF_u), "events.csv"); mac("nCpi", str(nCPI), "events.csv"); mac("nEmp", str(nEMP), "events.csv")5859# ---------------- 2. daily factors ----------------60ds = J("daily_factors_summary.json")61_n2008 = sum(1 for row in csv.DictReader(open(M / "daily_factors_cmt.csv")) if row[list(row.keys())[0]] >= "2008-01-01" and row["level"] not in ("", "nan"))62rows = []63for f in FAC:64    rows.append(f"{FL[f]} & {r(ds['factor_means_2008_'][f], 2)} & {r(ds['factor_sd_2008_'][f], 2)} & {r(ds['dfactor_sd_bp'][f], 1)} & " + " & ".join(r(ds['corr_dfactors'][f][g], 2) for g in FAC) + f" & {r(ds['corr_with_gsw_dfactors'][f], 2)} \\\\")65table("tab_daily_factors.tex", "tab:dailyfactors", f"Daily Nelson--Siegel factors from constant-maturity Treasury yields: summary statistics, 2008--2026 ({_n2008:,} yield days)",66      r"Factor & Mean (pp) & SD (pp) & SD of $\Delta$ (bp) & \multicolumn{3}{c}{Correlation of daily changes} & Corr.\ GSW \\ \cmidrule(lr){5-7} & & & & Level & Slope & Curvature & \\",67      "\n".join(rows),68      f"Factors are the daily OLS fit of the Nelson--Siegel curve with $\\lambda={ds['lambda_dl']}$ per year to the eleven H.15 constant-maturity yields (1, 3, 6 months; 1, 2, 3, 5, 7, 10, 20, 30 years). Level $L$ is the long-run yield, slope $S$ the short rate minus $L$ (hence negative on average) and curvature $C$ the medium-term hump, all in percentage points (pp); changes are first differences in basis points (bp). "69      f"Means, SDs and correlations use the {_n2008:,} daily observations from 2008-01-02 to {ds['last']} (the fit itself starts {ds['first']}). "70      f"Root-mean-squared fitting error {r(ds['rmse_fit_bp'],1)} bp ({r(ds['rmse_fit_bp_lambdahat'],1)} bp with $\\hat\\lambda={r(ds['lambda_hat_2008_2026'],3)}$ estimated by nonlinear least squares). "71      f"``Corr.\\ GSW'' is the correlation of daily factor changes with those obtained from the G\\\"urkaynak--Sack--Wright zero-coupon curve (1--30 years). Level correlates {r(ds['corr_level_vs_dgs10'],2)} with the 10-year yield and slope {r(ds['corr_slope_vs_spread10y3m'],3)} with the 3-month minus 10-year spread. Entries are sample moments without standard errors.",72      "lrrrrrrr")73mac("nDailyDaysFromTwoThousandEight", f"{_n2008:,}", "daily_factors_cmt.csv rows >= 2008 with a level estimate")74for f in FAC:75    mac(f"dsd{MN[f]}", r(ds['dfactor_sd_bp'][f], 1), "daily_factors_summary.json dfactor_sd_bp"); mac(f"gswCorr{MN[f]}", r(ds['corr_with_gsw_dfactors'][f], 2), "corr_with_gsw_dfactors")76mac("lambdaDL", str(ds['lambda_dl']), "lambda_dl"); mac("lambdaHat", r(ds['lambda_hat_2008_2026'], 3), "lambda_hat_2008_2026"); mac("fitRmse", r(ds['rmse_fit_bp'], 1), "rmse_fit_bp")77mac("nDailyDays", f"{ds['n_days']:,}", "n_days"); mac("corrLevelSlopeDaily", r(ds['corr_dfactors']['level']['slope'], 2), "corr_dfactors level-slope")7879# ---------------- 3. fidelity ----------------80fid = J("mimicking_fidelity.json"); rob = J("mimicking_fidelity_robustness.json"); ts = J("targets_and_spanning.json")81rows = []82PN = {"full_2011_2026": "Full", "2011_2015_ZLB": "Zlb", "2016_2019": "Mid", "2020_2021": "Pandemic", "2022_2026": "Recent"}83for key, lab in PER:84    cells = []85    for f in FAC:86        v = fid[key][f]; thr = 0.60 if f == "curvature" else 0.9087        r2txt = r(v['r2_oos'], 2); r2txt = f"\\textbf{{{r2txt}}}" if v['r2_oos'] < thr else r2txt88        cells.append(f"{r2txt} & {r(v['slope'],2)} & {r(v['rmse_bp'],1)} & {r(v['sd_actual_bp'],1)}")89        mac(f"fid{MN[f]}{PN[key]}", r(v['r2_oos'], 2), f"mimicking_fidelity.json {key}.{f}.r2_oos"); mac(f"fidSlope{MN[f]}{PN[key]}", r(v['slope'], 2), f"{key}.{f}.slope")90        mac(f"fidRmse{MN[f]}{PN[key]}", r(v['rmse_bp'], 1), f"{key}.{f}.rmse_bp")91    rows.append(f"{lab} & {fid[key]['level']['n']:,} & " + " & ".join(cells) + r" \\")92panelA = "\n".join(rows)93def r2row(label, d, keys):94    out = []95    for k in keys:96        if k in d: out.append(" & ".join(r(d[k][f]["r2_oos"], 2) for f in FAC))97        else: out.append(" & ".join(["--"] * 3))98    return f"{label} & " + " & ".join(out) + r" \\"99rowsB = [r2row("GSW zero curve, 1--30 years", rob["gsw"], [k for k, _ in PER2]), r2row("CMT, maturities $\\geq 1$ year", rob["cmt_1y_plus"], [k for k, _ in PER2]),100         r2row("CMT, maturities $\\geq 2$ years", ts["cmt_2y_plus"], [k for k, _ in PER2]), r2row("Ceiling: cash 2, 5, 10, 30-year CMT yields", ts["ceiling_cash_2_5_10_30_for_cmt"], [k for k, _ in PER2]),101         r2row("Six roots $+$ SR3 (estimates from 2021Q1)", rob["cmt_with_sr3"], [k for k, _ in PER2])]102headB = r"\midrule \multicolumn{14}{l}{\textit{Panel B: Out-of-sample $R^2$ for alternative targets and regressors (level, slope, curvature)}} \\" + "\n" + \103        r"Target / regressors & " + " & ".join(f"\\multicolumn{{3}}{{c}}{{{lab}}}" for _, lab in PER2) + r" \\" + "\n" + \104        "".join(f"\\cmidrule(lr){{{2+3*i}-{4+3*i}}}" for i in range(5)) + "\n" + " & " + " & ".join(["L & S & C"] * 5) + r" \\"105# Panel B has 16 columns if we keep 5 periods x 3; render as a separate tabular to keep widths sane106tabB = ["\\begin{table}[tp]", "\\centering", "\\footnotesize", "\\begin{threeparttable}", "\\caption{Out-of-sample $R^2$ of the daily factor changes under alternative targets and regressors, by sub-period}", "\\label{tab:fidelityalt}",107        "\\setlength{\\tabcolsep}{3pt}", "\\begin{tabular}{p{3.9cm}" + "rrr" * 5 + "}", "\\toprule", r"Target / regressors & " + " & ".join(f"\\multicolumn{{3}}{{c}}{{{lab}}}" for _, lab in PER2) + r" \\",108        "".join(f"\\cmidrule(lr){{{2+3*i}-{4+3*i}}}" for i in range(5)), " & " + " & ".join(["L & S & C"] * 5) + r" \\", "\\midrule", "\n".join(rowsB), "\\bottomrule", "\\end{tabular}",109        "\\begin{tablenotes}[flushleft]\\footnotesize", "\\item \\textit{Notes:} Out-of-sample $R^2$ of daily factor changes (level L, slope S, curvature C) on the mimicking-portfolio prediction, same rolling protocol as Table~\\ref{tab:fidelity} (weights estimated on the previous three years, re-estimated quarterly, applied to the following quarter). "110        "Rows change the daily target (Nelson--Siegel factors from the G\\\"urkaynak--Sack--Wright zero curve; from CMT yields of at least 1 or 2 years) or the regressors (the four cash constant-maturity yields 2, 5, 10 and 30 years instead of futures, which removes the futures basis and gives an upper bound; the six futures plus the front 3-month SOFR future SR3). "111        "In the SR3 row the columns headed 2011--2026 and 2020--2021 cover 2021Q1--2026Q4 and 2021 only, respectively, and are not comparable with the same columns of the other rows or of Table~\\ref{tab:fidelity}; compare the 2022--2026 column. L, S and C denote level, slope and curvature. Entries are point estimates without confidence intervals. Pre-registered target: Table~\\ref{tab:fidelity}.",112        "\\end{tablenotes}", "\\end{threeparttable}", "\\end{table}", ""]113(T / "tab_fidelity_alt.tex").write_text("\n".join(tabB))114table("tab_fidelity.tex", "tab:fidelity", "Out-of-sample fidelity of the futures mimicking portfolios for the daily Nelson--Siegel factor changes, 2011--2026 and sub-periods (H1)",115      r"Period & $n$ & \multicolumn{4}{c}{Level} & \multicolumn{4}{c}{Slope} & \multicolumn{4}{c}{Curvature} \\ \cmidrule(lr){3-6}\cmidrule(lr){7-10}\cmidrule(lr){11-14} & & $R^2$ & $b$ & RMSE & SD & $R^2$ & $b$ & RMSE & SD & $R^2$ & $b$ & RMSE & SD \\",116      panelA,117      "Each row evaluates, over $n$ out-of-sample yield days, the quarterly re-estimated tracking weights (OLS of the daily CMT-based factor change on the yield-day returns of the available Treasury futures over the trailing three years, applied to the following quarter only) on the daily change of the indicated factor. "118      "$R^2$ is the out-of-sample $1-\\sum(\\Delta\\beta-\\widehat{\\Delta\\beta})^2/\\sum(\\Delta\\beta-\\overline{\\Delta\\beta})^2$ of the realised daily change on the prediction formed with the weights only (no intercept); $b$ is the slope of actual on predicted; RMSE and SD (standard deviation of the actual change) are in basis points. "119      "A root enters the regression when it has at least 100 one-minute returns on at least 85\\% of the training days (root sets: ZT ZF ZN US in 2011--2012, plus UB from 2012Q4, plus TN from 2018Q4). Pre-registered thresholds: $R^2\\geq0.90$ for level and slope, $\\geq0.60$ for curvature; bold marks $R^2$ below the threshold. Entries are point estimates without confidence intervals.",120      "lr" + "rrrr@{\\hspace{8pt}}" * 2 + "rrrr")121for k, lab in PER2:122    for f in FAC:123        if k in rob["gsw"]: mac(f"fidGsw{MN[f]}{PN[[p for p,_ in PER][[q for q,_ in PER2].index(k)]]}", r(rob["gsw"][k][f]["r2_oos"], 2), f"mimicking_fidelity_robustness.json gsw.{k}")124        if k in ts["ceiling_cash_2_5_10_30_for_cmt"]: mac(f"fidCeil{MN[f]}{PN[[p for p,_ in PER][[q for q,_ in PER2].index(k)]]}", r(ts["ceiling_cash_2_5_10_30_for_cmt"][k][f]["r2_oos"], 2), f"targets_and_spanning.json ceiling_cash_2_5_10_30_for_cmt.{k}")125        if k in rob["cmt_with_sr3"]: mac(f"fidSrThree{MN[f]}{PN[[p for p,_ in PER][[q for q,_ in PER2].index(k)]]}", r(rob["cmt_with_sr3"][k][f]["r2_oos"], 2), f"mimicking_fidelity_robustness.json cmt_with_sr3.{k}")126        if k in ts["cmt_2y_plus"]: mac(f"fidTwoY{MN[f]}{PN[[p for p,_ in PER][[q for q,_ in PER2].index(k)]]}", r(ts["cmt_2y_plus"][k][f]["r2_oos"], 2), f"targets_and_spanning.json cmt_2y_plus.{k}")127        if k in rob["cmt_1y_plus"]: mac(f"fidOneY{MN[f]}{PN[[p for p,_ in PER][[q for q,_ in PER2].index(k)]]}", r(rob["cmt_1y_plus"][k][f]["r2_oos"], 2), f"mimicking_fidelity_robustness.json cmt_1y_plus.{k}")128ins = fid["insample_2016_2026_six_roots"]129for f in FAC: mac(f"fidInsample{MN[f]}", r(ins[f]["r2"], 2), "mimicking_fidelity.json insample_2016_2026_six_roots.r2")130mac("fidInsampleN", f"{ins['level']['n']:,}", "insample n")131by = fid["by_year"]; worst = min((v["slope"]["r2_oos"], y) for y, v in by.items()); best = max((v["slope"]["r2_oos"], y) for y, v in by.items())132mac("fidSlopeWorstYear", worst[1], "by_year"); mac("fidSlopeWorstYearValue", r(worst[0], 2), "by_year"); mac("fidSlopeBestYear", best[1], "by_year"); mac("fidSlopeBestYearValue", r(best[0], 2), "by_year")133lv = min(v["level"]["r2_oos"] for v in by.values()); mac("fidLevelMinYear", r(lv, 2), "by_year min level r2")134135# ---------------- 4. weights ----------------136W = J("mimicking_weights_quarterly.json")137inc = Counter(); acc = defaultdict(list)138for w in W:139    for rt in w["roots"]: inc[rt] += 1140    for f in FAC:141        for rt in w["roots"]: acc[(f, rt)].append(w["weights"][f][rt])142def msd(v):143    import statistics144    return (statistics.mean(v), statistics.pstdev(v)) if v else (None, None)145rows = []146for rt in ROOTS:147    cells = [r(ins[f]["weights"][rt], 3) for f in FAC]148    for f in FAC:149        m, s = msd(acc[(f, rt)]); cells.append(f"{r(m,3)} ({r(s,3)})" if m is not None else "--")150    rows.append(f"{rt} & " + " & ".join(cells) + f" & {inc[rt]}/{len(W)} \\\\")151rows.append(r"Constant (pp) & " + " & ".join(r(ins[f]["weights"]["const"], 4) for f in FAC) + r" & & & & \\")152rows.append(r"$R^2$ (in sample) & " + " & ".join(r(ins[f]["r2"], 2) for f in FAC) + r" & & & & \\")153rootsets = Counter(tuple(w["roots"]) for w in W)154rs_txt = "; ".join(f"{{{', '.join(k)}}} in {v} quarters" for k, v in rootsets.most_common())155_q5 = next((w["quarter"] for w in W if len(w["roots"]) >= 5), "--"); _q6 = next((w["quarter"] for w in W if len(w["roots"]) >= 6), "--")156table("tab_weights.tex", "tab:weights", f"Mimicking-portfolio weights: in-sample OLS estimates, 2016--2026, and quarterly out-of-sample estimates, {W[0]['quarter']}--{W[-1]['quarter']}",157      r"Root & \multicolumn{3}{c}{In sample, 2016--2026, six roots} & \multicolumn{3}{c}{Quarterly out-of-sample weights: mean (SD)} & Quarters \\ \cmidrule(lr){2-4}\cmidrule(lr){5-7} & Level & Slope & Curvature & Level & Slope & Curvature & included \\",158      "\n".join(rows),159      f"Appendix table. Weights are OLS coefficients of the daily Nelson--Siegel factor change (percentage points) on the yield-day log return of each futures root (percent), so entries are in percentage points per percent. Left panel: one regression on all {ins['level']['n']:,} yield days from 2016 with the six roots, with the constant (four decimals) and the $R^2$ in the last rows; no standard errors are reported. "160      f"Right panel: mean and, in parentheses, standard deviation across quarters of the {len(W)} rolling three-year estimates ({W[0]['quarter']}--{W[-1]['quarter']}), over the quarters in which the root met the coverage rule of Table~\\ref{{tab:fidelity}} (``Quarters included''). "161      f"Root sets used out of sample: {rs_txt}; five roots from {_q5}, six roots from {_q6}.",162      "lrrrrrrr")163mac("nQuarters", str(len(W)), "mimicking_weights_quarterly.json"); mac("lastQuarter", W[-1]["quarter"], ""); mac("firstQuarterFiveRoots", _q5, "first quarter with 5 roots"); mac("firstQuarterSixRoots", _q6, "first quarter with 6 roots")164for rt in ROOTS: mac(f"quartersIncluded{rt}", str(inc[rt]), "roots per quarter")165166# ---------------- 5. spanning ----------------167sp = ts["leave_one_out_spanning_2019_2026"]168rows = []169for rt in ROOTS:170    rows.append(f"{rt} & " + " & ".join(r(sp[k][h][rt], 2) for k in ("1m", "5m", "30m") for h in ("08-16ET", "all_hours")) + r" \\")171rows.append(r"\midrule Share of first three PCs & " + " & ".join(r(sp[k][h]["pc_share_3"], 3) for k in ("1m", "5m", "30m") for h in ("08-16ET", "all_hours")) + r" \\")172rows.append(r"Observations & " + " & ".join(f"{sp[k][h]['n_obs']:,}" for k in ("1m", "5m", "30m") for h in ("08-16ET", "all_hours")) + r" \\")173table("tab_spanning.tex", "tab:spanning", "Intraday spanning: in-sample $R^2$ of each held-out futures root on the first three principal components of the other five roots, 1-, 5- and 30-minute returns, 2019--2026",174      r"Held-out root & \multicolumn{2}{c}{1 minute} & \multicolumn{2}{c}{5 minutes} & \multicolumn{2}{c}{30 minutes} \\ \cmidrule(lr){2-3}\cmidrule(lr){4-5}\cmidrule(lr){6-7} & 08--16 ET & All hours & 08--16 ET & All hours & 08--16 ET & All hours \\",175      "\n".join(rows),176      "Appendix table. For each root, the first three principal components of the other five roots' returns (sampled at 1, 5 or 30 minutes, 2019--2026, all six contracts trading) are used to predict the held-out root's return in sample; the table reports the $R^2$. ``08--16 ET'' (``US hours'' in the text) restricts to intervals starting between 08:00 and 15:59 Eastern time; ``All hours'' includes the overnight session. The last rows give the share of total variance explained by the first three principal components of all six roots (three decimals because the values are close to one) and the number of return intervals. No standard errors are reported.",177      "lrrrrrr")178for k, tag in (("1m", "One"), ("5m", "Five"), ("30m", "Thirty")):179    mac(f"pcShare{tag}", r(sp[k]["08-16ET"]["pc_share_3"], 3), f"leave_one_out_spanning {k} 08-16ET pc_share_3")180    mac(f"looZT{tag}", r(sp[k]["08-16ET"]["ZT"], 2), f"{k} 08-16ET ZT"); mac(f"looZN{tag}", r(sp[k]["08-16ET"]["ZN"], 2), f"{k} 08-16ET ZN")181    mac(f"looMin{tag}", r(min(sp[k]["08-16ET"][rt] for rt in ROOTS), 2), f"{k} 08-16ET min"); mac(f"looMax{tag}", r(max(sp[k]["08-16ET"][rt] for rt in ROOTS), 2), f"{k} 08-16ET max")182183# ---------------- 6. events ----------------184es = J("event_summary.json")185EN = {"FOMC": "FOMC", "CPI": "CPI", "EMP": "Employment"}; EP = [("all", "All"), ("2011_2015", "2011--2015"), ("2016_2019", "2016--2019"), ("2020_2021", "2020--2021"), ("2022_2026", "2022--2026")]186PNe = {"all": "All", "2011_2015": "Zlb", "2016_2019": "Mid", "2020_2021": "Pandemic", "2022_2026": "Recent"}187rowsA, rowsB = [], []188for typ in ("FOMC", "CPI", "EMP"):189    first = True190    for per, lab in EP:191        k = f"{typ}_{per}"192        if k not in es: continue193        s = es[k]194        name = EN[typ] if first else ""; first = False195        rowsA.append(f"{name} & {lab} & {s['n_events']} & {r(s['sd_dL_bp'],1)} & {r(s['sd_dS_bp'],1)} & {r(s['sd_dC_bp'],1)} & {r(s['sd_dSR_bp'],2)} & {r(s['sd_dy2_bp'],1)} & {r(s['sd_dy10_bp'],1)} & {r(s['ratio_sd_vs_placebo_dSR'][0],1)} {ci(s['ratio_sd_vs_placebo_dSR'][1],1)} & {r(s['ratio_sd_vs_placebo_dy10'][0],1)} {ci(s['ratio_sd_vs_placebo_dy10'][1],1)} \\\\")196        rowsB.append(f"{name} & {lab} & {s['n_events']} & " + " & ".join(f"{r(100*s[f'rvshare_{c}'][0],1)} & [{r(100*s[f'rvshare_{c}'][1][0],1)}, {r(100*s[f'rvshare_{c}'][1][1],1)}] & {r(100*s[f'rvshare_{c}_median'],1)}" for c in ("dL", "dS", "dC")) + r" \\")197        for c, cn in (("dL", "Level"), ("dS", "Slope"), ("dC", "Curv"), ("dSR", "ShortRate"), ("dy2", "TwoY"), ("dy10", "TenY")):198            mac(f"ev{typ.title()}Sd{cn}{PNe[per]}", r(s[f"sd_{c}_bp"], 1 if c != "dSR" else 2), f"event_summary.json {k}.sd_{c}_bp")199            if f"ratio_sd_vs_placebo_{c}" in s: mac(f"ev{typ.title()}Ratio{cn}{PNe[per]}", r(s[f"ratio_sd_vs_placebo_{c}"][0], 1), f"{k}.ratio_sd_vs_placebo_{c}")200        for c, cn in (("dL", "Level"), ("dS", "Slope"), ("dC", "Curv")):201            mac(f"ev{typ.title()}RvShare{cn}{PNe[per]}", pct(s[f"rvshare_{c}"][0]), f"{k}.rvshare_{c}[0]"); mac(f"ev{typ.title()}RvShareLo{cn}{PNe[per]}", r(100 * s[f"rvshare_{c}"][1][0], 1), f"{k}.rvshare_{c} CI lo"); mac(f"ev{typ.title()}RvShareHi{cn}{PNe[per]}", r(100 * s[f"rvshare_{c}"][1][1], 1), f"{k}.rvshare_{c} CI hi")202            mac(f"ev{typ.title()}RvShareMedian{cn}{PNe[per]}", pct(s[f"rvshare_{c}_median"]), f"{k}.rvshare_{c}_median")203        mac(f"ev{typ.title()}N{PNe[per]}", str(s["n_events"]), f"{k}.n_events"); mac(f"ev{typ.title()}NPlacebo{PNe[per]}", str(s["n_placebo"]), f"{k}.n_placebo")204        for t, tn in (("3m", "ThreeM"), ("2", "TwoY"), ("5", "FiveY"), ("10", "TenY"), ("30", "ThirtyY")): mac(f"ev{typ.title()}VarDy{tn}{PNe[per]}", r(s["var_dy_bp2"][t], 1), f"{k}.var_dy_bp2.{t}")205        for c, cn in (("dL", "Level"), ("dS", "Slope"), ("dSR", "ShortRate"), ("dy10", "TenY")):206            mac(f"ev{typ.title()}PreDrift{cn}{PNe[per]}", r(s[f"pre24h_mean_{c}_bp"][0], 2), f"{k}.pre24h_mean_{c}_bp"); mac(f"ev{typ.title()}PreDriftLo{cn}{PNe[per]}", r(s[f"pre24h_mean_{c}_bp"][1][0], 2), "CI lo"); mac(f"ev{typ.title()}PreDriftHi{cn}{PNe[per]}", r(s[f"pre24h_mean_{c}_bp"][1][1], 2), "CI hi")207wm = es["window_minutes"]208table("tab_events_a.tex", "tab:eventsa", f"Announcement windows $[{wm[0]},+{wm[1]}]$ minutes: standard deviation of factor and yield changes and ratio to placebo windows, scheduled releases, 2011--2026 (H2)",209      r"Release & Period & $n$ & \multicolumn{6}{c}{SD of window change (bp)} & \multicolumn{2}{c}{SD ratio to placebo [95\% CI]} \\ \cmidrule(lr){4-9}\cmidrule(lr){10-11} & & & Level & Slope & Curv. & Short rate & 2-year & 10-year & Short rate & 10-year \\",210      "\n".join(rowsA),211      f"Window change is the sum of one-minute factor changes (basis points) from {-wm[0]} minutes before to {wm[1]} minutes after the scheduled release time (FOMC: 14:15 ET before April 2011, 12:30 ET on press-conference days in 2011--2012, 14:00 ET from 2013; CPI and Employment Situation: 08:30 ET); $n$ is the number of scheduled events with complete minute coverage of the window. Short rate is the implied change in $y(0)=L+S$ (two decimals because it is sub-basis-point in 2011--2015); 2-year and 10-year are the implied yield changes $\\Delta L+\\Delta S\\,\\ell_2(\\tau)+\\Delta C\\,\\ell_3(\\tau)$. "212      "SD ratio to placebo is the standard deviation of the window change on event days divided by that on placebo windows taken at the same clock time on three non-event days per event (same weekday, within 200 days); brackets give the 95\\% interval of a 2,000-draw bootstrap that resamples events and placebo windows with replacement (events treated as independent). Only scheduled FOMC statements are included. Curvature is scaled by the Nelson--Siegel loading and is therefore several times more volatile than level and slope in factor units.",213      "llrrrrrrrrr")214_allshares = [100 * es[f"{t}_all"][f"rvshare_{c}"][0] for t in ("FOMC", "CPI", "EMP") for c in ("dL", "dS", "dC")]215_bench = 100 * es["benchmark_share_30min"]216table("tab_events_b.tex", "tab:eventsb", f"Share of the yield day's realized factor variance falling in the $[{wm[0]},+{wm[1]}]$-minute announcement window, scheduled releases, 2011--2026 (H2)",217      r"Release & Period & $n$ & \multicolumn{3}{c}{Level} & \multicolumn{3}{c}{Slope} & \multicolumn{3}{c}{Curvature} \\ \cmidrule(lr){4-6}\cmidrule(lr){7-9}\cmidrule(lr){10-12} & & & Mean & [95\% CI] & Median & Mean & [95\% CI] & Median & Mean & [95\% CI] & Median \\",218      "\n".join(rowsB),219      f"Share is the sum of squared one-minute factor changes in the window divided by the sum over the whole yield day (15:30 ET to 15:30 ET) for the same factor, in percent. Cells give the mean across events, the 95\\% interval of a 2,000-draw bootstrap over events (brackets, in percent) and the median across events; $n$ is the number of scheduled events with complete minute coverage. "220      f"Under a uniform distribution of variance a 30-minute window out of a 1,380-minute session holds {r(_bench,1)}\\% of the day; the all-sample shares are between {r(min(_allshares)/_bench,0)} and {r(max(_allshares)/_bench,0)} times that benchmark.",221      "llr" + "rcr@{\\hspace{5pt}}" * 2 + "rcr", colsep="2pt")222mac("rvShareBenchRatioMin", r(min(_allshares) / _bench, 0), "min all-sample rvshare / benchmark"); mac("rvShareBenchRatioMax", r(max(_allshares) / _bench, 0), "max all-sample rvshare / benchmark")223mac("benchmarkShare", r(100 * es["benchmark_share_30min"], 1), "event_summary.json benchmark_share_30min x100"); mac("windowPre", str(-wm[0]), "window_minutes"); mac("windowPost", str(wm[1]), "window_minutes")224225# ---------------- 7. correlations ----------------226rc = J("realized_correlations.json"); nb = J("noise_benchmark.json")227rows = []228for per, lab in PER2:229    cells = []230    for k in ("1m", "5m", "30m"): cells.append(" & ".join(r(rc[per][k][p], 2) for p in ("LS", "LC", "SC")))231    cells.append(" & ".join(r(rc["daily_fred"][per][p], 2) for p in ("LS", "LC", "SC")))232    rows.append(f"{lab} & " + " & ".join(cells) + r" \\")233    for k, tag in (("1m", "One"), ("5m", "Five"), ("30m", "Thirty")):234        for p in ("LS", "LC", "SC"): mac(f"rc{p}{tag}{PN[[q for q,_ in PER][[q for q,_ in PER2].index(per)]]}", r(rc[per][k][p], 2), f"realized_correlations.json {per}.{k}.{p}")235    for p in ("LS", "LC", "SC"): mac(f"rc{p}Daily{PN[[q for q,_ in PER][[q for q,_ in PER2].index(per)]]}", r(rc["daily_fred"][per][p], 2), f"daily_fred.{per}.{p}")236rows.append(r"\midrule")237rows.append(r"Tick-noise benchmark & " + " & ".join(r(nb[f"mean_noise_corr_{p}"], 2) for p in ("LS", "LC", "SC")) + " & " + " & ".join(["--"] * 9) + r" \\")238rows.append(r"\midrule \multicolumn{13}{l}{\textit{Mean daily realized variance of the factor changes (bp$^2$ per day)}} \\")239rows.append(r" & " + " & ".join(["L & S & C"] * 4) + r" \\")240rows.append(r"2011--2026 & " + " & ".join(r(rc["rv_mean_bp2"][k][c], 0) for k in ("1m", "5m", "30m") for c in ("L", "S", "C")) + " & " + " & ".join(r(float(ds['dfactor_sd_bp'][f]) ** 2, 0) for f in FAC) + r" \\")241table("tab_correlations.tex", "tab:correlations", "Realized correlations between level (L), slope (S) and curvature (C) factor changes by sampling interval and sub-period, 2011--2026 (H4)",242      r"Period & \multicolumn{3}{c}{1 minute} & \multicolumn{3}{c}{5 minutes} & \multicolumn{3}{c}{30 minutes} & \multicolumn{3}{c}{Daily (CMT)} \\ \cmidrule(lr){2-4}\cmidrule(lr){5-7}\cmidrule(lr){8-10}\cmidrule(lr){11-13} & L,S & L,C & S,C & L,S & L,C & S,C & L,S & L,C & S,C & L,S & L,C & S,C \\",243      "\n".join(rows),244      "Entries are correlations of intraday factor changes aggregated to 1-, 5- and 30-minute intervals, computed as the ratio of the summed realized covariance to the square root of the product of the summed realized variances over all yield days of the period; L,S, L,C and S,C denote the level--slope, level--curvature and slope--curvature pairs. ``Daily (CMT)'' is the correlation of the daily CMT-based factor changes over the same period (Table~\\ref{tab:dailyfactors} reports the 2008--2026 values). "245      f"``Tick-noise benchmark'' is the correlation that independent price-rounding noise (one tick per contract, variance tick$^2/6$ for a difference) would induce through the mimicking weights, averaged over quarters; the associated noise standard deviation per minute is {r(nb['mean_noise_sd_bp_per_min']['level'],2)}, {r(nb['mean_noise_sd_bp_per_min']['slope'],2)} and {r(nb['mean_noise_sd_bp_per_min']['curvature'],2)} bp for level, slope and curvature. "246      "The last block reports, with its own L/S/C column labels, the mean daily realized variance of each factor by sampling frequency (2011--2026) and, in the daily columns, the variance of the daily CMT factor changes (2008--2026). Entries are sample moments without standard errors.",247      "l" + "rrr@{\\hspace{4pt}}" * 3 + "rrr", colsep="2pt")248for p in ("LS", "LC", "SC"): mac(f"noiseCorr{p}", r(nb[f"mean_noise_corr_{p}"], 2), f"noise_benchmark.json mean_noise_corr_{p}")249for f in FAC: mac(f"noiseSd{MN[f]}", r(nb["mean_noise_sd_bp_per_min"][f], 2), "mean_noise_sd_bp_per_min")250for k, tag in (("1m", "One"), ("5m", "Five"), ("30m", "Thirty")):251    for c, cn in (("L", "Level"), ("S", "Slope"), ("C", "Curv")): mac(f"rvMean{cn}{tag}", r(rc["rv_mean_bp2"][k][c], 0), f"rv_mean_bp2.{k}.{c}")252mac("rvSignatureLevel", r(rc["rv_mean_bp2"]["1m"]["L"] / rc["rv_mean_bp2"]["30m"]["L"], 2), "rv 1m / rv 30m level"); mac("rvSignatureCurv", r(rc["rv_mean_bp2"]["1m"]["C"] / rc["rv_mean_bp2"]["30m"]["C"], 2), "rv 1m / rv 30m curvature")253254# ---------------- 8. HAR ----------------255from math import erf, sqrt256def pnorm2(z): return 2 * (1 - 0.5 * (1 + erf(abs(z) / sqrt(2))))257har = J("har_forecasts.json")258tests = [(f, h, har[f][f"h{h}"]["dm_qlike_har_vs_garch"]) for f in FAC for h in (1, 5, 22)]259pv = sorted([(pnorm2(z), i) for i, (_, _, z) in enumerate(tests)]); m = len(pv); holm = {}260running = 0261for rank, (p, i) in enumerate(pv):262    adj = min(1.0, max(running, (m - rank) * p)); running = adj; holm[i] = adj263def stars(p): return "$^{***}$" if p < 0.01 else "$^{**}$" if p < 0.05 else "$^{*}$" if p < 0.10 else ""264rows = [r"\multicolumn{14}{l}{\textit{Panel A: Out-of-sample forecast accuracy by horizon $h$ (yield days)}} \\"]265_minabs_roll = min(abs(har[f][f"h{h}"]["dm_qlike_har_vs_roll22"]) for f in FAC for h in (1, 5, 22))266_n_holm05 = sum(1 for i in holm if holm[i] < 0.05); _n_holm10 = sum(1 for i in holm if holm[i] < 0.10)267for f in FAC:268    first = True269    for h in (1, 5, 22):270        L = har[f][f"h{h}"]; i = [t[:2] for t in tests].index((f, h))271        name = FL[f] if first else ""; first = False272        rows.append(f"{name} & {h} & {r(L['har']['qlike'],3)} & {r(L['garch']['qlike'],3)} & {r(L['roll22']['qlike'],3)} & {r(L['garch']['mse']/L['har']['mse'],2)} & {r(L['roll22']['mse']/L['har']['mse'],2)} & {r(L['har']['r2_log'],2)} & {r(L['garch']['r2_log'],2)} & {r(L['roll22']['r2_log'],2)} & {r(L['dm_qlike_har_vs_garch'],2)} & {stars(holm[i])} & {r(L['dm_qlike_har_vs_roll22'],2)} & {L['n']:,} \\\\")273        mac(f"harQlike{MN[f]}H{ {1:'One',5:'Five',22:'TwentyTwo'}[h]}", r(L['har']['qlike'], 3), f"har_forecasts.json {f}.h{h}.har.qlike"); mac(f"garchQlike{MN[f]}H{ {1:'One',5:'Five',22:'TwentyTwo'}[h]}", r(L['garch']['qlike'], 3), f"{f}.h{h}.garch.qlike")274        mac(f"harDm{MN[f]}H{ {1:'One',5:'Five',22:'TwentyTwo'}[h]}", r(L['dm_qlike_har_vs_garch'], 2), f"{f}.h{h}.dm_qlike_har_vs_garch"); mac(f"harDmRoll{MN[f]}H{ {1:'One',5:'Five',22:'TwentyTwo'}[h]}", r(L['dm_qlike_har_vs_roll22'], 2), f"{f}.h{h}.dm_qlike_har_vs_roll22")275        mac(f"harHolmP{MN[f]}H{ {1:'One',5:'Five',22:'TwentyTwo'}[h]}", r(holm[i], 4), "Holm-adjusted p of DM har vs garch")276        mac(f"harMseGain{MN[f]}H{ {1:'One',5:'Five',22:'TwentyTwo'}[h]}", r(100 * (1 - L['har']['mse'] / L['garch']['mse']), 1), "1 - mse_har/mse_garch, percent")277        mac(f"harRtwo{MN[f]}H{ {1:'One',5:'Five',22:'TwentyTwo'}[h]}", r(L['har']['r2_log'], 2), f"{f}.h{h}.har.r2_log"); mac(f"garchRtwo{MN[f]}H{ {1:'One',5:'Five',22:'TwentyTwo'}[h]}", r(L['garch']['r2_log'], 2), f"{f}.h{h}.garch.r2_log")278rows.append(r"\midrule \multicolumn{14}{l}{\textit{Panel B: HAR coefficients, one-day horizon, full-sample in-sample OLS (log realized variance, 5-minute sampling)}} \\")279rows.append(r"Factor & & Constant & $\beta_{\mathrm{day}}$ & $\beta_{\mathrm{week}}$ & $\beta_{\mathrm{month}}$ & $R^2$ & & & & & & & \\")280for f in FAC:281    c = har[f]["har_coef_h1_full"]; rows.append(f"{FL[f]} & & {r(c['c'],3)} & {r(c['d'],3)} & {r(c['w'],3)} & {r(c['m'],3)} & {r(har[f]['har_r2_h1_full'],2)} & & & & & & & \\\\")282    mac(f"harCoefD{MN[f]}", r(c['d'], 2), "har_coef_h1_full.d"); mac(f"harCoefW{MN[f]}", r(c['w'], 2), "har_coef_h1_full.w"); mac(f"harCoefM{MN[f]}", r(c['m'], 2), "har_coef_h1_full.m"); mac(f"harRtwoFull{MN[f]}", r(har[f]['har_r2_h1_full'], 2), "har_r2_h1_full")283per0 = har["level"]["h1"]["period"]; mac("harOosStart", per0[0], "period"); mac("harOosEnd", per0[1], "period"); mac("harN", f"{har['level']['h1']['n']:,}", "n")284table("tab_har.tex", "tab:har", f"Forecasting realized factor variance: HAR versus GARCH(1,1) and rolling-variance benchmarks, out of sample {per0[0][:4]}--{per0[1][:4]} (H5)",285      r"Factor & $h$ & \multicolumn{3}{c}{QLIKE} & \multicolumn{2}{c}{MSE ratio to HAR} & \multicolumn{3}{c}{$R^2$ (logs)} & \multicolumn{3}{c}{DM, QLIKE} & $n$ \\ \cmidrule(lr){3-5}\cmidrule(lr){6-7}\cmidrule(lr){8-10}\cmidrule(lr){11-13} & & HAR & GARCH & Roll & GARCH & Roll & HAR & GARCH & Roll & \multicolumn{2}{l}{vs GARCH} & vs Roll & \\",286      "\n".join(rows),287      f"Target: realized variance of the factor (5-minute sampling, bp$^2$) cumulated over the next $h$ yield days. HAR: regression of the log target on the daily, weekly (5-day) and monthly (22-day) averages of log realized variance, with a log-normal correction; GARCH: GARCH(1,1) with constant mean on daily CMT factor changes, $h$-step cumulative variance forecast; Roll: 22-day rolling variance of daily factor changes times $h$. All models are re-estimated monthly on an expanding window; evaluation {per0[0]} to {per0[1]}. "288      "MSE ratio is the benchmark's mean squared error (in bp$^4$) divided by that of HAR. QLIKE $=\\log f + y/f$; $R^2$ (logs) is one minus the ratio of squared log forecast errors to the variance of the log target. DM is the Diebold--Mariano statistic for the QLIKE loss differential (HAR minus benchmark; negative favours HAR) with a Bartlett HAC variance ($1.3\\,T^{1/2}$ lags, at least $h-1$). "289      f"Stars (own column) on the HAR-vs-GARCH statistics mark Holm-adjusted two-sided $p$-values across the nine tests of the family: $^{{*}}$ $p<0.10$, $^{{**}}$ $p<0.05$, $^{{***}}$ $p<0.01$; {_n_holm05} of the nine are below 0.05 and {_n_holm10} below 0.10. Statistics versus the rolling variance are not starred; all nine are below $-{r(_minabs_roll,2)}$. $n$ is the number of out-of-sample forecast days. "290      "Panel B reports in-sample OLS coefficients on the daily, weekly (5-day) and monthly (22-day) averages of log realized variance with the full-sample $R^2$ (which differs from the out-of-sample $R^2$ (logs) of Panel A); no standard errors are shown.",291      "lr" + "r" * 9 + "@{}l" + "rr")292mac("harHolmBelowFive", str(_n_holm05), "number of Holm-adjusted p < 0.05 among the 9 DM har vs garch tests"); mac("harHolmBelowTen", str(_n_holm10), "number < 0.10"); mac("harDmRollMinAbs", r(_minabs_roll, 2), "min |DM| har vs roll")293294# ---------------- 9. ETF ----------------295et = J("etf_crossval.json")296rows = [r"\multicolumn{7}{l}{\textit{By sampling interval, 2011--2026}} \\"]297for k, lab in (("1m", "1 minute"), ("5m", "5 minutes"), ("30m", "30 minutes")):298    rows.append(f"{lab} & " + " & ".join(f"{r(et[f'corr_{k}'][c],2)} & {r(et[f'beta_fut_on_etf_{k}'][c],2)}" for c in ("dL", "dS", "dC")) + r" \\")299    for c, cn in (("dL", "Level"), ("dS", "Slope"), ("dC", "Curv")): mac(f"etfCorr{cn}{ {'1m':'One','5m':'Five','30m':'Thirty'}[k]}", r(et[f'corr_{k}'][c], 2), f"etf_crossval.json corr_{k}.{c}"); mac(f"etfBeta{cn}{ {'1m':'One','5m':'Five','30m':'Thirty'}[k]}", r(et[f'beta_fut_on_etf_{k}'][c], 2), f"beta_fut_on_etf_{k}.{c}")300rows.append(r"\midrule \multicolumn{7}{l}{\textit{By period, 5-minute sampling (correlation only)}} \\")301for per, lab in [("2011_2015", "2011--2015"), ("2016_2019", "2016--2019"), ("2020_2021", "2020--2021"), ("2022_2026", "2022--2026")]:302    rows.append(f"{lab} & " + " & ".join(f"{r(et['corr_5m_by_period'][per][c],2)} & " for c in ("dL", "dS", "dC")) + r" \\")303    for c, cn in (("dL", "Level"), ("dS", "Slope"), ("dC", "Curv")): mac(f"etfCorrFive{cn}{PNe[per]}", r(et['corr_5m_by_period'][per][c], 2), f"corr_5m_by_period.{per}.{c}")304rows.append(r"\midrule \multicolumn{7}{l}{\textit{Other samples}} \\")305rows.append(r"FOMC windows $[-5,+25]$ min, $n=" + str(et["fomc_windows_n"]) + r"$ & " + " & ".join(f"{r(et['fomc_window_corr_etf_vs_fut'][c],2)} & {r(et['fomc_window_beta_fut_on_etf'][c],2)}" for c in ("dL", "dS", "dC")) + r" \\")306rows.append(r"Daily, regular hours & " + " & ".join(f"{r(et['daily_regular_hours_corr'][c],2)} & " for c in ("dL", "dS", "dC")) + r" \\")307for c, cn in (("dL", "Level"), ("dS", "Slope"), ("dC", "Curv")): mac(f"etfFomcCorr{cn}", r(et['fomc_window_corr_etf_vs_fut'][c], 2), "fomc_window_corr_etf_vs_fut"); mac(f"etfDailyCorr{cn}", r(et['daily_regular_hours_corr'][c], 2), "daily_regular_hours_corr")308mac("etfFomcN", str(et["fomc_windows_n"]), "fomc_windows_n"); mac("etfMinutes", f"{et['n_minutes_overlap']:,}", "n_minutes_overlap"); mac("etfFirst", et["first"][:10], "first")309table("tab_etf.tex", "tab:etf", "Cross-check with Treasury ETFs: correlation and regression slope between futures-based and ETF-based intraday factor changes, 2011--2026",310      r"Sampling / sample & \multicolumn{2}{c}{Level} & \multicolumn{2}{c}{Slope} & \multicolumn{2}{c}{Curvature} \\ \cmidrule(lr){2-3}\cmidrule(lr){4-5}\cmidrule(lr){6-7} & Corr. & $b$ & Corr. & $b$ & Corr. & $b$ \\",311      "\n".join(rows),312      f"Appendix table. ETF-based factors are built without any regression: each one-minute return of SHY, IEI, IEF, TLH and TLT (09:30--16:00 ET, {et['first'][:10]} to {et['last'][:10]}, {et['n_minutes_overlap']:,} common minutes) is converted to an implied yield change at the fund's maturity point using fixed approximate modified durations (1.9, 4.4, 7.6, 11.0, 17.0 years; maturity points 1.9, 4.5, 8.5, 15, 25 years) and the three Nelson--Siegel factor changes are the minute-by-minute OLS fit of the five implied yield changes on the loadings. "313      "Corr.\\ is the correlation between the ETF-based and the futures-based factor change over intervals starting between 09:35 and 15:54 ET (the last interval ends at 15:55); $b$ is the OLS slope of the futures-based on the ETF-based change (not reported where blank). "314      f"The FOMC-window row sums the changes from 5 minutes before to 25 minutes after the $n={et['fomc_windows_n']}$ scheduled statements with ETF coverage (the slope of the plot in the ETF figure); ``Daily, regular hours'' cumulates both series over the common minutes of each day (09:30--16:00 ET). No standard errors are reported.",315      "lrrrrrr")316for f in FAC:317    mac(f"fomcRvShare{MN[f]}", NUM[f"evFomcRvShare{MN[f]}All"][0], "alias of evFomcRvShare<F>All"); mac(f"cpiRvShare{MN[f]}", NUM[f"evCpiRvShare{MN[f]}All"][0], "alias"); mac(f"empRvShare{MN[f]}", NUM[f"evEmpRvShare{MN[f]}All"][0], "alias")318    for hh, hn in (("One", "One"), ("Five", "Five"), ("TwentyTwo", "TwentyTwo")): mac(f"harDm{MN[f]}{hn}", NUM[f"harDm{MN[f]}H{hh}"][0], f"alias of harDm{MN[f]}H{hh}")319for f in FAC:320    for tl, tk in (("fomc", "FOMC"), ("cpi", "CPI"), ("emp", "EMP")):321        mac(f"{tl}RvShare{MN[f]}Pct", pct(es[f"{tk}_all"][f"rvshare_{ {'level':'dL','slope':'dS','curvature':'dC'}[f]}"][0]), f"event_summary.json {tk}_all rvshare in percent, half-up (same rounding as Table eventsb)")322rv = J("references_verification.json"); mac("nReferences", str(rv["n_verified"]), "references_verification.json n_verified")323324# ---------------- numbers.tex + index ----------------325lines = ["% Auto-generated by scripts/11_tables.py from results/metrics — do not edit by hand.", f"% {len(NUM)} macros."]326for k, (v, _) in NUM.items(): lines.append(f"\\newcommand{{\\{k}}}{{{v}}}")327(ROOT / "paper/numbers.tex").write_text("\n".join(lines) + "\n")328idx = ["# WP47 numeric macros (paper/numbers.tex)", "", f"Generated by `scripts/11_tables.py` from `results/metrics/`; {len(NUM)} macros. Period suffixes: Full = 2011–2026, Zlb = 2011–2015, Mid = 2016–2019, Pandemic = 2020–2021, Recent = 2022–2026, All = all events. Sampling suffixes: One/Five/Thirty minutes; horizons H One/Five/TwentyTwo days.", "", "| Macro | Value | Source |", "|---|---|---|"]329for k, (v, s) in NUM.items(): idx.append(f"| `\\{k}` | {v} | {s} |")330(ROOT / "docs/NUMBERS_INDEX.md").write_text("\n".join(idx) + "\n")331print(f"{len(NUM)} macros, tables:", sorted(p.name for p in T.glob("*.tex")))332
Files main
Menu

Theme