Betalens新手系列 · 18 事件研究 EventStudy:单标的、多标的、超额、固定持有期
阅读提示
这篇讲解 EventStudy 模块——如何分析特定事件(如年报披露、并购公告、指数调入)前后,股票的超额收益如何。学完之后,你能独立完成一次完整的事件研究。
导言
事件研究(Event Study)是量化金融里最经典的分析方法之一。它的核心问题是:某个事件(如年报披露、并购公告、分红)发生后,股票有没有显著的超额收益?
Betalens 的 EventStudy 模块提供了灵活的事件研究能力:
- 单标的:看某只股票在某个事件前后的收益曲线。
- 多标的平均:把一批股票的事件窗口收益取平均,得出”事件平均效应”。
- 基准超额:用沪深 300 等基准的同期收益做对照,得到超额收益。
- 固定持有期:指定持有期(如事件后持有 20 天),直接算总收益。
基本用法
1 2 3 4 5
| from betalens.datafeed import Datafeed from betalens.eventstudy import EventStudy
es = EventStudy(Datafeed("daily_market"))
|
场景 1:单标的事件分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| events = pd.DataFrame({ "code": ["000001.SZ"] * 3, "event_date": ["2022-04-29", "2023-04-28", "2024-04-26"], })
result = es.analyze( events, code="000001.SZ", window_before=5, window_after=10, metric="收盘价(元)", mode="flexible", benchmark_code=None, )
print(result.summary)
|
mode 参数:
场景 2:多标的平均事件效应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| events_batch = pd.DataFrame({ "code": ["000001.SZ", "000002.SZ", "000004.SZ"], "event_date": ["2023-04-28", "2023-04-28", "2023-04-28"], "event_type": ["年报披露", "年报披露", "年报披露"], })
result = es.analyze( events_batch, code="000001.SZ", window_before=5, window_after=20, metric="收盘价(元)", mode="flexible", benchmark_code=None, )
print(result.average_cumulative_return)
|
场景 3:超额收益分析
1 2 3 4 5 6 7 8 9 10
| result = es.analyze( events, code="000001.SZ", window_before=5, window_after=20, metric="收盘价(元)", mode="benchmark_excess", benchmark_code="000300.SH", )
|
返回结果包含:
- 事件窗口收益:每天的收益率。
- 累计超额收益(CAR):窗口内每一天的累计超额收益。
- 显著性检验:t 统计量和 p 值。
场景 4:固定持有期
1 2 3 4 5 6 7 8 9 10 11 12
| result = es.analyze( events, code="000001.SZ", window_before=0, window_after=20, metric="收盘价(元)", mode="fixed_holding", benchmark_code="000300.SH", ) print(result.holding_period_return) print(result.excess_return)
|
开发者侧:EventStudy 的内部逻辑
从源码角度,EventStudy.analyze 的核心流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| def analyze(events, code, window_before, window_after, ...): results = [] for each event: window_dates = get_trade_days_in_window(event_date, window_before, window_after)
prices = data.query_time_range(codes=[code], dates=window_dates, ...)
returns = prices.pct_change().dropna()
if benchmark_code: benchmark_returns = get_benchmark_returns(window_dates, benchmark_code) excess_returns = returns - benchmark_returns car = excess_returns.cumsum()
results.append({"returns": returns, "car": car, ...})
return EventStudyResult(average_car=mean(results), ...)
|
常见错误
1. 事件日期不是交易日
1 2 3
|
events["event_date"] = pd.bdate_range(events["event_date"], events["event_date"])
|
2. 事件窗口内停牌
如果事件窗口内股票停牌,收益率会是 0 或 NaN。需要在分析前检查停牌情况:
1 2 3 4
| 停牌日 = trade_status_matrix[trade_status_matrix[code] == 0].index if len(停牌日.intersection(window_dates)) > len(window_dates) * 0.3: print("警告:事件窗口内停牌比例超过 30%,结果可能不可靠")
|
延伸阅读