小白笔记,谨慎食用

什么是事件研究法?

  • 事件研究法 (Event Study Methodology,ESM)是一种用于分析重大事件对公司层面变量短期影响的计量方法。
  • 事件研究法的基本思想是,在一个指定的时间范围内,计算该期间的日异常收益率(实际收益率与预期收益率之差)及其累计值。
  • 历史上,该方法主要应用于金融领域,尤其是用于评估特定事件对企业股票价格的影响。
  • 事件研究法通常用于验证证券市场是否为半强式有效,即检查市场在新信息(如公告、政策变动等)发布后是否迅速且准确地调整价格。
    • 1.若累积异常收益率在事件窗口内显著为零,说明市场迅速反映信息,支持半强式有效性。
    • 2.若在事件发生前出现异常收益,则可能存在信息泄露。
    • 3.若在事件发生后价格反应缓慢,说明市场存在非有效性。

事件研究法的步骤

定义事件

  • 事件是指可能对感兴趣的因变量(Y)产生影响的相关政策或措施。例如,公司发布的重要公告,并购重组或行业突发情况等。

选定事件研究日

  • 常见的问题是一个事件发生会有多个日期,根据实际科学且合理地选择多个日期中信息含量最大的日期作为事件发生日期,即Day0。

确定窗口期及估计期

  • 窗口期:事件窗口的时间范围,包括事件发生前后的若干天。如,[-10, +10]表示事件发生前10天到事件后10天的窗口。如果事件日为交易日,则窗口期是21天,反之,是20天。
  • 估计期:用于估算正常市场反应的时间段。

计算异常收益及累积异常收益

  • 正常收益:正常收益是模型基于历史数据预测正常状态下的表现,因此,要选定正常收益预测模型。
  • 异常收益:事件窗口期内的实际收益与正常收益的差值。
  • 累积异常收益:事件窗口内的异常收益求和,以评估事件的总影响。

显著性及稳健性检验

  • 单样本T检验:异常收益或累积异常收益是否显著为0。

实际案例

京沪高铁股利政策效应分析,即京沪高铁股利政策实施对股价的影响。

事件研究日

  • 本案例以京沪高铁2021-2023年的股利分配事件为样本,且以股利分配公告日为事件研究日。

    年份 2021 2022 2023
    事件研究日 2022-07-23 2023-07-22 2024-06-24
  • 样本数据整理,市场收益率:上证指数;数据范围:20210101-20240930。

    数据获取:qstock包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas as pd
import qstock as qs
import statsmodels.api as sm
from scipy.stats import ttest_1samp

# 利用qstock包获取数据下载到excel
data = qs.get_data(['京沪高铁','上证指数'], start='20190101', end='20240930', freq='d', fqt=1)
data = data.reset_index()

# 1. 下载并整理数据:计算每日收益率
data['daily_return'] = data['close'].pct_change() # 计算每日收益率
stock_data = data[data['name'] == '京沪高铁'][['date', 'daily_return']].rename(columns={'daily_return': 'stock_return'})
index_data = data[data['name'] == '上证指数'][['date', 'daily_return']].rename(columns={'daily_return': 'index_return'})

# 合并数据并清理空值
merged_data = pd.merge(stock_data, index_data, on='date').dropna()

merged_data

窗口期及估计期

  • 窗口期:短期事件冲击窗口期一般为3天,5天或10天,本案例将事件日前后5个交易日数据作为事件窗口期,即【-5,5】
  • 估计期:短期事件冲击窗口期一般为90天,120天或180天,本案例将窗口期前120个交易日数据作为估计期,即【-125,-6】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 2. 提取建模数据和事件窗口数据
def extract_event_data(event_date, data_frame, estimation_window=120,event_window=5):

try:
event_date = pd.to_datetime(event_date)
data_frame = data_frame.reset_index(drop=True)

# 筛选出早于公告日的数据,找到最接近公告日的日期或公告日(包括当天)
filtered_data = data_frame[data_frame['date'] <= event_date]
event_idx = (filtered_data['date'] - event_date).abs().idxmin()

# 检查公告日是否是交易日,是的话为0,不是的话为1特殊处理边界
is_current_day = event_date not in filtered_data['date'].values

# 计算估计期和事件窗口范围
start_idx = max(0, event_idx - estimation_window - event_window + int(is_current_day))
estimation_end_idx = event_idx - event_window + int(is_current_day)

# 估计期数据
estimation_data = data_frame.iloc[start_idx:estimation_end_idx]

# 事件窗口数据考虑事件日不是交易日
event_window_data = data_frame.iloc[event_idx - event_window + int(is_current_day) : event_idx + event_window + 1]


return estimation_data, event_window_data

except IndexError:

print(f"警告:事件日期 {event_date} 超出数据范围或数据不足。")

return None, None

正常收益率

  • 正常收益率模型:使用估计期数据进行参数估计,拟合得到线性回归模型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 3. 构建OLS回归模型,计算回归参数
    def calculate_regression_coefficients(estimation_data):
    """
    使用估计期数据进行OLS回归,计算回归参数。
    """
    X = sm.add_constant(estimation_data['index_return']) # 添加常数项
    y = estimation_data['stock_return']
    model = sm.OLS(y, X).fit()
    print(model.summary())

    return model.params['const'], model.params['index_return']

异常收益率&累积异常收益率

1
2
3
4
5
6
7
8
9
10
11
12
# 4. 计算累积异常收益率(CAR)
def calculate_CAR(event_data, alpha, beta, event_date):
"""
使用事件窗口数据和回归参数计算累积异常收益率(CAR)。
"""
event_data = event_data.copy() # 避免修改原始数据
event_data['predicted_return'] = event_data['index_return'] * beta + alpha # 预测收益率
event_data['abnormal_return'] = event_data['stock_return'] - event_data['predicted_return'] # 异常收益率
event_data['cumulative_abnormal_return'] = event_data['abnormal_return'].cumsum() # 累积异常收益率
event_data['event_date'] = event_date

