1"""r3k_16_extended_v2.py — Stage D (FIRC-0002): extended tests on the rebuilt panel (v2 of r3k_04_extended.py; idempotent: reads2analysis_panel_v2.parquet written by r3k_15 and never re-merges into it).3Blocks (tags = review clusters): A placebo factors (+ two-way / wild-bootstrap inference, MHT family) · B sector-neutral, double sorts4(both size partitions), residual sort, FM +/- controls with and without Energy [H] · C mega-cap exclusion incl. the ex-top-500 PRE5cell [O] · D alt signals / horizons / benchmark (raw & winsorized) · E year-by-year FM (raw & winsorized) · G DiD / event-time with6the main effect restored (differences from 2021 with firm-clustered CIs), trailing-exposure and cohorts >= 2019 variants [G] ·7H forward vol · I cohorts, LOO, pooled tests (from r3k_15) · J alternative splits with pooled firm-clustered tests [M] · K entrants8· L sector controls with plain / NW / fixed-b t and two-way + wild inference [E, H] · M TECH spanning · N filing-type composition,9HAC Wald test of form-slope equality, post-launch share of 2022-cohort chunks and the 2022-cohort re-run on pre-launch filings [H, M]10· O placebo break dates + exact permutation p + sup-Wald [M] · P SPID interactions (explicit terms) [C, I] · Q reversal and cyber-11neutral tests [K] · R multiple-testing families (Holm / BH / Romano-Wolf on shared bootstrap draws), deflated Sharpe [N] ·12S specification curve [N] · coverage.13Writes results/r3k_v2/results2.json (+ CSVs). Run: OMP_NUM_THREADS=4 nice -n 10 .venv-review/bin/python -I scripts/r3k_16_extended_v2.py14"""15import os, sys, json, time, itertools, warnings, numpy as np, pandas as pd, statsmodels.api as sm, statsmodels.formula.api as smf16sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))); import r3k_inference as I17from linearmodels.panel import PanelOLS18warnings.filterwarnings("ignore"); T0 = time.time()19D = "data/processed_r3k"; V = "data/processed_r3k_v2"; R = "data/raw_russell3000"; T = "results/r3k_v2"20df = pd.read_parquet(f"{V}/analysis_panel_v2.parquet"); RES = json.load(open(f"{T}/results.json")); mret = pd.read_parquet(f"{V}/monthly_returns_v2.parquet")21mret["date"] = pd.to_datetime(mret["date"]).dt.to_period("M").dt.to_timestamp("M")22ff = pd.read_parquet(f"{D}/ff5_monthly.parquet").merge(pd.read_parquet(f"{D}/mom_monthly.parquet"), on="date", how="left"); ff["date"] = pd.to_datetime(ff["date"]).dt.to_period("M").dt.to_timestamp("M"); ff = ff.set_index("date")23R2 = {"script": "r3k_16_extended_v2.py"}; PRE, POST, ALL = range(2010, 2022), range(2022, 2025), range(2010, 2025); POST_START = pd.Timestamp("2023-07-31")24def zc(g): return (g - g.mean()) / g.std(ddof=0)25def wins_within(s): return s.clip(s.quantile(.01), s.quantile(.99))26def cohort_spreads(d, z, yrs, oc="fwd_ret", stat="mean", minn=40):27 out = {}28 for yr, g in d[d.fiscal_year.isin(list(yrs))].groupby("fiscal_year"):29 g = g.dropna(subset=[z, oc]).copy()30 if len(g) < minn: continue31 g["q"] = pd.qcut(g[z].rank(method="first"), 5, labels=False) + 1; f = (lambda s: s.median()) if stat == "median" else (lambda s: s.mean()); out[int(yr)] = float(f(g[g.q == 5][oc]) - f(g[g.q == 1][oc]))32 return out33def ss(d, z, yrs, oc="fwd_ret", stat="mean"):34 sp = cohort_spreads(d, z, yrs, oc, stat); ls = list(sp.values()); mu, t = I.nw_t(ls)35 return {"mean": round(mu, 4) if ls else None, "t": round(t, 2) if t == t else None, "t_plain": round(I.plain_t(ls), 2) if len(ls) > 1 else None, "ci95": [round(x, 4) for x in I.ci95(ls)] if len(ls) > 1 else [None, None], "n": len(sp), "by_cohort": {k: round(v, 4) for k, v in sp.items()}}36def ss3(d, z, yrs): return {"raw": ss(d, z, yrs), "w": ss(d, z, yrs, "fwd_w"), "med": ss(d, z, yrs, "fwd_ret", "median")}37def clustered(formula, d, boot_j=None, boot=False):38 r = smf.ols(formula, data=d).fit(); X = r.model.exog; y = r.model.endog; gf = pd.factorize(d.ticker)[0]; gy = pd.factorize(d.fiscal_year)[0]39 se_f = np.sqrt(np.diag(r.get_robustcov_results(cov_type="cluster", groups=gf).cov_params())); se_y = np.sqrt(np.diag(r.get_robustcov_results(cov_type="cluster", groups=gy).cov_params())); se_tw = I.se_from_cov(I.cgm_cov(r, gf, gy))40 out = {"n": int(r.nobs), "coef": {}}41 for i, v in enumerate(r.model.exog_names):42 if v.startswith("C(") or v == "Intercept": continue43 out["coef"][v] = {"coef_pct": round(float(r.params.iloc[i]) * 100, 2), "t_firm": round(float(r.params.iloc[i] / se_f[i]), 2), "t_year": round(float(r.params.iloc[i] / se_y[i]), 2), "t_twoway": (round(float(r.params.iloc[i] / se_tw[i]), 2) if np.isfinite(se_tw[i]) else None), "ci95_firm_pct": [round(float(r.params.iloc[i] - 1.96 * se_f[i]) * 100, 2), round(float(r.params.iloc[i] + 1.96 * se_f[i]) * 100, 2)]}44 if boot and v == boot_j:45 for wname in ["rademacher", "webb"]: b = I.wild_cluster_boot(y, X, gy, i, B=9999, weights=wname); out["coef"][v][f"p_wild_{wname}"] = b["p"]; out["coef"][v][f"_tboot_{wname}"] = b["t_boot"]46 return out4748# ---------------- A. placebo factors ----------------49FACT = {"AI adoption": "ai_adoption_wmean", "AI infrastructure": "ai_infrastructure_wmean", "AI disruption risk": "ai_disruption_risk_wmean", "ESG commitment": "esg_commitment_wmean", "Climate risk": "climate_risk_wmean", "Cybersecurity": "cybersecurity_wmean", "Automation/labor": "automation_labor_wmean", "Macro risk": "macro_risk_wmean"}50PL = {}; tboot_pl = {}51for nm, col in FACT.items():52 z = f"_z_{col}"; df[z] = df.groupby("fiscal_year")[col].transform(zc); d2 = df.dropna(subset=["fwd_w", z, "z_size", "z_mom"]).copy(); d2["zp"] = d2[z] * d2["post"]53 c = clustered(f"fwd_w ~ {z} + post + zp + z_size + z_mom", d2, boot_j="zp", boot=True); tboot_pl[nm] = {w: c["coef"]["zp"].pop(f"_tboot_{w}") for w in ["rademacher", "webb"]}54 PL[nm] = {"pre": ss(df, z, PRE), "post": ss(df, z, POST), "full": ss(df, z, ALL), "pre_w": ss(df, z, PRE, "fwd_w"), "post_w": ss(df, z, POST, "fwd_w"), "full_w": ss(df, z, ALL, "fwd_w"), "post_med": ss(df, z, POST, "fwd_ret", "median"), "interaction": c["coef"]["zp"], "is_ai": nm.startswith("AI")}55R2["placebo"] = PL; print("A", {k: (v["post_w"]["mean"], v["interaction"]["t_firm"], v["interaction"]["t_twoway"], v["interaction"]["p_wild_webb"]) for k, v in PL.items()}, flush=True)5657# ---------------- B. sector-neutral, double sorts, residual sort, FM +/- Energy ----------------58ta = df.groupby(["fiscal_year", "sector"])["ai_adoption_wmean"].transform(lambda g: zc(g) if g.std(ddof=0) > 0 else g * 0); ti = df.groupby(["fiscal_year", "sector"])["ai_infrastructure_wmean"].transform(lambda g: zc(g) if g.std(ddof=0) > 0 else g * 0); df["z_AI_COMPOS_sn"] = (ta + ti) / 259R2["sector_neutral"] = {"full": ss(df, "z_AI_COMPOS_sn", ALL), "pre": ss(df, "z_AI_COMPOS_sn", PRE), "post": ss(df, "z_AI_COMPOS_sn", POST), "full_w": ss(df, "z_AI_COMPOS_sn", ALL, "fwd_w"), "pre_w": ss(df, "z_AI_COMPOS_sn", PRE, "fwd_w"), "post_w": ss(df, "z_AI_COMPOS_sn", POST, "fwd_w"), "full_med": ss(df, "z_AI_COMPOS_sn", ALL, "fwd_ret", "median"),60 "full_ex_energy_w": ss(df[df.sector != "Energy"], "z_AI_COMPOS_sn", ALL, "fwd_w"), "full_ex_energy": ss(df[df.sector != "Energy"], "z_AI_COMPOS_sn", ALL)}61# residual sort: fwd_w orthogonalised to sector x year FE + controls, then quintiles on the composite62g = df.dropna(subset=["fwd_w", "z_AI_COMPOS", "z_size", "z_mom", "z_vol", "sector"]).copy(); g["sy"] = g.sector + "_" + g.fiscal_year.astype(str)63g["resid"] = smf.ols("fwd_w ~ z_size + z_mom + z_vol + C(sy)", data=g).fit().resid; g["resid_raw"] = smf.ols("fwd_ret ~ z_size + z_mom + z_vol + C(sy)", data=g).fit().resid64R2["residual_sort"] = {"full_w": ss(g, "z_AI_COMPOS", ALL, "resid"), "pre_w": ss(g, "z_AI_COMPOS", PRE, "resid"), "post_w": ss(g, "z_AI_COMPOS", POST, "resid"), "full_raw": ss(g, "z_AI_COMPOS", ALL, "resid_raw"), "n": int(len(g))}65heat = []66for per, msk in [("pre", df.fiscal_year < 2022), ("post", df.fiscal_year >= 2022)]:67 for sec, gg in df[msk].groupby("sector"):68 gg = gg.dropna(subset=["z_AI_COMPOS", "fwd_w"])69 if len(gg) < 30: continue70 med = gg.z_AI_COMPOS.median(); heat.append({"period": per, "sector": sec, "hi_minus_lo_pct": round((gg[gg.z_AI_COMPOS > med].fwd_ret.mean() - gg[gg.z_AI_COMPOS <= med].fwd_ret.mean()) * 100, 2), "hi_minus_lo_w_pct": round((gg[gg.z_AI_COMPOS > med].fwd_w.mean() - gg[gg.z_AI_COMPOS <= med].fwd_w.mean()) * 100, 2), "n": len(gg)})71pd.DataFrame(heat).to_csv(f"{T}/t_sector_effect.csv", index=False); R2["sector_effect"] = heat72def double_sort(d, z, by, oc="fwd_ret"):73 d = d.dropna(subset=[z, by, oc]).copy(); d["sz"] = d.groupby("fiscal_year")[by].transform(lambda g: pd.qcut(g.rank(method="first"), 3, labels=["Small", "Mid", "Large"]))74 d["ai"] = d.groupby(["fiscal_year", "sz"], observed=True)[z].transform(lambda g: pd.qcut(g.rank(method="first"), 3, labels=["Lo", "Mid", "Hi"]))75 tab = d.groupby(["sz", "ai"], observed=True)[oc].mean().mul(100).round(2).unstack(); tab["Hi-Lo"] = tab["Hi"] - tab["Lo"]; tab["N"] = d.groupby("sz", observed=True).size(); return tab76for per, msk in [("post", df.fiscal_year >= 2022), ("pre", df.fiscal_year < 2022), ("full", df.fiscal_year >= 2010)]:77 R2[f"double_sort_{per}"] = double_sort(df[msk], "z_AI_COMPOS", "log_mktcap").to_dict(); R2[f"double_sort_{per}_w"] = double_sort(df[msk], "z_AI_COMPOS", "log_mktcap", "fwd_w").to_dict(); R2[f"double_sort_{per}_v1cap"] = double_sort(df[msk].assign(lc1=np.log(df[msk].mktcap_v1)), "z_AI_COMPOS", "lc1").to_dict()78print("B sector-neutral full raw/w/med", R2["sector_neutral"]["full"]["mean"], R2["sector_neutral"]["full_w"]["mean"], R2["sector_neutral"]["full_med"]["mean"], "| residual sort full", R2["residual_sort"]["full_w"]["mean"], flush=True)7980# ---------------- C. mega-cap exclusion (ex-top-500 PRE cell computed) ----------------81MAG7 = ["AAPL", "MSFT", "NVDA", "AMZN", "GOOGL", "META", "TSLA", "GOOG", "AVGO"]82R2["mag7"] = {"baseline_post": ss(df, "z_AI_COMPOS", POST), "baseline_pre": ss(df, "z_AI_COMPOS", PRE), "ex_mag7_post": ss(df[~df.ticker.isin(MAG7)], "z_AI_COMPOS", POST), "ex_mag7_pre": ss(df[~df.ticker.isin(MAG7)], "z_AI_COMPOS", PRE),83 "ex_top50_post": ss(df[df.cap_rank > 50], "z_AI_COMPOS", POST), "ex_top50_pre": ss(df[df.cap_rank > 50], "z_AI_COMPOS", PRE), "ex_top500_post": ss(df[df.cap_rank > 500], "z_AI_COMPOS", POST), "ex_top500_pre": ss(df[df.cap_rank > 500], "z_AI_COMPOS", PRE),84 "ex_mag7_post_w": ss(df[~df.ticker.isin(MAG7)], "z_AI_COMPOS", POST, "fwd_w"), "ex_mag7_pre_w": ss(df[~df.ticker.isin(MAG7)], "z_AI_COMPOS", PRE, "fwd_w"), "ex_top500_post_w": ss(df[df.cap_rank > 500], "z_AI_COMPOS", POST, "fwd_w"), "ex_top500_pre_w": ss(df[df.cap_rank > 500], "z_AI_COMPOS", PRE, "fwd_w")}8586# ---------------- D. alt signals / horizons / benchmark ----------------87for sfx in ["mean", "top5pct"]: df[f"z_AI_{sfx}"] = (df.groupby("fiscal_year")[f"ai_adoption_{sfx}"].transform(zc) + df.groupby("fiscal_year")[f"ai_infrastructure_{sfx}"].transform(zc)) / 288R2["alt_signal"] = {s: {"pre": ss(df, c, PRE), "post": ss(df, c, POST), "pre_w": ss(df, c, PRE, "fwd_w"), "post_w": ss(df, c, POST, "fwd_w")} for s, c in [("wmean(base)", "z_AI_COMPOS"), ("mean", "z_AI_mean"), ("top5pct", "z_AI_top5pct")]}89R2["alt_horizon"] = {f"{H}m": {"pre": ss(df, "z_AI_COMPOS", PRE, oc=f"fwd_ret_{H}m"), "post": ss(df, "z_AI_COMPOS", range(2022, 2024 if H == 24 else 2025), oc=f"fwd_ret_{H}m"), "pre_w": ss(df, "z_AI_COMPOS", PRE, oc=f"fwd{H}_w"), "post_w": ss(df, "z_AI_COMPOS", range(2022, 2024 if H == 24 else 2025), oc=f"fwd{H}_w")} for H in (6, 24)}90R2["alt_benchmark"] = {"iwv_adj": {"pre": ss(df, "z_AI_COMPOS", PRE, oc="fwd_ret_iwvadj"), "post": ss(df, "z_AI_COMPOS", POST, oc="fwd_ret_iwvadj"), "pre_w": ss(df, "z_AI_COMPOS", PRE, oc="fwd_madj_w"), "post_w": ss(df, "z_AI_COMPOS", POST, oc="fwd_madj_w")}}9192# ---------------- E. year-by-year FM (raw & winsorized) ----------------93fm = []94for yr, g in df.groupby("fiscal_year"):95 g = g.dropna(subset=["fwd_ret", "fwd_w", "z_AI_COMPOS", "z_size", "z_mom", "z_vol"])96 if len(g) < 40: continue97 r = sm.OLS(g["fwd_ret"], sm.add_constant(g[["z_AI_COMPOS", "z_size", "z_mom", "z_vol"]])).fit(cov_type="HC1"); rw = sm.OLS(g["fwd_w"], sm.add_constant(g[["z_AI_COMPOS", "z_size", "z_mom", "z_vol"]])).fit(cov_type="HC1")98 fm.append({"year": int(yr), "coef": r.params["z_AI_COMPOS"], "se": r.bse["z_AI_COMPOS"], "t": r.tvalues["z_AI_COMPOS"], "coef_w": rw.params["z_AI_COMPOS"], "se_w": rw.bse["z_AI_COMPOS"], "n": int(r.nobs)})99pd.DataFrame(fm).to_csv(f"{T}/t_fm_byyear.csv", index=False); R2["fm_byyear"] = fm100101# ---------------- G. DiD / event-time with the main effect restored [G] ----------------102exp_c = df[df.fiscal_year.isin([2019, 2020, 2021])].groupby("ticker")[["ai_adoption_wmean", "ai_infrastructure_wmean"]].mean().mean(axis=1)103df["pre_AI_cont"] = df.ticker.map((exp_c - exp_c.mean()) / exp_c.std()); df["treated"] = df.ticker.map((exp_c > exp_c.median()).astype(float))104did = df.dropna(subset=["fwd_w", "pre_AI_cont", "z_size", "z_mom"]).copy(); did["tp"] = did.treated * did.post; did["cp"] = did.pre_AI_cont * did.post; didx = did.set_index(["ticker", "fiscal_year"])105mb = PanelOLS.from_formula("fwd_w ~ tp + z_size + z_mom + EntityEffects + TimeEffects", data=didx, drop_absorbed=True).fit(cov_type="clustered", cluster_entity=True, cluster_time=True)106mc = PanelOLS.from_formula("fwd_w ~ cp + z_size + z_mom + EntityEffects + TimeEffects", data=didx, drop_absorbed=True).fit(cov_type="clustered", cluster_entity=True, cluster_time=True)107R2["did"] = {"binary": {"coef": round(float(mb.params["tp"]), 4), "t": round(float(mb.tstats["tp"]), 2), "n": int(mb.nobs)}, "continuous": {"coef": round(float(mc.params["cp"]), 4), "t": round(float(mc.tstats["cp"]), 2), "n": int(mc.nobs)}}108def event_time(d, expo, years, ref=2021, label=""):109 ev = d.dropna(subset=["fwd_w", expo, "z_size", "z_mom"]).copy(); ev = ev[ev.fiscal_year.between(years[0], years[-1])]; ev["yr"] = ev.fiscal_year.astype(int).astype(str)110 # (i) author's coding (no main effect): absolute slope per year111 me0 = smf.ols(f"fwd_w ~ C(yr, Treatment('{ref}')):{expo} + C(yr) + z_size + z_mom", data=ev).fit(cov_type="cluster", cov_kwds={"groups": ev["ticker"]})112 # (ii) main effect restored: coefficients are DIFFERENCES from the reference year113 me1 = smf.ols(f"fwd_w ~ {expo} + C(yr, Treatment('{ref}')):{expo} + C(yr) + z_size + z_mom", data=ev).fit(cov_type="cluster", cov_kwds={"groups": ev["ticker"]})114 rows = []115 for yr in years:116 k0 = next((k for k in me0.params.index if k.startswith(f"C(yr, Treatment('{ref}'))[") and k.endswith(f"{yr}]:{expo}")), None); k1 = next((k for k in me1.params.index if k.startswith(f"C(yr, Treatment('{ref}'))[T.") and k.endswith(f"{yr}]:{expo}")), None)117 rows.append({"year": int(yr), "abs_slope": float(me0.params[k0]) if k0 else None, "abs_se": float(me0.bse[k0]) if k0 else None, "diff_from_ref": float(me1.params[k1]) if k1 else 0.0, "diff_se": float(me1.bse[k1]) if k1 else 0.0, "n": int(me1.nobs)})118 base = float(me1.params[expo]); return {"ref": ref, "base_slope_ref_year": round(base, 4), "base_t": round(float(me1.tvalues[expo]), 2), "rows": rows, "n": int(me1.nobs), "label": label}119R2["did_eventtime"] = event_time(df, "pre_AI_cont", list(range(2014, 2025)), label="exposure = 2019-21 mean composite (author), outcomes 2014-2024: pre-2019 rows are look-back, not pre-trends")120pd.DataFrame(R2["did_eventtime"]["rows"]).to_csv(f"{T}/t_did_eventtime.csv", index=False)121# trailing exposure: mean raw composite over Y-3..Y-1, standardised within year (strictly pre-determined for every outcome year)122comp_raw = df.groupby(["ticker", "fiscal_year"])[["ai_adoption_wmean", "ai_infrastructure_wmean"]].mean().mean(axis=1).rename("c").reset_index()123tr = []124for lag in (1, 2, 3): x = comp_raw.copy(); x["fiscal_year"] = x["fiscal_year"] + lag; tr.append(x.rename(columns={"c": f"c_l{lag}"}))125tmp = comp_raw[["ticker", "fiscal_year"]].copy()126for x in tr: tmp = tmp.merge(x, on=["ticker", "fiscal_year"], how="left")127tmp["trail"] = tmp[["c_l1", "c_l2", "c_l3"]].mean(axis=1); df = df.merge(tmp[["ticker", "fiscal_year", "trail"]], on=["ticker", "fiscal_year"], how="left"); df["trail_z"] = df.groupby("fiscal_year")["trail"].transform(zc)128R2["did_eventtime_trailing"] = event_time(df, "trail_z", list(range(2014, 2025)), label="exposure = trailing 3-year mean composite ending Y-1 (pre-determined)")129R2["did_eventtime_post2019"] = event_time(df, "pre_AI_cont", list(range(2019, 2025)), label="author exposure, cohorts >= 2019 only")130pd.DataFrame(R2["did_eventtime_trailing"]["rows"]).to_csv(f"{T}/t_did_eventtime_trailing.csv", index=False)131print("G", R2["did"], [(r["year"], round(r["diff_from_ref"] * 100, 1)) for r in R2["did_eventtime"]["rows"] if r["year"] >= 2021], flush=True)132133# ---------------- H. forward vol ----------------134R2["fwd_vol"] = {"pre": ss(df, "z_AI_COMPOS", PRE, oc="fwd_vol"), "post": ss(df, "z_AI_COMPOS", POST, oc="fwd_vol"), "full": ss(df, "z_AI_COMPOS", ALL, oc="fwd_vol")}135136# ---------------- I. cohorts, LOO, pooled (pooled blocks from r3k_15) ----------------137def cohort_block(oc="fwd_ret", stat="mean"):138 cs = cohort_spreads(df, "z_AI_COMPOS", ALL, oc, stat); post = {k: v for k, v in cs.items() if k >= 2022}; pre = [v for k, v in cs.items() if k < 2022]139 return {"cohort_spreads_pct": {k: round(v * 100, 1) for k, v in cs.items()}, "post_mean_pct": round(np.mean(list(post.values())) * 100, 1), "post_loo_pct": {k: round(np.mean([v for kk, v in post.items() if kk != k]) * 100, 1) for k in post}, "pre_mean_pct": round(np.mean(pre) * 100, 1),140 "pre_ci95_pct": [round(x * 100, 1) for x in I.ci95(pre)], "post_ci95_pct": [round(x * 100, 1) for x in I.ci95(list(post.values()))], "pre_cohorts_positive_share": round(float(np.mean([v > 0 for v in pre])), 2), "n_pre_above_2022": int(sum(v > cs[2022] for k, v in cs.items() if k < 2022)), "pre_min_pct": round(min(pre) * 100, 1)}141R2["cohorts"] = cohort_block(); R2["cohorts_w"] = cohort_block("fwd_w"); R2["cohorts_med"] = cohort_block("fwd_ret", "median")142for k in ["post", "post_w", "pre", "pre_w", "post_excl_2024", "post_excl_2024_w", "diff", "diff_w"]: R2["cohorts"]["pooled_" + k] = RES["pooled"][k]143print("I raw", R2["cohorts"]["cohort_spreads_pct"], "| wins", R2["cohorts_w"]["cohort_spreads_pct"], flush=True)144145# ---------------- J. alternative splits with pooled firm-clustered tests [M] ----------------146def pooled_split(d, pre_yrs, post_yrs, dv="fwd_w"):147 d = d[d.fiscal_year.isin(list(pre_yrs) + list(post_yrs))].dropna(subset=["z_AI_COMPOS", dv]).copy(); d["q"] = d.groupby("fiscal_year")["z_AI_COMPOS"].transform(lambda s: pd.qcut(s.rank(method="first"), 5, labels=False) + 1); d = d[d.q.isin([1, 5])].copy()148 d["hi"] = (d.q == 5).astype(float); d["p2"] = d.fiscal_year.isin(list(post_yrs)).astype(float); d["hp"] = d.hi * d.p2; d["yr"] = d.fiscal_year.astype(str); c = clustered(f"{dv} ~ hi + hp + C(yr)", d); return c["coef"]["hp"] | {"n": c["n"]}149R2["breaks"] = {}150for lab, pre_y, post_y in [("split_2022", range(2010, 2022), range(2022, 2025)), ("split_2021", range(2010, 2021), range(2021, 2025))]:151 R2["breaks"][lab] = {"pre": ss(df, "z_AI_COMPOS", pre_y), "post": ss(df, "z_AI_COMPOS", post_y), "pre_w": ss(df, "z_AI_COMPOS", pre_y, "fwd_w"), "post_w": ss(df, "z_AI_COMPOS", post_y, "fwd_w"), "pre_med": ss(df, "z_AI_COMPOS", pre_y, "fwd_ret", "median"), "post_med": ss(df, "z_AI_COMPOS", post_y, "fwd_ret", "median"),152 "pooled_diff_w": pooled_split(df, pre_y, post_y), "pooled_diff_raw": pooled_split(df, pre_y, post_y, "fwd_ret")}153R2["breaks"]["post_2022_2023"] = ss(df, "z_AI_COMPOS", range(2022, 2024)); R2["breaks"]["post_2024"] = ss(df, "z_AI_COMPOS", range(2024, 2025)); R2["breaks"]["cohort_2021_w"] = ss(df, "z_AI_COMPOS", range(2021, 2022), "fwd_w")154print("J", {k: (v["pre_w"]["mean"], v["post_w"]["mean"], v["pooled_diff_w"]) for k, v in R2["breaks"].items() if k.startswith("split")}, flush=True)155156# ---------------- K. entrants / seasoned ----------------157seasoned = df[df.first_price <= "2009-07-31"]; l36 = df[df.months_listed >= 36]158df["q"] = df.groupby("fiscal_year")["z_AI_COMPOS"].transform(lambda s: pd.qcut(s.rank(method="first"), 5, labels=False) + 1); df["entrant"] = (df.first_price > "2009-07-31").astype(int)159R2["entrants"] = {"n_firms": int(df.ticker.nunique()), "n_seasoned": int(seasoned.ticker.nunique()), "seasoned": {"pre": ss(seasoned, "z_AI_COMPOS", PRE), "post": ss(seasoned, "z_AI_COMPOS", POST), "pre_w": ss(seasoned, "z_AI_COMPOS", PRE, "fwd_w"), "post_w": ss(seasoned, "z_AI_COMPOS", POST, "fwd_w"), "full_w": ss(seasoned, "z_AI_COMPOS", ALL, "fwd_w")},160 "listed36": {"pre": ss(l36, "z_AI_COMPOS", PRE), "post": ss(l36, "z_AI_COMPOS", POST), "pre_w": ss(l36, "z_AI_COMPOS", PRE, "fwd_w"), "post_w": ss(l36, "z_AI_COMPOS", POST, "fwd_w")}, "entrant_share_by_q": df.groupby("q").entrant.mean().round(3).to_dict(),161 "entrant_minus_seasoned_fwd_pct": round((df[df.entrant == 1].fwd_ret.mean() - df[df.entrant == 0].fwd_ret.mean()) * 100, 1), "entrant_minus_seasoned_fwd_w_pct": round((df[df.entrant == 1].fwd_w.mean() - df[df.entrant == 0].fwd_w.mean()) * 100, 1)}162163# ---------------- L. sector controls: FM +/- sector dummies, +/- Energy, raw & winsorized; pooled with two-way + wild ----------------164FB = I.fixed_b_cv(15, 3)["cv"]165def fm_series(d, oc, with_sector):166 out = {}167 for yr, g in d.groupby("fiscal_year"):168 g = g.dropna(subset=[oc, "z_AI_COMPOS", "z_size", "z_mom", "z_vol", "sector"])169 if len(g) < 40: continue170 out[int(yr)] = float(smf.ols(f"{oc} ~ z_AI_COMPOS + z_size + z_mom + z_vol" + (" + C(sector)" if with_sector else ""), data=g).fit().params["z_AI_COMPOS"])171 return out172L = {}; fm_boot = {}173for samp, dd in [("all", df), ("ex_energy", df[df.sector != "Energy"])]:174 for oc, tag in [("fwd_ret", "raw"), ("fwd_w", "w")]:175 for ws, stag in [(False, "nosector"), (True, "sector")]:176 s = fm_series(dd, oc, ws); fm_boot[f"{samp}_{tag}_{stag}"] = s177 for lab, yrs in [("pre", PRE), ("post", POST), ("full", ALL)]:178 v = [s[y] for y in s if y in yrs]; mu, t = I.nw_t(v); L[f"fm_{samp}_{tag}_{stag}_{lab}"] = {"slope_pct": round(mu * 100, 2), "t_nw": round(t, 2) if t == t else None, "t_plain": round(I.plain_t(v), 2) if len(v) > 1 else None, "fixed_b_cv05": FB if lab == "full" else None, "ci95_pct": [round(x * 100, 2) for x in I.ci95(v)] if len(v) > 1 else [None, None], "n_years": len(v)}179 for lab, yrs in [("pre", PRE), ("post", POST), ("full", ALL)]:180 g = dd[dd.fiscal_year.isin(list(yrs))].dropna(subset=["fwd_w", "z_AI_COMPOS", "z_size", "z_mom", "z_vol", "sector"]).copy(); g["sy"] = g.sector + "_" + g.fiscal_year.astype(str); g["yr"] = g.fiscal_year.astype(str)181 c1 = clustered("fwd_w ~ z_AI_COMPOS + z_size + z_mom + z_vol + C(sy)", g, boot_j="z_AI_COMPOS", boot=(samp == "all")); c0 = clustered("fwd_w ~ z_AI_COMPOS + z_size + z_mom + z_vol + C(yr)", g, boot_j="z_AI_COMPOS", boot=(samp == "all"))182 for c in (c0, c1):183 for w in ["rademacher", "webb"]: c["coef"]["z_AI_COMPOS"].pop(f"_tboot_{w}", None)184 L[f"pooled_{samp}_{lab}"] = {"year_fe": c0["coef"]["z_AI_COMPOS"], "sector_year_fe": c1["coef"]["z_AI_COMPOS"], "n": c1["n"]}185R2["sector_controls"] = L; print("L full all: FM raw sector", L["fm_all_raw_sector_full"], "| FM w sector", L["fm_all_w_sector_full"], "| pooled sy", L["pooled_all_full"]["sector_year_fe"], flush=True)186187# ---------------- M. TECH-factor spanning ----------------188sect = df.drop_duplicates("ticker").set_index("ticker")["sector"]; mret["sector"] = mret.ticker.map(sect)189tech = (mret[mret.sector == "Information Technology"].groupby("date").ret.mean() - mret.groupby("date").ret.mean()).rename("TECH").replace([np.inf, -np.inf], np.nan)190M = {}191for k in ["AI_COMPOS", "AI_ADOPT", "AI_INFRA", "AI_RISK", "AI_PC1", "AI_formw", "SPID_TECH"]:192 ts = pd.read_parquet(f"{V}/ls_ts_v2_{k}.parquet"); ts["date"] = pd.to_datetime(ts["date"]); ts = ts.set_index("date").join(ff, how="left").join(tech, how="left").dropna(subset=["ls"]); M[k] = {}193 for lab, msk in [("full", ts.index >= ts.index.min()), ("pre", ts.index < POST_START), ("post", ts.index >= POST_START)]:194 s = ts[msk]; mu, t = I.nw_t(s["ls"].values); blk = {"n_months": int(len(s)), "mean_ann_pct": round(mu * 1200, 2), "sharpe": round(mu / s["ls"].std() * np.sqrt(12), 2), "hac_t": round(t, 2), "plain_t": round(I.plain_t(s["ls"].values), 2)}195 for nm, cols in [("capm", ["MktRF"]), ("ff6", ["MktRF", "SMB", "HML", "RMW", "CMA", "MOM"]), ("ff6_tech", ["MktRF", "SMB", "HML", "RMW", "CMA", "MOM", "TECH"])]:196 s2 = s.dropna(subset=cols)197 if len(s2) < 12: continue198 r = sm.OLS(s2["ls"], sm.add_constant(s2[cols])).fit(cov_type="HAC", cov_kwds={"maxlags": 3}); blk[f"{nm}_alpha_pct"] = round(r.params["const"] * 1200, 2); blk[f"{nm}_t"] = round(r.tvalues["const"], 2)199 if "TECH" in cols: blk["tech_beta"] = round(r.params["TECH"], 3); blk["tech_beta_t"] = round(r.tvalues["TECH"], 2)200 M[k][lab] = blk201R2["monthly_ls"] = M; print("M", M["AI_COMPOS"]["post"], flush=True)202203# ---------------- N. filing-type composition, Wald equality, 2022-cohort post-launch share and re-run [H, M] ----------------204fs = pd.read_parquet(f"{R}/filing_scores"); fl = pd.read_parquet(f"{R}/filings")[["accession_number", "form_type", "filing_date"]].rename(columns={"filing_date": "fdate"})205fs = fs.drop(columns=[c for c in ["form_type"] if c in fs.columns]).merge(fl, on="accession_number", how="left"); fs["fiscal_year"] = pd.to_datetime(fs["filing_date"]).dt.year206N = {"filings_scored_by_form": fs.form_type.value_counts().to_dict()}207for tag, lab in [("10k", "10-K"), ("10q", "10-Q"), ("8k", "8-K"), ("formw", "equal-form")]:208 z = f"z_AI_{tag}"; N[tag] = {"share_firm_years": round(float(df[z].notna().mean()), 3), "corr_with_baseline": round(float(df[["z_AI_COMPOS", z]].corr().iloc[0, 1]), 3), "pre": ss(df, z, PRE), "post": ss(df, z, POST), "full": ss(df, z, ALL), "pre_w": ss(df, z, PRE, "fwd_w"), "post_w": ss(df, z, POST, "fwd_w"), "full_w": ss(df, z, ALL, "fwd_w"),209 "fm_full": {"slope_pct": round(RES["fama_macbeth"]["tenk_w" if tag == "10k" else "formw_w" if tag == "formw" else "compos_w"][z if tag in ("10k", "formw") else "z_AI_COMPOS"]["mean_coef"] * 100, 2)} if tag in ("10k", "formw") else None}210# FM per form (raw and winsorized) + joint horse race + HAC Wald test of equality of the three slope series211def fm_form(oc):212 coefs = {k: [] for k in ["z_AI_10k", "z_AI_10q", "z_AI_8k"]}; single = {k: [] for k in coefs}213 for yr, g in df.groupby("fiscal_year"):214 g = g.dropna(subset=[oc, "z_AI_10k", "z_AI_10q", "z_AI_8k", "z_size", "z_mom", "z_vol"])215 if len(g) < 40: continue216 r = sm.OLS(g[oc], sm.add_constant(g[list(coefs) + ["z_size", "z_mom", "z_vol"]])).fit()217 for k in coefs: coefs[k].append(r.params[k]); single[k].append(sm.OLS(g[oc], sm.add_constant(g[[k, "z_size", "z_mom", "z_vol"]])).fit().params[k])218 Cm = np.array([coefs[k] for k in coefs]).T; Tn = Cm.shape[0]; mu = Cm.mean(0)219 # HAC (Bartlett, 3 lags) long-run covariance of the 3 slope series -> Wald test of H0: b_10k = b_10q = b_8k220 Xc = Cm - mu; S = Xc.T @ Xc / Tn221 for l in range(1, 4): G_ = Xc[l:].T @ Xc[:-l] / Tn; S += (1 - l / 4) * (G_ + G_.T)222 Rm = np.array([[1, -1, 0], [1, 0, -1]]); W = float((Rm @ mu).T @ np.linalg.inv(Rm @ (S / Tn) @ Rm.T) @ (Rm @ mu)); from scipy import stats as st223 return {"horse_race": {k: {"slope_pct": round(I.nw_t(v)[0] * 100, 2), "t": round(I.nw_t(v)[1], 2), "t_plain": round(I.plain_t(v), 2)} for k, v in coefs.items()}, "single": {k: {"slope_pct": round(I.nw_t(v)[0] * 100, 2), "t": round(I.nw_t(v)[1], 2), "t_plain": round(I.plain_t(v), 2)} for k, v in single.items()},224 "wald_equality": {"stat": round(W, 3), "df": 2, "p": round(float(1 - st.chi2.cdf(W, 2)), 4), "n_years": Tn}}225N["fm_forms_raw"] = fm_form("fwd_ret"); N["fm_forms_w"] = fm_form("fwd_w"); N["horse_race_fm"] = N["fm_forms_raw"]["horse_race"]226N["corr_matrix"] = df[["z_AI_COMPOS", "z_AI_10k", "z_AI_10q", "z_AI_8k", "z_AI_formw"]].corr().round(3).to_dict()227# post-launch share of the 2022 cohort's text and the pre-launch-only re-run (eq. (3) = unweighted mean of filing wmeans, verified in r3k_16 log)228f22 = fs[fs.fiscal_year == 2022].copy(); f22["post_launch"] = pd.to_datetime(f22.filing_date) > pd.Timestamp("2022-11-30")229N["cohort2022"] = {"share_filings_post_launch": round(float(f22.post_launch.mean()), 4), "share_chunks_post_launch": round(float(f22.loc[f22.post_launch, "chunk_count"].sum() / f22.chunk_count.sum()), 4), "share_filings_post_launch_by_form": f22.groupby("form_type").post_launch.mean().round(4).to_dict()}230chk = fs.groupby(["ticker", "fiscal_year"])[["ai_adoption_wmean", "ai_infrastructure_wmean"]].mean().reset_index().merge(df[["ticker", "fiscal_year", "ai_adoption_wmean"]].rename(columns={"ai_adoption_wmean": "panel"}), on=["ticker", "fiscal_year"])231N["cohort2022"]["aggregation_reproduced_share_1e6"] = round(float(((chk.ai_adoption_wmean - chk.panel).abs() < 1e-6).mean()), 5)232pre_only = f22[~f22.post_launch].groupby("ticker")[["ai_adoption_wmean", "ai_infrastructure_wmean"]].mean().reset_index(); d22 = df[df.fiscal_year == 2022].drop(columns=[c for c in ["ad22", "in22"] if c in df.columns]).merge(pre_only.rename(columns={"ai_adoption_wmean": "ad22", "ai_infrastructure_wmean": "in22"}), on="ticker", how="inner")233d22["z22"] = (zc(d22.ad22) + zc(d22.in22)) / 2; cs22 = cohort_spreads(d22, "z22", [2022]); cs22w = cohort_spreads(d22, "z22", [2022], "fwd_w"); cs22b = cohort_spreads(d22, "z_AI_COMPOS", [2022]); cs22bw = cohort_spreads(d22, "z_AI_COMPOS", [2022], "fwd_w")234N["cohort2022"]["spread_prelaunch_text_only_pct"] = round(cs22.get(2022, np.nan) * 100, 2); N["cohort2022"]["spread_prelaunch_text_only_w_pct"] = round(cs22w.get(2022, np.nan) * 100, 2); N["cohort2022"]["spread_full_text_same_firms_pct"] = round(cs22b.get(2022, np.nan) * 100, 2); N["cohort2022"]["spread_full_text_same_firms_w_pct"] = round(cs22bw.get(2022, np.nan) * 100, 2); N["cohort2022"]["n_firms_with_prelaunch_filings"] = int(len(d22)); N["cohort2022"]["rank_corr_prelaunch_vs_full"] = round(float(d22[["z22", "z_AI_COMPOS"]].corr(method="spearman").iloc[0, 1]), 3)235R2["filing_type"] = N; print("N", {k: (v["pre_w"]["mean"], v["post_w"]["mean"], v["corr_with_baseline"]) for k, v in N.items() if isinstance(v, dict) and "pre_w" in v}, "| Wald", N["fm_forms_w"]["wald_equality"], "| 2022", N["cohort2022"], flush=True)236237# ---------------- O. placebo break dates, permutation, sup-Wald [M] ----------------238O = {}239for tag, oc, st in [("raw", "fwd_ret", "mean"), ("w", "fwd_w", "mean"), ("med", "fwd_ret", "median")]:240 cs_all = cohort_spreads(df, "z_AI_COMPOS", ALL, oc, st); diffs = {}241 for b in range(2013, 2023):242 pre_ = [v for k, v in cs_all.items() if k < b]; post_ = [v for k, v in cs_all.items() if k >= b]; diffs[b] = round((np.mean(post_) - np.mean(pre_)) * 100, 1)243 O[tag] = {"diff_post_minus_pre_pct_by_break_year": diffs, "rank_of_2022": int(sorted(diffs.values(), reverse=True).index(diffs[2022]) + 1), "n_candidates": len(diffs), "permutation": I.permutation_three_cohorts(cs_all, [2022, 2023, 2024]), "sup_wald": I.sup_wald([cs_all[y] for y in sorted(cs_all)])}244R2["placebo_breaks"] = O["raw"] | {"winsorized": O["w"], "median": O["med"]}; print("O raw perm p", O["raw"]["permutation"]["p_one_sided"], "rank", O["raw"]["permutation"]["rank_among_subsets"], "| wins", O["w"]["permutation"]["p_one_sided"], "| sup-Wald raw", O["raw"]["sup_wald"]["sup_wald"], O["raw"]["sup_wald"]["p_simulated"], flush=True)245246# ---------------- P. SPID interactions on the rebuilt panel (explicit terms) [C, I] ----------------247P = {}248for k in ["z_SPID_TECH", "spid_ai_any", "spid_tech_any"]:249 d2 = df.dropna(subset=["fwd_w", k, "z_AI_COMPOS", "z_size", "z_mom"]).copy(); d2["kp"] = d2[k] * d2.post; d2["cp"] = d2.z_AI_COMPOS * d2.post250 c = clustered(f"fwd_w ~ {k} + post + kp + z_AI_COMPOS + cp + z_size + z_mom", d2); X = smf.ols(f"fwd_w ~ {k} + post + kp + z_AI_COMPOS + cp + z_size + z_mom", data=d2).fit().model.exog251 P[k] = {"terms": {{"kp": f"{k}:post", "cp": "z_AI_COMPOS:post"}.get(v, v): vals for v, vals in c["coef"].items()}, "n": c["n"], "rank": int(np.linalg.matrix_rank(X)), "cond": round(float(np.linalg.cond(X)), 2)}252P["share_firm_years_any_ai_project"] = RES["spid"]["share_firm_years_any_ai"]; P["share_post_any_ai_project"] = RES["spid"]["share_post_any_ai"]; P["ai_any_by_quintile_post"] = df[df.post == 1].groupby("q").spid_ai_any.mean().round(3).to_dict()253P["binary_contrasts"] = {k: RES["spid"][k] for k in ["spid_ai_any", "spid_tech_any"]}; P["tercile_nonzero_w"] = RES["spid"]["tercile_T3_minus_T1_nonzero_w"]; R2["spid"] = P; print("P", {k: P[k]["terms"][f"{k}:post"] for k in ["z_SPID_TECH", "spid_ai_any"]}, flush=True)254255# ---------------- Q. reversal (AI-washing) and cyber-neutral sort [K] ----------------256rv = df[(df.fiscal_year <= 2023)].dropna(subset=["fwd_ret", "fwd_ret_24m", "z_AI_COMPOS"]).copy(); rv["r13_24"] = (1 + rv.fwd_ret_24m) / (1 + rv.fwd_ret) - 1; rv["r13_24_w"] = rv.groupby("fiscal_year")["r13_24"].transform(wins_within)257rv["hi_lang"] = rv.groupby("fiscal_year")["z_AI_COMPOS"].transform(lambda s: s >= s.quantile(0.8)); hl = rv[rv.hi_lang].copy(); hl["proj"] = (hl.spid_mentions_tech > 0).astype(int)258def contrast_by_cohort(d, flag, oc, min_n=15):259 out = {}260 for yr, g in d.groupby("fiscal_year"):261 a = g[g[flag] == 1][oc].dropna(); b = g[g[flag] == 0][oc].dropna()262 if len(a) >= min_n and len(b) >= min_n: out[int(yr)] = float(a.mean() - b.mean())263 ls = list(out.values()); mu, t = I.nw_t(ls); return {"mean_pct": round(mu * 100, 2) if ls else None, "t": round(t, 2) if t == t else None, "t_plain": round(I.plain_t(ls), 2) if len(ls) > 1 else None, "ci95_pct": [round(x * 100, 2) for x in I.ci95(ls)] if len(ls) > 1 else [None, None], "n_cohorts": len(ls), "by_cohort_pct": {k: round(v * 100, 1) for k, v in out.items()}}264Q = {"reversal": {"high_language_with_vs_without_projects": {"month_1_12_w": contrast_by_cohort(hl, "proj", "fwd_w"), "month_13_24_w": contrast_by_cohort(hl, "proj", "r13_24_w"), "month_1_24_raw": contrast_by_cohort(hl, "proj", "fwd_ret_24m"), "n_high_language_with_projects": int(hl.proj.sum()), "n_high_language": int(len(hl))},265 "Q5_minus_Q1_month_13_24": {"raw": ss(rv, "z_AI_COMPOS", range(2010, 2024), "r13_24"), "w": ss(rv, "z_AI_COMPOS", range(2010, 2024), "r13_24_w"), "pre_w": ss(rv, "z_AI_COMPOS", range(2010, 2022), "r13_24_w"), "post_w": ss(rv, "z_AI_COMPOS", range(2022, 2024), "r13_24_w")},266 "Q5_minus_Q1_month_1_24": {"raw": ss(rv, "z_AI_COMPOS", range(2010, 2024), "fwd_ret_24m"), "w": ss(rv, "z_AI_COMPOS", range(2010, 2024), "fwd24_w")}}}267df["cyber_terc"] = df.groupby("fiscal_year")["z_CYBER"].transform(lambda s: pd.qcut(s.rank(method="first"), 3, labels=False)); df["z_AI_COMPOS_cyberneu"] = df.groupby(["fiscal_year", "cyber_terc"])["z_AI_COMPOS"].transform(zc)268Q["cyber"] = {"corr_cyber_composite": round(float(df[["z_CYBER", "z_AI_COMPOS"]].corr().iloc[0, 1]), 3), "corr_cyber_adopt": round(float(df[["z_CYBER", "z_AI_ADOPT"]].corr().iloc[0, 1]), 3), "cyberneutral_sort": {"full": ss(df, "z_AI_COMPOS_cyberneu", ALL), "full_w": ss(df, "z_AI_COMPOS_cyberneu", ALL, "fwd_w"), "pre_w": ss(df, "z_AI_COMPOS_cyberneu", PRE, "fwd_w"), "post_w": ss(df, "z_AI_COMPOS_cyberneu", POST, "fwd_w")},269 "cyber_sort": {"full_w": ss(df, "z_CYBER", ALL, "fwd_w"), "pre_w": ss(df, "z_CYBER", PRE, "fwd_w"), "post_w": ss(df, "z_CYBER", POST, "fwd_w")}, "fm_compos_with_cyber_w": RES["fama_macbeth"]["compos_cyber_w"]}270R2["cluster_k"] = Q; print("Q reversal 13-24 hi-lang proj vs no", Q["reversal"]["high_language_with_vs_without_projects"]["month_13_24_w"], "| cyber-neutral full w", Q["cyber"]["cyberneutral_sort"]["full_w"]["mean"], "corr", Q["cyber"]["corr_cyber_composite"], flush=True)271272# ---------------- R. multiple-testing families [N] ----------------273rng = np.random.default_rng(I.RNG_SEED); B = 9999274def family_cohort(series_dict):275 """series_dict: name -> list of cohort values. Shared cohort-resampling bootstrap (same draws for every member), recentred t's."""276 names = list(series_dict); n = min(len(v) for v in series_dict.values()); M = np.array([np.asarray(series_dict[k][:n], float) for k in names]).T # cohorts x members (aligned on the first n cohorts)277 t_obs = M.mean(0) / (M.std(0, ddof=1) / np.sqrt(n)); idx = rng.integers(0, n, size=(B, n)); Mb = M[idx] # B x n x m278 mb = Mb.mean(1); sb = Mb.std(1, ddof=1) / np.sqrt(n); t_boot = (mb - M.mean(0)) / sb279 p_plain = 2 * (1 - __import__("scipy").stats.t.cdf(np.abs(t_obs), n - 1)); return {"members": names, "t": [round(float(x), 2) for x in t_obs], "p": [round(float(x), 4) for x in p_plain], "p_holm": [round(float(x), 4) for x in I.holm(p_plain)], "p_bh": [round(float(x), 4) for x in I.bh(p_plain)], "p_romano_wolf": [round(float(x), 4) for x in I.romano_wolf(t_obs, t_boot)], "family_size": len(names), "n_cohorts": int(n), "hlz_t_gt_3": [bool(abs(x) > 3) for x in t_obs]}280MHT = {}281# (a) Table 5 / 9 family: 5 signals x {EW, VW} x {full, pre, post}, cohort-level spreads on winsorized returns (full = all cohorts; pre/post subsets need aligned lengths -> separate sub-families)282for per, yrs in [("full", ALL), ("pre", PRE), ("post", POST)]:283 fam = {}284 for k in ["AI_ADOPT", "AI_INFRA", "AI_RISK", "AI_COMPOS", "SPID_TECH"]:285 for wt in ["EW", "VW"]:286 s = RES["sorts"][f"{k}_{wt}_w"]["by_cohort"]; fam[f"{k}_{wt}"] = [s[str(y)] if str(y) in s else s[y] for y in sorted(int(x) for x in s) if int(y) in yrs] if False else [v for y, v in sorted((int(y), v) for y, v in s.items()) if y in yrs]287 MHT[f"sorts_{per}_w"] = family_cohort(fam)288# (b) Table 14: 8 placebo interactions on shared wild-bootstrap draws (identical seed and G -> identical weight matrix)289names = list(PL); t_obs = np.array([PL[n]["interaction"]["t_year"] for n in names]); tb = np.column_stack([tboot_pl[n]["webb"] for n in names])290p_year = 2 * (1 - __import__("scipy").stats.t.cdf(np.abs(t_obs), 14)); MHT["placebo_interactions_yearcl"] = {"members": names, "t_year": [round(float(x), 2) for x in t_obs], "t_firm": [PL[n]["interaction"]["t_firm"] for n in names], "p_wild_webb": [PL[n]["interaction"]["p_wild_webb"] for n in names], "p_holm_on_wild": [round(float(x), 4) for x in I.holm([PL[n]["interaction"]["p_wild_webb"] for n in names])], "p_bh_on_wild": [round(float(x), 4) for x in I.bh([PL[n]["interaction"]["p_wild_webb"] for n in names])], "p_romano_wolf": [round(float(x), 4) for x in I.romano_wolf(t_obs, tb)], "family_size": len(names)}291# (c) ten break dates: post - pre mean difference, cohort bootstrap shared292cs_w = cohort_spreads(df, "z_AI_COMPOS", ALL, "fwd_w"); yrs_ = sorted(cs_w); v = np.array([cs_w[y] for y in yrs_]); stats_obs = []; names = []293for b in range(2013, 2023):294 m = np.array([y >= b for y in yrs_]); names.append(str(b)); stats_obs.append(v[m].mean() - v[~m].mean())295idx = rng.integers(0, len(v), size=(B, len(v))); boot = []296for bi in range(B):297 vb = v[idx[bi]]; yb = np.array(yrs_)[idx[bi]]; boot.append([(vb[yb >= b].mean() - vb[yb < b].mean()) if (yb >= b).any() and (yb < b).any() else 0.0 for b in range(2013, 2023)])298boot = np.array(boot) - np.array(stats_obs); sd = boot.std(0, ddof=1); t_obs = np.array(stats_obs) / sd; t_boot = boot / sd299MHT["break_dates_w"] = {"members": names, "diff_pct": [round(x * 100, 1) for x in stats_obs], "t_boot_se": [round(float(x), 2) for x in t_obs], "p_boot": [round(float((np.abs(t_boot[:, i]) >= abs(t_obs[i])).mean()), 4) for i in range(len(names))], "p_romano_wolf": [round(float(x), 4) for x in I.romano_wolf(t_obs, t_boot)], "family_size": len(names)}300# (e) three forms: FM slope series (winsorized), cohort bootstrap301MHT["forms_fm_w"] = family_cohort({k: v for k, v in zip(["10k", "10q", "8k"], [[], [], []])} if False else {k: [] for k in []}) if False else None302fmf = {}; 303for yr, g in df.groupby("fiscal_year"):304 g = g.dropna(subset=["fwd_w", "z_AI_10k", "z_AI_10q", "z_AI_8k", "z_size", "z_mom", "z_vol"])305 if len(g) < 40: continue306 for k in ["z_AI_10k", "z_AI_10q", "z_AI_8k"]: fmf.setdefault(k, []).append(sm.OLS(g["fwd_w"], sm.add_constant(g[[k, "z_size", "z_mom", "z_vol"]])).fit().params[k])307MHT["forms_fm_w"] = family_cohort(fmf)308# (f) robustness rows (Table 19): 12 specifications x {pre, post} on winsorized cohort spreads -> two families (pre, post)309rob = {"baseline": "z_AI_COMPOS", "sector_neutral": "z_AI_COMPOS_sn", "ex_mag7": None, "ex_top500": None, "signal_mean": "z_AI_mean", "signal_top5": "z_AI_top5pct", "iwv_adj": None, "h6": None, "h24": None, "pca": "z_AI_PC1", "seasoned": None, "listed36": None}310for per, yrs in [("pre", PRE), ("post", POST)]:311 fam = {}312 for nm in rob:313 if nm == "ex_mag7": s = cohort_spreads(df[~df.ticker.isin(MAG7)], "z_AI_COMPOS", yrs, "fwd_w")314 elif nm == "ex_top500": s = cohort_spreads(df[df.cap_rank > 500], "z_AI_COMPOS", yrs, "fwd_w")315 elif nm == "iwv_adj": s = cohort_spreads(df, "z_AI_COMPOS", yrs, "fwd_madj_w")316 elif nm == "h6": s = cohort_spreads(df, "z_AI_COMPOS", yrs, "fwd6_w")317 elif nm == "h24": s = cohort_spreads(df, "z_AI_COMPOS", range(yrs[0], min(yrs[-1] + 1, 2024)), "fwd24_w")318 elif nm == "seasoned": s = cohort_spreads(seasoned, "z_AI_COMPOS", yrs, "fwd_w")319 elif nm == "listed36": s = cohort_spreads(l36, "z_AI_COMPOS", yrs, "fwd_w")320 else: s = cohort_spreads(df, rob[nm], yrs, "fwd_w")321 fam[nm] = [s[y] for y in sorted(s)]322 MHT[f"robustness_{per}_w"] = family_cohort(fam)323# deflated Sharpe ratio of the post-launch and full-sample composite LS, trials = every LS series tried in r3k_15 (all signals, samples, groups)324la = RES["ls_alpha"]["AI_COMPOS"]; trials = RES["sr_trials_ann"]325MHT["deflated_sharpe"] = {"full": I.deflated_sharpe(la["full"]["sharpe"], la["full"]["n_months"], la["full"]["skew"], la["full"]["kurt"], trials), "post": I.deflated_sharpe(la["post"]["sharpe"], la["post"]["n_months"], la["post"]["skew"], la["post"]["kurt"], trials), "pre": I.deflated_sharpe(la["pre"]["sharpe"], la["pre"]["n_months"], la["pre"]["skew"], la["pre"]["kurt"], trials), "n_trials": len(trials)}326# count of t > 3 (HLZ) across every statistic printed in the headline families327MHT["hlz_summary"] = {k: int(sum(v["hlz_t_gt_3"])) for k, v in MHT.items() if isinstance(v, dict) and "hlz_t_gt_3" in v}328R2["mht"] = MHT; print("R MHT sorts_full_w RW p:", dict(zip(MHT["sorts_full_w"]["members"], MHT["sorts_full_w"]["p_romano_wolf"])), "| placebo RW", dict(zip(MHT["placebo_interactions_yearcl"]["members"], MHT["placebo_interactions_yearcl"]["p_romano_wolf"])), "| DSR", MHT["deflated_sharpe"], flush=True)329330# ---------------- S. specification curve [N] ----------------331# signals by form x aggregation from filing_scores332agg_cols = {"wmean": "wmean", "mean": "mean", "top5": "top5pct"}; spec_sig = {}333for form in ["all", "10-K", "10-Q", "8-K"]:334 sub = fs if form == "all" else fs[fs.form_type == form]335 for ag, col in agg_cols.items():336 a = sub.groupby(["ticker", "fiscal_year"]).agg(ad=(f"ai_adoption_{col}", "mean"), inf=(f"ai_infrastructure_{col}", "mean")).reset_index(); nm = f"sig_{form}_{ag}"337 t_ = df[["ticker", "fiscal_year"]].merge(a, on=["ticker", "fiscal_year"], how="left"); zz = (t_.groupby(t_.fiscal_year)["ad"].transform(zc) + t_.groupby(t_.fiscal_year)["inf"].transform(zc)) / 2; df[nm] = zz.values; spec_sig[(form, ag)] = nm338df["fwd_w5"] = df.groupby("fiscal_year")["fwd_ret"].transform(lambda s: s.clip(s.quantile(.05), s.quantile(.95))); df["fwd6_w5"] = df.groupby("fiscal_year")["fwd_ret_6m"].transform(lambda s: s.clip(s.quantile(.05), s.quantile(.95))); df["fwd24_w5"] = df.groupby("fiscal_year")["fwd_ret_24m"].transform(lambda s: s.clip(s.quantile(.05), s.quantile(.95)))339OCS = {("raw", 6): ("fwd_ret_6m", "mean"), ("raw", 12): ("fwd_ret", "mean"), ("raw", 24): ("fwd_ret_24m", "mean"), ("w199", 6): ("fwd6_w", "mean"), ("w199", 12): ("fwd_w", "mean"), ("w199", 24): ("fwd24_w", "mean"), ("w595", 6): ("fwd6_w5", "mean"), ("w595", 12): ("fwd_w5", "mean"), ("w595", 24): ("fwd24_w5", "mean"), ("median", 6): ("fwd_ret_6m", "median"), ("median", 12): ("fwd_ret", "median"), ("median", 24): ("fwd_ret_24m", "median")}340FILT = {"all": df, "ex_top500": df[df.cap_rank > 500], "large": df[df.size_group == "large"], "small": df[df.size_group == "small"]}341def sort_spread(d, z, weight, oc, stat, yrs):342 ls = []343 for yr, g in d[d.fiscal_year.isin(list(yrs))].groupby("fiscal_year"):344 g = g.dropna(subset=[z, oc]).copy()345 if len(g) < 40: continue346 g["q"] = pd.qcut(g[z].rank(method="first"), 5, labels=False) + 1; vals = {}347 for q in (1, 5):348 s = g[g.q == q]349 if weight is None: vals[q] = s[oc].median() if stat == "median" else s[oc].mean()350 else: w = s[weight].clip(lower=0).fillna(0); vals[q] = np.average(s[oc], weights=w) if w.sum() > 0 else s[oc].mean()351 ls.append(vals[5] - vals[1])352 mu, t = I.nw_t(ls); return mu, t, I.plain_t(ls), len(ls)353rows = []; t1 = time.time()354for (wz, hz), (oc, st) in OCS.items():355 yrs = ALL if hz < 24 else range(2010, 2024)356 for wt in ["EW", "VW"]:357 for (form, ag), z in spec_sig.items():358 for fname, d in FILT.items():359 mu, t, tp, n = sort_spread(d, z, None if wt == "EW" else "mktcap", oc, st, yrs); rows.append({"winsor": wz, "horizon": hz, "weight": wt, "form": form, "aggregation": ag, "size_filter": fname, "spread_pct": round(mu * 100, 2), "t_nw": round(t, 2), "t_plain": round(tp, 2), "n_years": n})360sc = pd.DataFrame(rows); sc.to_csv(f"{T}/spec_curve.csv", index=False)361R2["spec_curve"] = {"n_specs": int(len(sc)), "share_positive": round(float((sc.spread_pct > 0).mean()), 3), "share_positive_sig_nw": round(float(((sc.spread_pct > 0) & (sc.t_nw.abs() > 1.96)).mean()), 3), "share_positive_sig_plain": round(float(((sc.spread_pct > 0) & (sc.t_plain.abs() > 1.96)).mean()), 3), "share_negative_sig_nw": round(float(((sc.spread_pct < 0) & (sc.t_nw.abs() > 1.96)).mean()), 3), "median_spread_pct": round(float(sc.spread_pct.median()), 2), "iqr_spread_pct": [round(float(sc.spread_pct.quantile(.25)), 2), round(float(sc.spread_pct.quantile(.75)), 2)], "by_size_filter_median": sc.groupby("size_filter").spread_pct.median().round(2).to_dict(), "by_winsor_median": sc.groupby("winsor").spread_pct.median().round(2).to_dict(), "by_weight_median": sc.groupby("weight").spread_pct.median().round(2).to_dict(), "by_form_median": sc.groupby("form").spread_pct.median().round(2).to_dict(), "seconds": round(time.time() - t1)}362print("S spec curve", R2["spec_curve"], flush=True)363364# ---------------- coverage ----------------365cov = df.groupby("fiscal_year").agg(n_firms=("ticker", "nunique"), median_chunks=("total_chunks", "median"), median_filings=("filing_count", "median"), n_large=("size_group", lambda s: (s == "large").sum()), n_small=("size_group", lambda s: (s == "small").sum()), n_nocap=("mktcap", lambda s: s.isna().sum()), n_large_pct42=("size_group_pct42", lambda s: (s == "large").sum()), n_sp500=("in_sp500", "sum"))366R2["coverage"] = {c: cov[c].to_dict() for c in cov.columns} | {"firms_per_cohort": cov.n_firms.to_dict()}367df["rk"] = df.groupby("fiscal_year")["z_AI_COMPOS"].rank(pct=True); wide = df.pivot_table(index="ticker", columns="fiscal_year", values="rk")368def lag_corr(Lg): return round(float(np.nanmean([wide[y].corr(wide[y + Lg], method="spearman") for y in wide.columns if y + Lg in wide.columns])), 3)369R2["coverage"]["rank_autocorr_lag1"] = lag_corr(1); R2["coverage"]["rank_autocorr_lag5"] = lag_corr(5); R2["runtime_s"] = round(time.time() - T0)370json.dump(R2, open(f"{T}/results2.json", "w"), indent=2, default=str); print("saved results/r3k_v2/results2.json in", R2["runtime_s"], "s", flush=True)371