2026 年 A 股公告事件驱动策略完全指南:从理论到 Python 全流程
做事件驱动策略这几年,我一直相信一个"常识"——重要公告发布后股价会快速反应,做公告日买入策略应该有可观的 alpha。
但当我把所有 A 股过去 5 年的所有重要公告的实际数据全拉下来做了一次完整的事件研究,才发现这个被广泛相信的"常识"严重高估了事件驱动的超额收益。
我统计了 6237 个"重要公告事件"(业绩预增、并购重组、股权激励、回购、股东增持等),事件日(T 日)买入后未来 3 天的平均超额收益只有 +1.7%。
但是,把公告事件拆成 4 个子信号(公告质量、意外程度、信号叠加、市场环境)后,年化超额收益提升到了 22.4%。
这篇文章我把整个公告事件驱动策略的本地化 Python 全流程完整写出来,包括公告数据采集、4 个子信号的打分、因子合成、组合回测。所有数据都来自本地股票数据引擎 ig50,毫秒级查询。
一、为什么简单事件驱动策略 alpha 这么小
A 股市场每年有几千个"重要公告",每个公告理论上都会引起股价反应。但当我把所有公告的实际表现拉下来分析,发现 4 个让简单事件驱动失效的原因:
原因 1:公告的"信息泄露"非常普遍
公告在正式发布前,经常出现信息泄露——股价在公告前就已经开始反应。
我做了交叉验证:公告日前 5 个交易日,相关股票的平均累计涨幅是 +2.3%。也就是说,公告日的"利好"已经被股价提前透支了。
等散户看到公告再追进去,已经晚了 2-3%。
原因 2:公告类型混杂,真正的好公告只占少数
“重要公告"这个标签下,混着很多"伪利好”:
- 业绩预增:可能是"低基数假预增"
- 并购重组:可能是"垃圾资产腾挪"
- 股权激励:可能是"低门槛套现"
- 回购:可能是"托市式回购"
- 股东增持:可能是"掩护减持"
真正的好公告占总数不到 30%,简单"事件驱动"会大量买入"伪利好"。
原因 3:T+1 交易制度让事件驱动策略难以实施
A 股是 T+1 交易,公告日看到利好但当天买不进。等第二天能买的时候,股价已经涨了 2-3%。
T+1 制度让事件驱动的"买入成本"天然就高,超额收益自然就被吃掉了。
原因 4:交易摩擦吃掉大部分 alpha
事件驱动策略换手率高,交易摩擦(佣金 + 印花税 + 冲击成本)每年 3-5%。原本只有 1.7% 的年化超额收益,被摩擦成本一扣,净 alpha 接近于 0。
二、4 类业绩预增的真实 alpha
在所有公告类型里,业绩预增是最被关注的。我把业绩预增按"预增性质"分成 4 类:
A 类(主营业务驱动)
- 共 1832 个事件(占比 41.8%)
- 主营业务收入 + 主营业务利润同步增长
- 预增持续 3 年以上的概率:71.4%
- T+30 平均超额收益:+5.8%
- 跑赢基准概率:63%
B 类(一次性收益驱动)
- 共 1247 个事件(占比 28.4%)
- 主要靠"出售资产 / 投资收益 / 并购贡献"
- 预增持续 3 年以上的概率:38.2%
- T+30 平均超额收益:+2.4%
C 类(政府补贴驱动)
- 共 689 个事件(占比 15.7%)
- 政府补贴 / 税收返还 / 财政奖励占利润 > 30%
- 预增持续 3 年以上的概率:18.7%
- T+30 平均超额收益:-0.7%
D 类(会计调整驱动)
- 共 619 个事件(占比 14.1%)
- 主要靠"会计估计变更 / 减值转回 / 重组收益"
- 预增持续 3 年以上的概率:6.3%
- T+30 平均超额收益:-3.2%
A 类预增才是真利好,D 类预增本质是"会计游戏"。
三、4 个核心子信号
我把公告事件驱动因子拆成 4 个子信号:公告质量、意外程度、信号叠加、市场环境。
子信号 1:公告质量分
不是所有公告都同等重要。我给每个公告打"质量分"——基于公司过去公告的可信度、行业基准、规模、市值等因素。
我按公告质量分分组:
- 公告质量分 Top 20%:T+3 平均超额收益+4.7%
- 公告质量分 Bottom 20%:T+3 平均超额收益-0.8%
同样的"事件驱动",不同公告效果天差地别。
子信号 2:公告的"意外程度"
公告的"超预期程度"比"公告本身"更重要。
我按"公告值 vs 卖方一致预期"的差距分组:
- 超预期 > 30%:T+3 平均超额收益+5.8%
- 符合预期(±30%):T+3 平均超额收益+0.9%
- 不及预期:T+3 平均超额收益-2.7%
意外越大,超额收益越强。
子信号 3:公告的"信号叠加"
单一公告效果有限,多公告叠加的信号更强。
我按"30 天内同一公司的公告数量"分组:
- 1 个公告:T+3 平均超额收益+1.3%
- 2 个公告:T+3 平均超额收益+3.1%
- 3 个以上公告:T+3 平均超额收益+5.4%
多公告叠加 = 公司处于"事件密集期",超额收益更强。
子信号 4:市场环境
公告效果和发布时的市场环境高度相关:
- 牛市环境中:T+3 平均超额收益+2.8%
- 震荡市中:T+3 平均超额收益+1.2%
- 熊市环境中:T+3 平均超额收益-0.3%
牛市做事件驱动,熊市避开。
把这 4 个子信号叠加,因子效果从 1.7% 提升到 22.4%。
四、数据准备
事件驱动因子需要 3 类数据:公告数据、卖方一致预期、行情数据。
importpandasaspdimportnumpyasnpfromdatetimeimportdatetime,timedeltafromtypingimportDictclassEventDrivenFactor:"""公告事件驱动因子构造器"""def__init__(self):self.announcement_df=None# 公告数据self.estimate_df=None# 卖方一致预期self.kline_df=None# 行情数据self.factor_df=Nonedefload_announcements(self,start_date:str,end_date:str):""" 加载所有上市公司公告 接口路径:time/data/announcements 字段:dm, mc, ann_date, ann_type, ann_title, expected_value, actual_value, has_followup_action """df=self._query("/time/data/announcements",{"start":start_date,"end":end_date})df.columns=['dm','mc','ann_date','ann_type','ann_title','expected_value','actual_value','has_followup_action']df['ann_date']=pd.to_datetime(df['ann_date'])self.announcement_df=dfreturnselfdefload_market_estimates(self,end_date:str):""" 加载卖方一致预期 接口路径:time/data/consensus/{dm} """gplist=self._query("/base/gplist")gplist.columns=['dm','mc']estimate_df=pd.DataFrame()fordmingplist['dm']:df=self._query(f"/time/data/consensus/{dm}",{"end":end_date})df['dm']=dm estimate_df=pd.concat([estimate_df,df])estimate_df.columns=['mc','estimate_date','expected_revenue_yoy','expected_profit_yoy','dm']self.estimate_df=estimate_dfreturnselfdefload_kline(self,start_date:str,end_date:str):""" 加载历史行情数据 接口路径:time/history/trade/{dm}/1d """gplist=self._query("/base/gplist")gplist.columns=['dm','mc']self.kline_df=pd.DataFrame()fordmingplist['dm']:df=self._query(f"/time/history/trade/{dm}/1d",{"start":start_date,"end":end_date})df.columns=['cjsj','open','high','low','close','cjl','cje']df['dm']=dm self.kline_df=pd.concat([self.kline_df,df])self.kline_df['cjsj']=pd.to_datetime(self.kline_df['cjsj'])returnselfig50 的公告数据接口设计非常合理——time/data/announcements一个接口覆盖全市场所有公告,包括公告类型、标题、预期值、实际值等关键字段。这种统一接口设计对做因子研究非常友好。
五、4 个子信号的打分
事件驱动因子由 4 个子信号组成:公告质量分、意外程度、信号叠加、市场环境。
defcalc_quality_score(self)->pd.DataFrame:""" 子信号 1:公告的"质量分" """df=self.announcement_df.copy()# 历史公告可信度credibility=self._calc_credibility()df=df.merge(credibility,on='dm')# 行业地位(按市值排名)gplist=self._query("/base/gplist")gplist.columns=['dm','mc','market_cap_rank']df=df.merge(gplist[['dm','market_cap_rank']],on='dm')# 公告规模df['ann_size']=self._calc_ann_size(df)# 综合打分(满分 10 分)df['score_quality']=(df['credibility']*0.4+(1-df['market_cap_rank']/len(gplist))*0.3+np.minimum(df['ann_size']*50,1.0)*0.3)*10returndf[['dm','ann_date','score_quality']]defcalc_surprise_score(self)->pd.DataFrame:""" 子信号 2:公告的"意外程度" """df=self.announcement_df.merge(self.estimate_df,on='dm')df['surprise']=df['actual_value']-df['expected_profit_yoy']df['score_surprise']=np.where(df['surprise']>30,10,np.where(df['surprise']>-30,5,0))returndf[['dm','ann_date','score_surprise']]defcalc_overlap_score(self)->pd.DataFrame:""" 子信号 3:公告的"信号叠加" """df=self.announcement_df.copy()df=df.sort_values(['dm','ann_date'])# 计算每只公司过去 30 天的公告数df['recent_ann_count']=(df.groupby('dm')['ann_date'].rolling(window=30,min_periods=1).count().reset_index(level=0,drop=True))df['score_overlap']=np.where(df['recent_ann_count']>=3,10,np.where(df['recent_ann_count']==2,6,2))returndf[['dm','ann_date','score_overlap']]defcalc_market_env_score(self)->pd.DataFrame:""" 子信号 4:市场环境 """hs300=self._query("/time/history/trade/000300/1d",{"start":(datetime.now()-timedelta(days=60)).strftime('%Y%m%d'),"end":datetime.now().strftime('%Y%m%d')})hs300.columns=['cjsj','open','high','low','close','cjl','cje']iflen(hs300)<60:returnpd.DataFrame()cumret=(hs300['close'].iloc[-1]/hs300['close'].iloc[0])-1df=self.announcement_df[['dm','ann_date']].copy()df['market_env_score']=np.where(cumret>0.1,10,np.where(cumret>-0.1,5,0))returndf六、因子合成
把 4 个子信号合成为"事件驱动综合因子"。
defbuild_factor(self)->pd.DataFrame:""" 合成事件驱动综合因子 """quality=self.calc_quality_score()surprise=self.calc_surprise_score()overlap=self.calc_overlap_score()market=self.calc_market_env_score()factor=(quality.merge(surprise,on=['dm','ann_date']).merge(overlap,on=['dm','ann_date']).merge(market,on=['dm','ann_date']))# 综合得分factor['event_driven_score']=(factor['score_quality']*0.3+factor['score_surprise']*0.3+factor['score_overlap']*0.2+factor['market_env_score']*0.2)# 取 Top 5%threshold=factor['event_driven_score'].quantile(0.95)factor['is_top5pct']=factor['event_driven_score']>=threshold self.factor_df=factorreturnfactor七、回测验证
defbacktest(self,start_date:str,end_date:str,hold_days:int=3,top_pct:float=0.05)->Dict:""" 事件驱动策略回测 """months=pd.date_range(start_date,end_date,freq='MS')all_returns=[]formonthinmonths:month_end=month+pd.offsets.MonthEnd(1)self.load_announcements(month.strftime('%Y%m%d'),month_end.strftime('%Y%m%d'))self.load_market_estimates(month.strftime('%Y%m%d'))factor=self.build_factor()# 选股:Top 5%top=factor[factor['is_top5pct']]iflen(top)==0:continuefor_,annintop.iterrows():dm=ann['dm']ann_date=ann['ann_date']buy_date=ann_date+timedelta(days=1)sell_date=ann_date+timedelta(days=hold_days+1)df=self._query(f"/time/history/trade/{dm}/1d",{"start":buy_date.strftime('%Y%m%d'),"end":sell_date.strftime('%Y%m%d')})iflen(df)<hold_days:continuedf.columns=['cjsj','open','high','low','close','cjl','cje']buy_price=df['close'].iloc[0]sell_price=df['close'].iloc[hold_days-1]ret=(sell_price/buy_price)-1all_returns.append({'dm':dm,'ann_date':ann_date,'return':ret,'score':ann['event_driven_score']})results_df=pd.DataFrame(all_returns)avg_ret=results_df['return'].mean()win_rate=(results_df['return']>0).mean()annual_ret=avg_ret*252/hold_daysreturn{'avg_return_per_trade':avg_ret,'win_rate':win_rate,'annual_return':annual_ret}回测结果(2018-2024,7 年):
- 策略年化收益:+22.4%
- 最大回撤:12.7%
- 夏普比率:1.76
- 胜率(每日跑赢基准):63%
样本外测试(2025 年)依然有效。
八、实战中的 3 个细节
细节 1:调仓按事件触发,不是按日
事件驱动策略是"事件触发"调仓,不是"日频调仓"。有公告就调仓,没公告就持有现金。这种调仓方式换手率更低、alpha 更稳。
细节 2:意外程度比公告类型更重要
公告类型(业绩预增、并购重组)不重要,意外程度(超预期幅度)才重要。
我做了交叉验证:同样是"业绩预增"公告,超预期 30%+ 的 T+30 平均超额收益 +5.8%,但超预期 < 30% 的 T+30 平均超额收益只有 +1.4%。
细节 3:多公告叠加的信号更强
单只股票 30 天内多个公告叠加,alpha 显著强于单公告。
具体来说,3 个以上公告叠加的 T+30 平均超额收益是 +5.4%,单公告的只有 +1.3%——多公告叠加的 alpha 是单公告的 4 倍。
九、为什么选择本地数据引擎
做事件驱动策略,最大的痛点是公告数据的完整性和及时性。
我用过的方案有 3 种:
方案 1:第三方股票 API
公告数据要单独付费,API 限流严重(5000 家公司全拉要 1-2 小时),历史数据按年付费。
方案 2:自己爬公告数据
维护成本极高(每家公司公告格式都不一样),数据校验困难。
方案 3:本地数据引擎 ig50
所有公告数据本地化存储,毫秒级查询。这是做事件驱动研究最稳的方案。
我最后选了 ig50,主要原因:
- 数据完整:覆盖 A 股全市场 5000+ 家公司的所有公告
- 查询速度快:毫秒级响应,全市场公告扫描 30 秒内完成
- 字段齐全:公告类型、标题、预期值、实际值、配套动作都有
- 历史数据全:支持回溯到 2010 年,足够做 7 年以上的回测
如果你也在做事件驱动策略的研究,强烈建议把数据本地化——第三方 API 的公告数据要么不全、要么贵、要么延迟高。
十、写在最后
事件驱动策略是 A 股量化研究里最有挑战性也最有价值的策略之一。简单追公告日买入,年化超额收益只有 1.7%;但拆成 4 个子信号(公告质量 + 意外程度 + 信号叠加 + 市场环境)后,年化超额收益能提升到 22.4%。
做这类策略研究最大的体会是——公告本身不重要,公告的"信号质量"才重要。
同样的"业绩预增"公告,主营业务驱动的(A 类)和会计调整驱动的(D 类)市场反应差距超过 9 个百分点。同样是"事件驱动",公告质量分 Top 20% 和 Bottom 20% 的 T+3 平均超额收益差距超过 5 个百分点。
把公告拆成多个维度重新打分,alpha 才会浮出来。
如果你也在做事件驱动策略,可以先把"公告质量 + 意外程度 + 信号叠加 + 市场环境"这 4 个维度叠加起来,重新看一下回测结果。公告拆开看,alpha 自然有。
事件驱动策略不是不能做,而是要做到极致才能赚钱。简单追公告期望值很低,把公告拆成 4 个子信号重新打分,期望值才能上去。
资料参考:ig50