return event_data

整合版Python代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import pandas as pd
import qstock as qs
import statsmodels.api as sm
from scipy.stats import ttest_1samp

def main():

# 获取并整理数据
stock = ['京沪高铁','上证指数']
merged_data = get_Data(stock)
print(merged_data)

all_results = pd.DataFrame()
t_test_res = pd.DataFrame()

event_dates = ['2022-07-23', '2023-07-22', '2024-06-24']

for event_date in event_dates:

# 获取估计期及窗口期数据
estimation_data, event_data = extract_event_data(event_date, merged_data)

# 正常收益模型
alpha, beta = calculate_regression_coefficients(estimation_data)

# 异常收益率及累积异常收益率
car_result = calculate_CAR(event_data, alpha, beta, event_date)
all_results = pd.concat([all_results,car_result],ignore_index=True)

# 显著性检验
t_test = single_sample_test(car_result, event_date)
t_test_res = pd.concat([t_test_res,t_test],ignore_index=True)


# 将结果写入excel
all_results.to_excel('异常收益率&累积异常收益率.xlsx',index=False)
t_test_res.to_excel('显著性检验.xlsx',index=False)


# 1.利用qstock包获取数据下载到excel

def get_Data(stock):

data = qs.get_data(stock, start='20210101', end='20240930', freq='d', fqt=1)
data = data.reset_index()

# 1. 下载并整理数据:计算每日收益率
data['daily_return'] = data['close'].pct_change() # 计算每日收益率
stock_data = data[data['name'] == stock[0]][['date', 'daily_return']].rename(columns={'daily_return': 'stock_return'})
index_data = data[data['name'] == stock[1]][['date', 'daily_return']].rename(columns={'daily_return': 'index_return'})

# 合并数据并清理空值
merged_data = pd.merge(stock_data, index_data, on='date').dropna()

return merged_data


# 2. 提取建模数据和事件窗口数据
def extract_event_data(event_date, data_frame, estimation_window=120,event_window=5):

try:
event_date = pd.to_datetime(event_date)
data_frame = data_frame.reset_index(drop=True)

# 筛选出早于公告日的数据,找到最接近公告日的日期或公告日(包括当天)
filtered_data = data_frame[data_frame['date'] <= event_date]
event_idx = (filtered_data['date'] - event_date).abs().idxmin()

# 如果目标日期是交易日,即目标日期在日期中
# 检查公告日是不是交易日,是的话为true,即数字1,不是的话为0
is_current_day = event_date not in filtered_data['date'].values

# 计算估计期和事件窗口范围
start_idx = max(0, event_idx - estimation_window - event_window + int(is_current_day))
estimation_end_idx = event_idx - event_window + int(is_current_day)

# 估计期数据
estimation_data = data_frame.iloc[start_idx:estimation_end_idx]

# 事件窗口数据考虑事件日不是交易日
event_window_data = data_frame.iloc[event_idx - event_window + int(is_current_day) : event_idx + event_window + 1]


return estimation_data, event_window_data

except IndexError:

print(f"警告:事件日期 {event_date} 超出数据范围或数据不足。")

return None, None


# 3. 构建OLS回归模型,计算回归参数
def calculate_regression_coefficients(estimation_data):
"""
使用估计期数据进行OLS回归,计算回归参数。
"""
X = sm.add_constant(estimation_data['index_return']) # 添加常数项
y = estimation_data['stock_return']
model = sm.OLS(y, X).fit()

return model.params['const'], model.params['index_return']


# 4. 计算累积异常收益率(CAR)
def calculate_CAR(event_data, alpha, beta, event_date):
"""
使用事件窗口数据和回归参数计算累积异常收益率(CAR)。
"""
event_data = event_data.copy() # 避免修改原始数据
event_data['predicted_return'] = event_data['index_return'] * beta + alpha # 预测收益率
event_data['abnormal_return'] = event_data['stock_return'] - event_data['predicted_return'] # 异常收益率
event_data['cumulative_abnormal_return'] = event_data['abnormal_return'].cumsum() # 累积异常收益率
event_data['event_date'] = event_date

return event_data


# 5.累积超额收益率的单样本检验,检验其显著性是否为0
def single_sample_test(car_data, event_date):

"""
对每个事件窗口的累积异常收益率(CAR)进行单样本 t 检验。
"""
event_car = car_data[car_data['event_date'] == event_date]['cumulative_abnormal_return']

# 单样本 t 检验,假设均值为 0
t_stat, p_value = ttest_1samp(event_car, popmean=0)

# 保存结果
res = {
'event_date': event_date,
'mean_CAR': event_car.mean(),
't_stat': t_stat,
'p_value': p_value,
'significant': p_value < 0.05 # 是否显著
}

return pd.DataFrame([res])


if __name__ == '__main__':
main()
  • OLS模型的R方为0.3左右,模型解释度偏低。【可考虑正常收益的预测用精确率更高的模型替换】
  • 显著性检验结果解读
    • 3次事件中,2022年的股利分配事件5%水平不显著,其余两次均在5%水平显著。
    • 2023年的mean_CAR为3.16%,说明股利政策公布对京沪高铁股票价格产生正向影响。这点是比较符合实际的,因为2023年京沪高铁分红10股派1.116元,分红较前几年有质的提升,以及前几年因为疫情,股价比较低迷,公告之后存在正向的累积超额收益率感觉正常。