TeX 69.3%
Python 30.6%
1"""207_event_study.py -- Market-model event study around the public launch of3ChatGPT (30 November 2022). Tests whether firms with HIGHER pre-event AI4disclosure (from their last pre-event 10-K, FY2021) earned higher cumulative5abnormal returns (CAR) after the AI-narrative shock.67Design:8 Event date t0 = 2022-11-30.9 Estimation window: 2022-01-03 .. 2022-10-31 (market model AR = r - a - b*rmkt).10 Event window: 2022-11-01 .. 2024-06-30.11 Treatment: tercile of pre-event AI exposure (composite adoption+infra, FY2021).12Outputs daily CAR paths per tercile + cross-sectional CAR regressions.13"""14import time, warnings15import numpy as np, pandas as pd, yfinance as yf16import statsmodels.api as sm17warnings.filterwarnings("ignore")18D="data/processed"; T="results/tables"1920panel=pd.read_parquet(f"{D}/analysis_panel.parquet")21tickers=sorted(panel.ticker.dropna().unique().tolist())2223# ---- download daily prices (cache) ----24import os25DPATH=f"{D}/prices_daily_event.parquet"2627def extract_close(d, t):28 """Return Close series for ticker t from a yf.download frame (any layout)."""29 try:30 if isinstance(d.columns, pd.MultiIndex):31 if t in d.columns.get_level_values(0):32 return d[t]["Close"].dropna()33 if "Close" in d.columns.get_level_values(0):34 return d["Close"][t].dropna()35 return d["Close"].dropna()36 except Exception:37 return None3839if not os.path.exists(DPATH):40 frames=[]41 for i in range(0,len(tickers),60):42 b=tickers[i:i+60]43 d=yf.download(b,start="2021-09-01",end="2024-07-15",interval="1d",auto_adjust=True,44 progress=False,group_by="ticker",threads=True)45 for t in b:46 s=extract_close(d,t)47 if s is None or len(s)==0: continue48 tmp=s.reset_index(); tmp.columns=["date","px"]; tmp["ticker"]=t; frames.append(tmp)49 time.sleep(1); print("daily batch",i//60+1, flush=True)50 spy=yf.download("SPY",start="2021-09-01",end="2024-07-15",interval="1d",auto_adjust=True,progress=False)51 sc=extract_close(spy,"SPY")52 sdf=sc.reset_index(); sdf.columns=["date","px"]; sdf["ticker"]="SPY"53 frames.append(sdf)54 dp=pd.concat(frames,ignore_index=True)55 dp.to_parquet(DPATH,index=False)56else:57 dp=pd.read_parquet(DPATH)5859dp["date"]=pd.to_datetime(dp["date"])60dp=dp.sort_values(["ticker","date"])61dp["ret"]=dp.groupby("ticker")["px"].pct_change()62spy=dp[dp.ticker=="SPY"][["date","ret"]].rename(columns={"ret":"mkt"})6364t0=pd.Timestamp("2022-11-30")65est0,est1=pd.Timestamp("2022-01-03"),pd.Timestamp("2022-10-31")66ev0,ev1=pd.Timestamp("2022-11-01"),pd.Timestamp("2024-06-30")6768# pre-event AI exposure from FY2021 filing69exp=panel[panel.fiscal_year==2021].copy()70exp["ai_pre"]=(exp.groupby("fiscal_year")["ai_adoption_wmean"].transform(lambda g:(g-g.mean())/g.std())71 +exp.groupby("fiscal_year")["ai_infrastructure_wmean"].transform(lambda g:(g-g.mean())/g.std()))/272exp=exp[["ticker","ai_pre","log_mktcap","sector"]].dropna(subset=["ai_pre"])73exp["tercile"]=pd.qcut(exp.ai_pre,3,labels=["Low","Mid","High"])7475# compute AR per ticker76car_rows=[]; daily_ar={}77beta_map={}78for t,g in dp[dp.ticker!="SPY"].groupby("ticker"):79 g=g.merge(spy,on="date",how="left").dropna(subset=["ret","mkt"])80 est=g[(g.date>=est0)&(g.date<=est1)]81 if len(est)<60: continue82 X=sm.add_constant(est["mkt"]); m=sm.OLS(est["ret"],X).fit()83 a,b=m.params["const"],m.params["mkt"]; beta_map[t]=b84 ev=g[(g.date>=ev0)&(g.date<=ev1)].copy()85 ev["ar"]=ev["ret"]-(a+b*ev["mkt"])86 ev["car"]=ev["ar"].cumsum()87 daily_ar[t]=ev.set_index("date")["car"]88 for hz,lab in [(60,"car_60d"),(125,"car_6m"),(250,"car_1y"),(395,"car_18m")]:89 sub=ev.iloc[:hz]90 car_rows.append({"ticker":t,"hz":lab,"car":sub["ar"].sum()})91car=pd.DataFrame(car_rows).pivot(index="ticker",columns="hz",values="car").reset_index()92car=car.merge(exp,on="ticker",how="inner"); car["beta"]=car.ticker.map(beta_map)93car.to_parquet(f"{D}/event_car.parquet",index=False)9495# mean CAR path by tercile96allc=pd.DataFrame(daily_ar) # cols=tickers, index=date97allc=allc.dropna(how="all")98paths={}99for terc in ["Low","Mid","High"]:100 tks=[t for t in exp[exp.tercile==terc].ticker if t in allc.columns]101 paths[terc]=allc[tks].mean(axis=1)102path_df=pd.DataFrame(paths)103path_df["High_minus_Low"]=path_df["High"]-path_df["Low"]104path_df.to_parquet(f"{D}/event_car_paths.parquet")105106# tests107RES={}108print("=== ChatGPT event study: mean CAR by pre-event AI tercile ===")109for hz in ["car_60d","car_6m","car_1y","car_18m"]:110 hi=car[car.tercile=="High"][hz].dropna(); lo=car[car.tercile=="Low"][hz].dropna()111 from scipy import stats112 tt=stats.ttest_ind(hi,lo,equal_var=False)113 RES[hz]={"High":round(hi.mean()*100,2),"Mid":round(car[car.tercile=='Mid'][hz].mean()*100,2),114 "Low":round(lo.mean()*100,2),"H_minus_L":round((hi.mean()-lo.mean())*100,2),115 "t":round(tt.statistic,2),"p":round(tt.pvalue,4)}116 print(f" {hz:8s} High={RES[hz]['High']:+6.2f}% Low={RES[hz]['Low']:+6.2f}% H-L={RES[hz]['H_minus_L']:+6.2f}% (t={RES[hz]['t']}, p={RES[hz]['p']})")117118# cross-sectional regression: CAR_1y on ai_pre + controls119reg=car.dropna(subset=["car_1y","ai_pre","log_mktcap","beta"]).copy()120X=sm.add_constant(reg[["ai_pre","log_mktcap","beta"]])121m=sm.OLS(reg["car_1y"],X).fit(cov_type="HC1")122RES["xsec_reg_1y"]={"ai_pre_coef":round(m.params["ai_pre"]*100,3),"ai_pre_t":round(m.tvalues["ai_pre"],2),123 "n":int(m.nobs),"r2":round(m.rsquared,3)}124# add sector FE version125import statsmodels.formula.api as smf126m2=smf.ols("car_1y ~ ai_pre + log_mktcap + beta + C(sector)",data=reg).fit(cov_type="HC1")127RES["xsec_reg_1y_sectfe"]={"ai_pre_coef":round(m2.params["ai_pre"]*100,3),"ai_pre_t":round(m2.tvalues["ai_pre"],2),"n":int(m2.nobs)}128print(f"\n Cross-sec: CAR_1y on AI_pre: {RES['xsec_reg_1y']['ai_pre_coef']:+.2f}%/SD (t={RES['xsec_reg_1y']['ai_pre_t']}, n={RES['xsec_reg_1y']['n']})")129print(f" + sector FE: {RES['xsec_reg_1y_sectfe']['ai_pre_coef']:+.2f}%/SD (t={RES['xsec_reg_1y_sectfe']['ai_pre_t']})")130131import json; json.dump(RES,open(f"{T}/results_event.json","w"),indent=2,default=str)132print("\nSaved event-study results.")133