这篇博客我们会完成事件研究的核心部分,包括正常收益率估计、异常收益率计算、累计异常收益率计算和显著性检验,视频讲解在 b 站。
先回顾一下事件研究法的核心步骤,包括: - 定义事件与事件窗口 - 研究样本选择与数据准备 - 估计正常收益率与计算异常收益率 - 累计异常收益率计算与显著性检验
import datetime
import logging
import pandas as pd
import numpy as np
import statsmodels.api as sm
import tushare as ts
- 日个股回报率:国泰安数据库(Tushare、Baostock)
- 3 因子数据:国泰安数据库
- 无风险利率:3 个月定期存款利率
data_2013_2017 = pd.read_csv(
"./data/日个股回报率文件2013_2017/TRD_Dalyr.csv", dtype={"Stkcd": str}
)
data_2013_2017_1 = pd.read_csv(
"./data/日个股回报率文件2013_2017/TRD_Dalyr1.csv", dtype={"Stkcd": str}
)
data_2013_2017_2 = pd.read_csv(
"./data/日个股回报率文件2013_2017/TRD_Dalyr2.csv", dtype={"Stkcd": str}
)
data_2013_2017_3 = pd.read_csv(
"./data/日个股回报率文件2013_2017/TRD_Dalyr3.csv", dtype={"Stkcd": str}
)
data_2018_2022 = pd.read_csv(
"./data/日个股回报率文件2018_2022/TRD_Dalyr.csv", dtype={"Stkcd": str}
)
data_2018_2022_1 = pd.read_csv(
"./data/日个股回报率文件2018_2022/TRD_Dalyr1.csv", dtype={"Stkcd": str}
)
data_2018_2022_2 = pd.read_csv(
"./data/日个股回报率文件2018_2022/TRD_Dalyr2.csv", dtype={"Stkcd": str}
)
data_2018_2022_3 = pd.read_csv(
"./data/日个股回报率文件2018_2022/TRD_Dalyr3.csv", dtype={"Stkcd": str}
)
# data:按流通市值加权的A股日收益率数据
data = pd.concat(
[
data_2013_2017,
data_2013_2017_1,
data_2013_2017_2,
data_2013_2017_3,
data_2018_2022,
data_2018_2022_1,
data_2018_2022_2,
data_2018_2022_3,
]
)
| 0 |
000001 |
2013-01-04 |
-0.001873 |
| 1 |
000001 |
2013-01-07 |
0.019387 |
| 2 |
000001 |
2013-01-08 |
-0.018405 |
| 3 |
000001 |
2013-01-09 |
-0.008750 |
| 4 |
000001 |
2013-01-10 |
0.000631 |
data = data.rename(columns={"Trddt": "TradingDate"}) # 更改列名,方便后续做比较合并
# 整理 3因子数据
ff_factor = pd.read_csv(
"./data/3factor.csv", skiprows=[1, 2], converters={"TradingDate": pd.to_datetime}
)
| 0 |
P9706 |
1990-12-19 |
2.473374 |
NaN |
NaN |
| 1 |
P9710 |
1990-12-19 |
2.473374 |
NaN |
NaN |
| 2 |
P9709 |
1990-12-19 |
2.473374 |
NaN |
NaN |
| 3 |
P9712 |
1990-12-19 |
2.473374 |
NaN |
NaN |
| 4 |
P9713 |
1990-12-19 |
2.473374 |
NaN |
NaN |
# 无风险利率数据
risk_free_rate = pd.read_excel(
"./data/定期存款利率_3个月.xlsx", dtype={"指标名称": datetime.date}
)
| 0 |
1989-02-28 |
7.56 |
| 1 |
1989-03-31 |
7.56 |
| 2 |
1989-04-30 |
7.56 |
| 3 |
1989-05-31 |
7.56 |
| 4 |
1989-06-30 |
7.56 |
r_dict = {}
for t, r in zip(risk_free_rate.iloc[:, 0], risk_free_rate.iloc[:, 1]):
t_temp = t + datetime.timedelta(
days=10
) # 月底公布下月三月定期存款利率,+10天将日期对应到下月
r_dict[str(t_temp.year) + str(t_temp.month)] = r / 100
r_list = []
for t in ff_factor.iloc[:, 1]:
r = r_dict[str(t.year) + str(t.month)]
r_list.append(r)
ff_factor[["r"]] = np.array(r_list).reshape(
-1, 1
) # 将无风险利率添加到 ff 三因子表格内,方便后续回归拟合|
| 0 |
P9706 |
1990-12-19 |
2.473374 |
NaN |
NaN |
0.0432 |
| 1 |
P9710 |
1990-12-19 |
2.473374 |
NaN |
NaN |
0.0432 |
| 2 |
P9709 |
1990-12-19 |
2.473374 |
NaN |
NaN |
0.0432 |
| 3 |
P9712 |
1990-12-19 |
2.473374 |
NaN |
NaN |
0.0432 |
| 4 |
P9713 |
1990-12-19 |
2.473374 |
NaN |
NaN |
0.0432 |
ff_factor["TradingDate"] = ff_factor["TradingDate"].astype(
str
) # 将时间列转化为字符串类型,方便后续做判断合并
sample = pd.read_csv("./data/final.csv", dtype={"secCode": str})
sample.head()
| 0 |
002152 |
广电运通 |
9900003423 |
广电运通:2016年4月1日投资者关系活动记录表 |
2016-04-06 |
http://static.cninfo.com.cn/finalpage/2016-04-... |
9、公司提过有储备区块链技术,是否可以介绍? |
| 1 |
002177 |
御银股份 |
9900003781 |
御银股份:2016年4月21日投资者关系活动记录表 |
2016-04-21 |
http://static.cninfo.com.cn/finalpage/2016-04-... |
4、公司在区块链技术上未来有什么规划? |
| 2 |
300386 |
飞天诚信 |
9900023058 |
飞天诚信:2016年4月22日投资者关系活动记录表 |
2016-04-25 |
http://static.cninfo.com.cn/finalpage/2016-04-... |
公司在区块链部分如何布局? |
| 3 |
300423 |
鲁亿通 |
9900028812 |
鲁亿通:关于本次交易前12个月内购买、出售资产的说明 |
2016-06-09 |
http://static.cninfo.com.cn/finalpage/2016-06-... |
同时,上市公司拟采用定价发行的方式向纪法清、孔剑平、孙奇锋、王麒诚和中信建投-数贝泽华人工智... |
| 4 |
000961 |
中南建设 |
gssz0000961 |
中南建设:关于本公司投资上海承泰信息科技股份有限公司的进展公告 |
2016-06-23 |
http://static.cninfo.com.cn/finalpage/2016-06-... |
,以区块链技术处理玛娜花园不同版本的个人数据,形成对数据信息的安全保护。 |
pro = ts.pro_api()
# 查询当前所有正常上市交易和退市的股票列表
stocks_L = pro.stock_basic(
exchange="", list_status="L", fields="symbol,name,area,industry,list_date,market"
)
stocks_D = pro.stock_basic(
exchange="", list_status="D", fields="symbol,name,area,industry,list_date,market"
)
| 0 |
000001 |
平安银行 |
深圳 |
银行 |
主板 |
19910403 |
| 1 |
000002 |
万科A |
深圳 |
全国地产 |
主板 |
19910129 |
| 2 |
000004 |
ST国华 |
深圳 |
软件服务 |
主板 |
19910114 |
| 3 |
000005 |
ST星源 |
深圳 |
环境保护 |
主板 |
19901210 |
| 4 |
000006 |
深振业A |
深圳 |
区域地产 |
主板 |
19920427 |
| 0 |
000003 |
PT金田A(退) |
None |
None |
主板 |
19910703 |
| 1 |
000013 |
*ST石化A(退) |
None |
None |
主板 |
19920506 |
| 2 |
000015 |
PT中浩A(退) |
None |
None |
主板 |
19920625 |
| 3 |
000018 |
神城A退(退) |
None |
None |
主板 |
19920616 |
| 4 |
000024 |
招商地产(退) |
None |
None |
主板 |
19930607 |
stocks = pd.concat([stocks_L, stocks_D])
# 构造一个 code:market 字典
cm_dict = {}
for c, m in zip(stocks.loc[:, "symbol"], stocks.loc[:, "market"]):
cm_dict[c] = m
market_l = []
for code in sample.loc[:, "secCode"]:
m = cm_dict[code]
market_l.append(m)
sample[["market"]] = np.array(market_l).reshape(-1, 1)
| 0 |
002152 |
广电运通 |
9900003423 |
广电运通:2016年4月1日投资者关系活动记录表 |
2016-04-06 |
http://static.cninfo.com.cn/finalpage/2016-04-... |
9、公司提过有储备区块链技术,是否可以介绍? |
中小板 |
| 1 |
002177 |
御银股份 |
9900003781 |
御银股份:2016年4月21日投资者关系活动记录表 |
2016-04-21 |
http://static.cninfo.com.cn/finalpage/2016-04-... |
4、公司在区块链技术上未来有什么规划? |
中小板 |
| 2 |
300386 |
飞天诚信 |
9900023058 |
飞天诚信:2016年4月22日投资者关系活动记录表 |
2016-04-25 |
http://static.cninfo.com.cn/finalpage/2016-04-... |
公司在区块链部分如何布局? |
创业板 |
| 3 |
300423 |
鲁亿通 |
9900028812 |
鲁亿通:关于本次交易前12个月内购买、出售资产的说明 |
2016-06-09 |
http://static.cninfo.com.cn/finalpage/2016-06-... |
同时,上市公司拟采用定价发行的方式向纪法清、孔剑平、孙奇锋、王麒诚和中信建投-数贝泽华人工智... |
创业板 |
| 4 |
000961 |
中南建设 |
gssz0000961 |
中南建设:关于本公司投资上海承泰信息科技股份有限公司的进展公告 |
2016-06-23 |
http://static.cninfo.com.cn/finalpage/2016-06-... |
,以区块链技术处理玛娜花园不同版本的个人数据,形成对数据信息的安全保护。 |
主板 |
# MarkettypeID [股票市场类型编码] - P9705:创业板;P9706:综合A股市场(不包含科创板、创业板);
# P9707:综合B股市场;P9709:综合A股和创业板; P9710:综合AB股和创业板;P9711:科创板;
# 构造一个 market:markettypeID 字典
mm_dict = {"中小板": "P9706", "主板": "P9706", "创业板": "P9705", "科创板": "P9711"}
markettypeID = []
for m in sample.loc[:, "market"]:
m_id = mm_dict[m]
markettypeID.append(m_id)
sample[["markettypeID"]] = np.array(markettypeID).reshape(-1, 1)
| 0 |
002152 |
广电运通 |
9900003423 |
广电运通:2016年4月1日投资者关系活动记录表 |
2016-04-06 |
http://static.cninfo.com.cn/finalpage/2016-04-... |
9、公司提过有储备区块链技术,是否可以介绍? |
中小板 |
P9706 |
| 1 |
002177 |
御银股份 |
9900003781 |
御银股份:2016年4月21日投资者关系活动记录表 |
2016-04-21 |
http://static.cninfo.com.cn/finalpage/2016-04-... |
4、公司在区块链技术上未来有什么规划? |
中小板 |
P9706 |
| 2 |
300386 |
飞天诚信 |
9900023058 |
飞天诚信:2016年4月22日投资者关系活动记录表 |
2016-04-25 |
http://static.cninfo.com.cn/finalpage/2016-04-... |
公司在区块链部分如何布局? |
创业板 |
P9705 |
| 3 |
300423 |
鲁亿通 |
9900028812 |
鲁亿通:关于本次交易前12个月内购买、出售资产的说明 |
2016-06-09 |
http://static.cninfo.com.cn/finalpage/2016-06-... |
同时,上市公司拟采用定价发行的方式向纪法清、孔剑平、孙奇锋、王麒诚和中信建投-数贝泽华人工智... |
创业板 |
P9705 |
| 4 |
000961 |
中南建设 |
gssz0000961 |
中南建设:关于本公司投资上海承泰信息科技股份有限公司的进展公告 |
2016-06-23 |
http://static.cninfo.com.cn/finalpage/2016-06-... |
,以区块链技术处理玛娜花园不同版本的个人数据,形成对数据信息的安全保护。 |
主板 |
P9706 |
| ... |
... |
... |
... |
... |
... |
... |
... |
... |
... |
| 325 |
300133 |
华策影视 |
9900013428 |
华策影视:关于全资子公司增资扩股暨关联交易的公告 |
2021-12-17 |
http://static.cninfo.com.cn/finalpage/2021-12-... |
影业上海基于节目库搭建的正版影视素材库“华策新视界”已进入正式运营,目前正在推进基于区块链技... |
创业板 |
P9705 |
| 326 |
300235 |
方直科技 |
9900011849 |
方直科技:关于对深圳证券交易所关注函的回复公告 |
2021-12-20 |
http://static.cninfo.com.cn/finalpage/2021-12-... |
元宇宙是整合多种新技术而产生的新型虚实相融的互联网应用和社会形态,它基于扩展现实技术提供沉浸... |
创业板 |
P9705 |
| 327 |
300649 |
杭州园林 |
9900023811 |
杭州园林:关于收购参股公司部分股权的公告 |
2021-12-24 |
http://static.cninfo.com.cn/finalpage/2021-12-... |
一般项目:网络技术服务;物联网技术服务;技术服务、技术开发、技术咨询、技术交流、技术转让、技... |
创业板 |
P9705 |
| 328 |
300533 |
冰川网络 |
9900027730 |
冰川网络:关于全资子公司设立三家子公司并完成工商注册登记的公告 |
2021-12-24 |
http://static.cninfo.com.cn/finalpage/2021-12-... |
人工智能通用应用系统;智能控制系统集成;量子计算技术服务;电竞信息科技;云计算装备技术服务;... |
创业板 |
P9705 |
| 329 |
301116 |
益客食品 |
9900028946 |
益客食品:法律意见书及补充法律意见书 |
2021-12-27 |
http://static.cninfo.com.cn/finalpage/2021-12-... |
不含特种设备制造);液气密元件及系统制造;工业自动控制系统装置销售;物料搬运装备销售;非居住... |
创业板 |
P9705 |
330 rows × 9 columns
daily_returns = data.loc[data.iloc[:, 0] == "002152"]
| 676981 |
002152 |
2013-01-04 |
-0.019027 |
| 676982 |
002152 |
2013-01-07 |
-0.008621 |
| 676983 |
002152 |
2013-01-08 |
0.011594 |
| 676984 |
002152 |
2013-01-09 |
0.002865 |
| 676985 |
002152 |
2013-01-10 |
-0.003571 |
| ... |
... |
... |
... |
| 612679 |
002152 |
2022-03-10 |
0.008427 |
| 612680 |
002152 |
2022-03-11 |
0.016713 |
| 612681 |
002152 |
2022-03-14 |
-0.011872 |
| 612682 |
002152 |
2022-03-15 |
-0.046211 |
| 612683 |
002152 |
2022-03-16 |
0.023256 |
2173 rows × 3 columns
def get_window(code, t, m_id):
"""获取事件期和理论收益率估计期
Args:
code (str): stock code
t (str): announcement date
m_id (str): MarkettypeID
Returns:
eventWD (DataFrame): event window
estimationWD (DataFrame): estimation window
"""
# 个股日收益率数据
daily_returns = data.loc[data.iloc[:, 0] == code]
# 交易日数据
trading_dates = daily_returns.loc[:, "TradingDate"]
t_1 = (datetime.date.fromisoformat(t) + datetime.timedelta(days=1)).strftime(
"%Y-%m-%d"
)
t_2 = (datetime.date.fromisoformat(t) + datetime.timedelta(days=2)).strftime(
"%Y-%m-%d"
)
t_3 = (datetime.date.fromisoformat(t) + datetime.timedelta(days=3)).strftime(
"%Y-%m-%d"
)
t_4 = (datetime.date.fromisoformat(t) + datetime.timedelta(days=4)).strftime(
"%Y-%m-%d"
)
t_5 = (datetime.date.fromisoformat(t) + datetime.timedelta(days=5)).strftime(
"%Y-%m-%d"
)
t_6 = (datetime.date.fromisoformat(t) + datetime.timedelta(days=6)).strftime(
"%Y-%m-%d"
)
t_7 = (datetime.date.fromisoformat(t) + datetime.timedelta(days=7)).strftime(
"%Y-%m-%d"
)
# 判断公告日是否为交易日,年假最长 7 天
if t in list(trading_dates):
i = list(trading_dates).index(t)
elif t_1 in list(trading_dates):
i = list(trading_dates).index(t_1)
elif t_2 in list(trading_dates):
i = list(trading_dates).index(t_2)
elif t_3 in list(trading_dates):
i = list(trading_dates).index(t_3)
elif t_4 in list(trading_dates):
i = list(trading_dates).index(t_4)
elif t_5 in list(trading_dates):
i = list(trading_dates).index(t_5)
elif t_6 in list(trading_dates):
i = list(trading_dates).index(t_6)
elif t_7 in list(trading_dates):
i = list(trading_dates).index(t_7)
else:
i = 0
# 估计期为 200 天
# 公司上市时间较短时,估计期数据不够
if i >= 210:
eventWD = daily_returns.iloc[i - 1 : i + 2, :]
estimationWD = daily_returns.iloc[i - 210 : i - 10, :]
eventWD.insert(0, "MarkettypeID", m_id)
estimationWD.insert(0, "MarkettypeID", m_id)
return estimationWD, eventWD
else:
return
def abnormal_return(estimationWD, eventWD, ff_factor):
"""获取异常收益率
Args:
estimationWD (DataFrame): estimation window
eventWD (DataFrame): event window
ff_factor (DataFrame): fama-french factors
Returns:
ar (array): abnormal returns
"""
estimation_data = pd.merge(
estimationWD, ff_factor, how="left", on=["MarkettypeID", "TradingDate"]
)
event_data = pd.merge(
eventWD, ff_factor, how="left", on=["MarkettypeID", "TradingDate"]
)
X = estimation_data.loc[:, ["RiskPremium1", "SMB1", "HML1"]].values
X = sm.add_constant(X)
y = np.array(
estimation_data.loc[:, "Dretwd"] - estimation_data.loc[:, "r"]
).reshape(-1, 1)
model = sm.OLS(y, X)
result = model.fit()
# rr: 理论收益率
rr = np.dot(
sm.add_constant(event_data.loc[:, ["RiskPremium1", "SMB1", "HML1"]].values),
result.params.reshape(-1, 1),
) + event_data.loc[:, "r"].values.reshape(-1, 1)
# ar: 异常收益率,每只股票在事件期的收益率 array
ar = event_data.loc[:, "Dretwd"].values.reshape(-1, 1) - rr
return ar
| 0 |
002152 |
广电运通 |
9900003423 |
广电运通:2016年4月1日投资者关系活动记录表 |
2016-04-06 |
http://static.cninfo.com.cn/finalpage/2016-04-... |
9、公司提过有储备区块链技术,是否可以介绍? |
中小板 |
P9706 |
| 1 |
002177 |
御银股份 |
9900003781 |
御银股份:2016年4月21日投资者关系活动记录表 |
2016-04-21 |
http://static.cninfo.com.cn/finalpage/2016-04-... |
4、公司在区块链技术上未来有什么规划? |
中小板 |
P9706 |
| 2 |
300386 |
飞天诚信 |
9900023058 |
飞天诚信:2016年4月22日投资者关系活动记录表 |
2016-04-25 |
http://static.cninfo.com.cn/finalpage/2016-04-... |
公司在区块链部分如何布局? |
创业板 |
P9705 |
| 3 |
300423 |
鲁亿通 |
9900028812 |
鲁亿通:关于本次交易前12个月内购买、出售资产的说明 |
2016-06-09 |
http://static.cninfo.com.cn/finalpage/2016-06-... |
同时,上市公司拟采用定价发行的方式向纪法清、孔剑平、孙奇锋、王麒诚和中信建投-数贝泽华人工智... |
创业板 |
P9705 |
| 4 |
000961 |
中南建设 |
gssz0000961 |
中南建设:关于本公司投资上海承泰信息科技股份有限公司的进展公告 |
2016-06-23 |
http://static.cninfo.com.cn/finalpage/2016-06-... |
,以区块链技术处理玛娜花园不同版本的个人数据,形成对数据信息的安全保护。 |
主板 |
P9706 |
ar_l = []
delete_code = []
for code, t, m_id in zip(
sample.loc[:, "secCode"],
sample.loc[:, "announcementTime"],
sample.loc[:, "markettypeID"],
):
try:
estimationWD, eventWD = get_window(code, t, m_id)
except:
delete_code.append(code)
continue
ar_array = abnormal_return(estimationWD, eventWD, ff_factor)
ar = sum(ar_array)[0]
ar_l.append(ar)
AR = pd.DataFrame(np.array(ar_l).reshape(-1, 1))
| count |
304.000000 |
| mean |
0.014020 |
| std |
0.068285 |
| min |
-0.173427 |
| 25% |
-0.018811 |
| 50% |
0.003446 |
| 75% |
0.032451 |
| max |
0.337091 |
stats.ttest_1samp(ar_l, 0)
Ttest_1sampResult(statistic=3.579861379325141, pvalue=0.00040021773286985055)
stats.wilcoxon(ar_l, len(ar_l) * [0], correction=True, alternative="two-sided")
WilcoxonResult(statistic=19144.0, pvalue=0.008515412037149648)
['300423',
'603106',
'300612',
'000793',
'601619',
'000038',
'300654',
'300525',
'000061',
'601066',
'300773',
'300788',
'601512',
'688158',
'688051',
'688096',
'300975',
'003040',
'300996',
'300889',
'601686',
'003032',
'688135',
'300949',
'301213',
'301116']