Repository Public Research preview
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