python做事件研究法_Event Study(附Python代码)(一)
事件研究法是一种分析特定事件对股价影响的统计方法,通过比较市场预期和实际收益来评估事件的影响。本文以新冠疫情中的肺炎事件为例,使用Fama的三因子模型作为基准,定义了估计窗口和事件窗口,计算了超额收益,并通过统计检验和可视化结果展示了大部分公司的超额收益显著。此外,还计算了累积超额收益(CAR)和平均累积超额收益(CAAR),并展示了其置信区间。结果显示,事件对大部分公司的影响显著,为投资者提供了重要的参考。
过年回国正好赶上肺炎疫情,也正好有时间开始写专栏了!第一个话题,我决定写写事件研究法(Event Study),原因之一就是疫情事件,可能我还太年轻,直到现在才意识到瘟疫事件对金融市场的影响。富时A50指数和日经225指数已经连续下跌两天,而美国股市也从年前就感受到了疫情带来的恐慌情绪,早早开始进行了避险操作。所以,我觉得现在是个好机会来深入探讨一下事件研究法,特别是这次疫情带来的影响。不知道未来会不会有更多关于这次疫情的事件研究文章呢?
所以根据字面意思,事件研究法就是研究某一个事件对股价(收益率)的影响,某个积极(也可能是消极)信息对于股价的影响,说到这儿,就不得不提一下宗师 Eugene F. Fama了——event study基本上还是根据有效市场假说而来,市场消化新的信息,并且对新的信息作出显著的反应。当然这个反应是立刻而且及时的,在事件公布之后,股价或许会归于正常,或许会因为是一个很差的消息而一蹶不振,也可能影响根本不显著。也就是如本文封面:
此图片源自Journal of Economic Literature,其中涉及到的异常收益(Abnormal Return)和自回归模型(AR)是研究中的核心内容。
开始讨论。首先需要明确两个时间段:estimation window和event window。其中,estimation window是用于估计market model的一个时间段,而estimation window则用于计算超额收益。另一个event window则用于计算超额收益。
,
其中,
也就是 abnormal return 在 第 i 期的超额收益,
是实际第 i 期的实际收益,
也就是在没有 X 事件发生条件下的期望收益。因此,计算超额收益的主要方法是计算这个。
在估计过程中,我们依赖于estimation window中的估计参数。那么让我们明确这两个estimation window的定义。
方便起见,estiamtion window为
,event window为
通常情况下,estimation的跨度被设定为250个交易日,而event的跨度则被设定为20个交易日(完全自由,其设置程度也受事件长度的影响)。
在下一步骤中,我们将搭建我们的基准模型,即代表用于评估市场预期收益的模型。具体而言,在本案例中,我采用Fama的经典三因子模型作为基准设定。
关于这一经典的公式,目前不做进一步的详细阐述,建议后续文章中进行更深入的探讨。值得推荐的是,该网站提供了丰富的历史三因子数据,涵盖了多个其他相关因子。
将上边的三因子模型以矩阵的形式求解,
一个
的向量,
为一个
的矩阵,t为我们estimation window的长度,可得:
也就是我们的超额收益啦(abnormal return)。我们根据统计检验一下,
是否显著:
AAR -- Aggregated Abnormal Return
关于"Emmm"这一词的翻译问题,目前尚未有明确的解决方案。在研究单个事件时,我们通常会考虑多个公司的数据,但这些公司并不总是会在事件发生日展现出显著的超额收益。因此,我们可以考察整个样本集合中超额收益的整体显著性。假设各个公司所经历的事件时间跨度不重叠,否则就可能难以区分是由公司间的相关性还是其他因素导致的同涨同跌现象。在我的示例中,确实存在这种情况。CAR -- 累积异常回报,CAAR -- 累积聚合异常回报。
基于此,需要考察在整个事件窗口期间,这个异常收益的表现是否普遍显著。
根据统计推断,
类似地,计算CAAR的过程是:首先,在该截面计算其期望值,然后基于此,再计算相应的置信区间。例如,以下附上代码示例:
我的栗子中选取的是16家曾发生过财务/经营丑闻的公司分别是:
estimation window = 250, event window = 20; 开始:
调必要的包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
import statsmodels.formula.api as smf
import scipy.stats as st
fig = plt.figure()
sns.set_palette("GnBu_d")
sns.set_style('whitegrid')
%matplotlib inline
#(导入数据各有各的方式,就不展示啦)
导入的数据中包含上述公司的历史收益率,同时记录event date为datetime格式,以及对应的。
三个因子的历史数据,通过将历史收益率数据与三因子数据按照datetime字段合并至一个DataFrame中;同时将event date字段纳入。
单独留在一个DataFrame中备用。
如下:
def EventStudyAnalysis(return_data=return_df, event_information=event_data, stock_list=stocklist):
def EventStudyAnalysis(return_data=return_df, event_information=event_data, stock_list=stocklist):
returndata: 是一个包含不同公司市场回报的数据框;eventdata: 用于不同公司的事件数据;stocklist: 包含参与分析的不同公司;Returns: abnreturn: 是一个记录每个公司异常回报的字典,在其特定时间段的事件窗口中。
abnreturn ={} # abnormal returns on the event window
returndata = returndata.reset_index()
Bse = []
for stock in stocklist:
eventindex等于int(returndata在满足Date字段等于str(eventdata在某一行的EventDate)的条件下取index.values)。
print(eventindex)
This assigns a window of +/-20 events to the event index, extracting relevant data for the specified security within the returndata dataframe object's .loc accessor.
estimation_df赋值为returndata对象从eventindex-270到eventindex-21的区间,包含Date、股票、RF、Mkt_RF、SMB和HML列。
formula = stock + " - RF ~ Mkt_RF + SMB + HML"
beta_Mkt = sm.OLS.from_formula(formula, data=estimation_df).fit().params["Mkt_RF"]
beta_SMB = sm.OLS.from_formula(formula, data=estimation_df).fit().params["SMB"]
beta_HML = sm.OLS.from_formula(formula, data=estimation_df).fit().params["HML"]
alpha等于通过构建基于公式ols模型来估计参数ols中的截距项。
standard_error = sm.OLS.from_formula(formula, data=estimation_df).fit().bse
Bse.append(standard_error)
显示以下信息:{},beta_Mkt= {},beta_SMB = {},beta_HML = {},alpha= {}。
#expected returns for each firm in the estimation window
expectedreturn_event_window := ((event_df[['Mkt_RF']].values * beta_Mkt) + (event_df[['SMB']].values *
beta_SMB) + (event_df[['HML']].values * beta_HML ) + alpha)
#abnormal returns on the event window - AR
anomalously realized return is equal to the event data frame’s stock-specific values minus the flattened list of expected return values within the event window.
abnreturn[stock] = abnormal_return
abnormalreturns_df = pd.DataFrame(abnreturn)
abnormalreturns_df.index = abnormalreturns_df.index-20
return abnormalreturns_df
在事件研究中,这一步骤往往被认为是具有挑战性的核心环节。具体而言,需要将不同时间段的数据进行整合,同时对这16家公司的数据进行重新索引。具体而言,以事件日期为基准,设置索引为0,前后各取20个交易日作为窗口,因此整个窗口的总长度为41个交易日。图中展示了所有公司进行的假设检验结果:
可以看到只有一个公司的超额收益并不显著,我们在把结果可视化:
plt.figure(figsize=(24,16))
for i in range(1,17):
plt.subplot(4,4,i)
abnormalreturns_df[abnormalreturns_df.columns[i-1]].plot()
plt.xlabel('Event Window')
plt.ylabel('Return')
plt.axhline(y=(np.sqrt((abnormalreturns_df.iloc[:,i-1].std()**2/41))*1.96),color='red',linestyle='--')
plt.axhline(y=(np.sqrt((abnormalreturns_df.iloc[:,i-1].std()**2/41))*-1.96),color='red',linestyle='--')
plt.title(abnormalreturns_df.columns[i-1])
结果就很明显了吧(红线为95%的置信区间)?再看一下AAR,同样with plot:
mean_AAR = abnormalreturns_df.mean(axis = 1)
var_AAR = (abnormalreturns_df.std())**2
var_matrix = pd.DataFrame(var_AAR)
var_matrix = var_matrix.T
var_AAR = sum(var_matrix.iloc[0])/16**2
Std_AAR = np.sqrt(var_AAR)
mean_AAR.plot()
plt.axhline(y=Std_AAR*1.96,color='red',linestyle='--')
plt.axhline(y=Std_AAR*-1.96,color='red',linestyle='--')
从整体来看,事件对公司的影响具有显著性。我们再来看一下CAR和CAAR,传统的做法是将曲线及其对应的置信区间加入分析中。
def CAR_se(Abnormal_return=abnormalreturns_df, stock_list=stocklist):
Calculating the standard error of Cumulative Abnormal Return per stock: Input: an abnormal return dataframe or matrix, a list of company names; Output: a dataframe containing the cumulative standard error per stock.
residual_sigma_single = pd.DataFrame()
residual_sigma_cum_single = pd.DataFrame()
resi_single = []
d = {}
for x in stocklist:
resistd = abnormalreturns_df[x].std()/16
d.update({x:resistd})
residual_sigma_single = pd.DataFrame(d,index=Abnormal_return.index)
residual_sigma_cum_single = np.sqrt(residual_sigma_single.cumsum())
se_cum_single = np.sqrt(((residual_sigma_cum_single**2)/16))
return se_cum_single
def CAAR_se(Abnormal_return=abnormalreturns_df, stock_list=stocklist):
To obtain the standard errors of Cumulative Average Abnormal Returns, input the Abnormal Return dataset or matrix along with a list of company names. The output will be a list of cumulative standard errors.
residual_sigma = pd.DataFrame()
resi = []
d = {}
for x in stocklist:
resistd = abnormalreturns_df[x].std()/16
d.update({x:resistd})
residual_sigma = pd.DataFrame(d,index=Abnormal_return.index)
residual_sigma_cum = np.sqrt(residual_sigma.cumsum())
se_cum = np.sqrt(((residual_sigma_cum**2)/16).mean(axis=1))
return se_cum
CAR_df = abnormalreturns_df.cumsum()
plt.figure(figsize=(24,16))
for i in range(1,17):
plt.subplot(4,4,i)
CAR_df[CAR_df.columns[i-1]].plot()
plt.plot(se_cum_single.iloc[:,i-1]*1.96, color='red',linestyle='--')
plt.plot(se_cum_single.iloc[:,i-1]*-1.96, color='red',linestyle='--')
plt.xlabel('Event Window')
plt.ylabel('CAR')
plt.title(CAR_df.columns[i-1])
Var_AAR = ((CAR_df.mean(axis=1))**2)/16
Std_AAR = np.sqrt(Var_AAR)
CAAR
CAAR = mean_AAR.cumsum()
Plot CAAR
CAAR.plot(figsize=(12,8))
plt.xlabel("Event Window")
plt.plot(se*1.96, color='red',linestyle='--')
plt.plot(se*-1.96, color='red',linestyle='--')
plt.ylabel("Cumulative Return")
plt.title("Cumulative Average Abnormal Return")
需要注意的是,CAR和CAAR的置信区间计算方式是一个逐步积累的过程。可以看出,就整体而言,事件对公司的影响是十分显著的。
欢迎大家讨论,非科班出身,代码可能比较屎,欢迎大家debug。
如果有想要数据来复现结果的同学,可以私信我!!!
P.s 现在数据量还不够,希望一个月后可以做一个这次肺炎的event study。
