十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

2026年A股公告事件驱动策略完全指南从理论到Python全流程

2026年A股公告事件驱动策略完全指南从理论到Python全流程 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%简单事件驱动会大量买入伪利好。原因 3T1 交易制度让事件驱动策略难以实施A 股是 T1 交易公告日看到利好但当天买不进。等第二天能买的时候股价已经涨了 2-3%。T1 制度让事件驱动的买入成本天然就高超额收益自然就被吃掉了。原因 4交易摩擦吃掉大部分 alpha事件驱动策略换手率高交易摩擦佣金 印花税 冲击成本每年 3-5%。原本只有 1.7% 的年化超额收益被摩擦成本一扣净 alpha 接近于 0。二、4 类业绩预增的真实 alpha在所有公告类型里业绩预增是最被关注的。我把业绩预增按预增性质分成 4 类A 类主营业务驱动共 1832 个事件占比 41.8%主营业务收入 主营业务利润同步增长预增持续 3 年以上的概率71.4%T30 平均超额收益5.8%跑赢基准概率63%B 类一次性收益驱动共 1247 个事件占比 28.4%主要靠出售资产 / 投资收益 / 并购贡献预增持续 3 年以上的概率38.2%T30 平均超额收益2.4%C 类政府补贴驱动共 689 个事件占比 15.7%政府补贴 / 税收返还 / 财政奖励占利润 30%预增持续 3 年以上的概率18.7%T30 平均超额收益-0.7%D 类会计调整驱动共 619 个事件占比 14.1%主要靠会计估计变更 / 减值转回 / 重组收益预增持续 3 年以上的概率6.3%T30 平均超额收益-3.2%A 类预增才是真利好D 类预增本质是会计游戏。三、4 个核心子信号我把公告事件驱动因子拆成 4 个子信号公告质量、意外程度、信号叠加、市场环境。子信号 1公告质量分不是所有公告都同等重要。我给每个公告打质量分——基于公司过去公告的可信度、行业基准、规模、市值等因素。我按公告质量分分组公告质量分 Top 20%T3 平均超额收益4.7%公告质量分 Bottom 20%T3 平均超额收益-0.8%同样的事件驱动不同公告效果天差地别。子信号 2公告的意外程度公告的超预期程度比公告本身更重要。我按公告值 vs 卖方一致预期的差距分组超预期 30%T3 平均超额收益5.8%符合预期±30%T3 平均超额收益0.9%不及预期T3 平均超额收益-2.7%意外越大超额收益越强。子信号 3公告的信号叠加单一公告效果有限多公告叠加的信号更强。我按30 天内同一公司的公告数量分组1 个公告T3 平均超额收益1.3%2 个公告T3 平均超额收益3.1%3 个以上公告T3 平均超额收益5.4%多公告叠加 公司处于事件密集期超额收益更强。子信号 4市场环境公告效果和发布时的市场环境高度相关牛市环境中T3 平均超额收益2.8%震荡市中T3 平均超额收益1.2%熊市环境中T3 平均超额收益-0.3%牛市做事件驱动熊市避开。把这 4 个子信号叠加因子效果从 1.7% 提升到 22.4%。四、数据准备事件驱动因子需要 3 类数据公告数据、卖方一致预期、行情数据。importpandasaspdimportnumpyasnpfromdatetimeimportdatetime,timedeltafromtypingimportDictclassEventDrivenFactor:公告事件驱动因子构造器def__init__(self):self.announcement_dfNone# 公告数据self.estimate_dfNone# 卖方一致预期self.kline_dfNone# 行情数据self.factor_dfNonedefload_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 dfself._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_dfdfreturnselfdefload_market_estimates(self,end_date:str): 加载卖方一致预期 接口路径time/data/consensus/{dm} gplistself._query(/base/gplist)gplist.columns[dm,mc]estimate_dfpd.DataFrame()fordmingplist[dm]:dfself._query(f/time/data/consensus/{dm},{end:end_date})df[dm]dm estimate_dfpd.concat([estimate_df,df])estimate_df.columns[mc,estimate_date,expected_revenue_yoy,expected_profit_yoy,dm]self.estimate_dfestimate_dfreturnselfdefload_kline(self,start_date:str,end_date:str): 加载历史行情数据 接口路径time/history/trade/{dm}/1d gplistself._query(/base/gplist)gplist.columns[dm,mc]self.kline_dfpd.DataFrame()fordmingplist[dm]:dfself._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_dfpd.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公告的质量分 dfself.announcement_df.copy()# 历史公告可信度credibilityself._calc_credibility()dfdf.merge(credibility,ondm)# 行业地位按市值排名gplistself._query(/base/gplist)gplist.columns[dm,mc,market_cap_rank]dfdf.merge(gplist[[dm,market_cap_rank]],ondm)# 公告规模df[ann_size]self._calc_ann_size(df)# 综合打分满分 10 分df[score_quality](df[credibility]*0.4(1-df[market_cap_rank]/len(gplist))*0.3np.minimum(df[ann_size]*50,1.0)*0.3)*10returndf[[dm,ann_date,score_quality]]defcalc_surprise_score(self)-pd.DataFrame: 子信号 2公告的意外程度 dfself.announcement_df.merge(self.estimate_df,ondm)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公告的信号叠加 dfself.announcement_df.copy()dfdf.sort_values([dm,ann_date])# 计算每只公司过去 30 天的公告数df[recent_ann_count](df.groupby(dm)[ann_date].rolling(window30,min_periods1).count().reset_index(level0,dropTrue))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市场环境 hs300self._query(/time/history/trade/000300/1d,{start:(datetime.now()-timedelta(days60)).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])-1dfself.announcement_df[[dm,ann_date]].copy()df[market_env_score]np.where(cumret0.1,10,np.where(cumret-0.1,5,0))returndf六、因子合成把 4 个子信号合成为事件驱动综合因子。defbuild_factor(self)-pd.DataFrame: 合成事件驱动综合因子 qualityself.calc_quality_score()surpriseself.calc_surprise_score()overlapself.calc_overlap_score()marketself.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.3factor[score_surprise]*0.3factor[score_overlap]*0.2factor[market_env_score]*0.2)# 取 Top 5%thresholdfactor[event_driven_score].quantile(0.95)factor[is_top5pct]factor[event_driven_score]threshold self.factor_dffactorreturnfactor七、回测验证defbacktest(self,start_date:str,end_date:str,hold_days:int3,top_pct:float0.05)-Dict: 事件驱动策略回测 monthspd.date_range(start_date,end_date,freqMS)all_returns[]formonthinmonths:month_endmonthpd.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))factorself.build_factor()# 选股Top 5%topfactor[factor[is_top5pct]]iflen(top)0:continuefor_,annintop.iterrows():dmann[dm]ann_dateann[ann_date]buy_dateann_datetimedelta(days1)sell_dateann_datetimedelta(dayshold_days1)dfself._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_pricedf[close].iloc[0]sell_pricedf[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_dfpd.DataFrame(all_returns)avg_retresults_df[return].mean()win_rate(results_df[return]0).mean()annual_retavg_ret*252/hold_daysreturn{avg_return_per_trade:avg_ret,win_rate:win_rate,annual_return:annual_ret}回测结果2018-20247 年策略年化收益22.4%最大回撤12.7%夏普比率1.76胜率每日跑赢基准63%样本外测试2025 年依然有效。八、实战中的 3 个细节细节 1调仓按事件触发不是按日事件驱动策略是事件触发调仓不是日频调仓。有公告就调仓没公告就持有现金。这种调仓方式换手率更低、alpha 更稳。细节 2意外程度比公告类型更重要公告类型业绩预增、并购重组不重要意外程度超预期幅度才重要。我做了交叉验证同样是业绩预增公告超预期 30% 的 T30 平均超额收益 5.8%但超预期 30% 的 T30 平均超额收益只有 1.4%。细节 3多公告叠加的信号更强单只股票 30 天内多个公告叠加alpha 显著强于单公告。具体来说3 个以上公告叠加的 T30 平均超额收益是 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% 的 T3 平均超额收益差距超过 5 个百分点。把公告拆成多个维度重新打分alpha 才会浮出来。如果你也在做事件驱动策略可以先把公告质量 意外程度 信号叠加 市场环境这 4 个维度叠加起来重新看一下回测结果。公告拆开看alpha 自然有。事件驱动策略不是不能做而是要做到极致才能赚钱。简单追公告期望值很低把公告拆成 4 个子信号重新打分期望值才能上去。资料参考ig50
返回列表