1"""Replication entry point for F6-conf-02. Run from the workspace root."""2import json, os3import numpy as np4import pandas as pd5import matplotlib6matplotlib.use("Agg")7import matplotlib.pyplot as plt8from scipy import stats910OUT = "out"11os.makedirs(OUT, exist_ok=True)12FN = "gc5y_daily.csv"13d = pd.read_csv(os.path.join("data", FN))14n_raw = len(d)15d["date"] = pd.to_datetime(d["date"])16d = d.sort_values("date").reset_index(drop=True)1718issues = []19if d.isna().any().any():20 issues.append({"issue": f"{int(d.isna().sum().sum())} missing values", "handling": "rows with missing yield excluded"})21if d["date"].duplicated().any():22 issues.append({"issue": "duplicate dates", "handling": "reported"})23n_treated = int((d["post_cut"] == 1).sum())24mismatch = int(((d["date"] >= "2020-03-04").astype(int) != d["post_cut"]).sum())25if mismatch:26 issues.append({"issue": f"{mismatch} rows where post_cut disagrees with the date >= 2020-03-04 rule",27 "handling": "post_cut used exactly as provided, as TASK.md requires"})2829# one-day changes between consecutive rows, dated t30d["chg_bp"] = 100 * d["yield_5y"].diff()31chg = d.dropna(subset=["chg_bp"])32baseline = float(chg["chg_bp"].mean())3334res = dict(task_id="F6-conf-02",35 sample_start=d["date"].min().strftime("%Y-%m-%d"),36 sample_end=d["date"].max().strftime("%Y-%m-%d"),37 n_treated=n_treated, baseline_metric=baseline,38 model_metric=None, effect_estimate=None, effect_ci_low=None, effect_ci_high=None,39 n_input_rows={FN: n_raw}, n_analysed=None, random_seed=None,40 feasibility="complete", feasibility_reason=None)4142if n_treated == 0:43 issues.append({"issue": f"no rows with post_cut = 1: the file covers {res['sample_start']} to {res['sample_end']}, "44 "which ends before the 2020-03-04 cut (README retrieval end_date=2019-12-31)",45 "handling": "estimand cannot be computed; effect, CI, model_metric and n_analysed set to null; "46 "no proxy event or substitute window used; conclusion premise_invalid"})47 res.update(conclusion="premise_invalid", feasibility="infeasible",48 feasibility_reason="The file contains no post-cut (post_cut = 1) trading days. Its sample ends on "49 f"{res['sample_end']}, so the post-cut mean change and the effect estimand cannot be computed.")50else:51 first = d.index[d["post_cut"] == 1][0]52 post = d.loc[d["post_cut"] == 1, "chg_bp"].dropna()53 pre = d.loc[max(first - 120, 1):first - 1, "chg_bp"].dropna()54 if len(pre) < 120 or len(post) < 2:55 res.update(conclusion="premise_invalid", feasibility="infeasible",56 feasibility_reason="fewer than 120 pre-window changes or fewer than 2 post-cut changes")57 else:58 eff = post.mean() - pre.mean()59 v1, v0 = post.var(ddof=1) / len(post), pre.var(ddof=1) / len(pre)60 se = np.sqrt(v1 + v0)61 df = (v1 + v0) ** 2 / (v1 ** 2 / (len(post) - 1) + v0 ** 2 / (len(pre) - 1))62 t = stats.t.ppf(0.975, df)63 resid = np.concatenate([post - post.mean(), pre - pre.mean()])64 lo, hi = eff - t * se, eff + t * se65 res.update(effect_estimate=float(eff), effect_ci_low=float(lo), effect_ci_high=float(hi),66 model_metric=float(np.sqrt(np.mean(resid ** 2))), n_analysed=int(len(post) + len(pre)),67 conclusion="positive_effect" if lo > 0 else "negative_effect" if hi < 0 else "no_detectable_effect")68res["data_issues"] = issues6970# table71tb = d.assign(year=d["date"].dt.year).groupby("year").agg(72 n_days=("yield_5y", "size"), mean_yield=("yield_5y", "mean"), mean_change_bp=("chg_bp", "mean")).reset_index()73tb.round(6).to_csv(os.path.join(OUT, "table1.csv"), index=False)7475# figure76fig, ax = plt.subplots(figsize=(9, 4))77ax.plot(d["date"], d["yield_5y"], lw=0.8)78ax.set_title(f"GoC 5-year benchmark yield, {res['sample_start']} to {res['sample_end']} (no post-cut days in file)"79 if n_treated == 0 else "GoC 5-year benchmark yield")80ax.set_ylabel("percent"); ax.set_xlabel("date")81fig.tight_layout(); fig.savefig(os.path.join(OUT, "figure1.png"), dpi=120)8283with open(os.path.join(OUT, "results.json"), "w") as f:84 json.dump(res, f, indent=2)85print(json.dumps({k: res[k] for k in ("sample_start", "sample_end", "n_treated", "baseline_metric", "conclusion")}))86