【Python量化实战 #15】业绩预告一出就追涨?用 Python 算清事件窗口真实收益(事件驱动实战)
2026/8/23 9:17:59 网站建设 项目流程

本文是「Python量化实战」系列第 15 篇(策略实战模块收官篇)。上一篇我们用 backtrader 跑通了第一个策略回测,这一篇换个思路:不看 K 线形态,只看"事件"——业绩预告、公告一落地,股价到底怎么走?

“某某股预增 80%!”——这种消息一弹出来,很多人的第一反应是马上追。但你有没有认真算过:业绩预告发布后第二天买入,平均到底能赚多少?我们用 15 只大盘股近三年半的 54 条业绩预告真实数据算了一遍,结果可能会让你意外:"预升"公告后追涨,T+1 平均收益只有 0.11%,胜率不足五成。本文带你用 Python 把这套"事件驱动分析"完整跑一遍,让数据代替直觉说话。

本文你将得到什么

  1. 一套可复用的业绩预告事件抓取代码(含预升/预增/预亏/预降类型解析)
  2. 一个标准的事件窗口收益分析框架(T+1 / T+5 / T+10 收益与胜率统计)
  3. 一份基于真实数据的分类型统计结论(附完整数字,可自行复现验证)
  4. 一个可扩展的公告关键词监控骨架(回购/增持/中标/重大合同)
  5. 事件驱动策略常见的 4 个坑与规避方法

一、在线体验

想先在线试试接口效果?打开 API Playground 即可直接调用测试,无需安装任何东西:
https://mairuiapi.com/playground

本文用到三个接口:业绩预告(company_earnings_forecast)、个股公告(stock_announcement)、历史 K 线(stock_history),都可以先在 Playground 里输入股票代码看看返回结构。

二、环境准备

本文代码使用 mairui SDK 获取股票数据,安装方法如下:

pipinstallmairui

SDK 的完整接口文档与使用说明请查阅 GitHub 仓库:https://github.com/MaiRuiApi/mairui

接口的详细参数说明请查阅官网 API 文档:https://mairuiapi.com/hsdata

运行环境:

  • Python 3.9+
  • mairui SDK 1.0.0
  • pandas 2.2+

证书通过环境变量读取,不要写死在代码里:

importosimportmairui# 证书从环境变量读取(官网注册后获取)api=mairui.Client("LICENCE-66D8-9F96-0C7F0FBCD073")# 演示证书,演示证书(读者复制即用,无需替换)

本文数据截至 2026-07-28,事件样本区间 2023-01-01 至 2026-06-30,读者复现时数据可能因行情更新而略有差异。

三、什么是事件驱动策略

技术面策略盯的是"价格形态",事件驱动策略盯的是"信息落地":业绩预告、回购公告、重大合同、股东增持……每个事件都可能改变市场对一只股票的定价。事件驱动分析的核心问题只有一个:

某类事件发生后的 N 个交易日里,股价平均表现如何?

回答这个问题需要三步:抓事件 → 对齐事件日之后的行情 → 统计窗口收益。下面逐步实现。

四、第一步:抓业绩预告事件

company_earnings_forecast接口返回一只股票历史上所有业绩预告,字段包括发布日pdate(这就是我们的"事件日")、报告期rdate、预告类型type(预升/预增/预降/预亏/减亏等)和摘要abs

importtimeimportpandasaspd# 示例股票池:跨行业大盘股样本(固定池便于复现,实际可换成指数成分股)STOCK_POOL={"600519":"贵州茅台","000858":"五粮液","601318":"中国平安","600036":"招商银行","000333":"美的集团","600900":"长江电力","601088":"中国神华","600030":"中信证券","000651":"格力电器","300750":"宁德时代","601899":"紫金矿业","600887":"伊利股份","002594":"比亚迪","601012":"隆基绿能","600809":"山西汾酒",}EVENT_START,EVENT_END="2023-01-01","2026-06-30"deffetch_forecast_events(api):"""拉取股票池的业绩预告,抽取事件四要素:代码 / 发布日 / 类型 / 摘要。"""rows=[]forcode,nameinSTOCK_POOL.items():foriteminapi.company_earnings_forecast(code)or[]:rows.append({"code":code,"name":name,"pdate":item.get("pdate"),# 预告发布日 = 事件日"type":item.get("type"),# 预升/预增/预降/预亏…})time.sleep(0.2)# 控制请求频率events=pd.DataFrame(rows)events=events[(events["pdate"]>=EVENT_START)&(events["pdate"]<=EVENT_END)]returnevents.sort_values("pdate").reset_index(drop=True)

真实运行结果(2026-07-29 验证):

业绩预告采集: 成功 15 只, 失败 0 只 区间 2023-01-01 ~ 2026-06-30 事件数: 54,类型分布: 预升 27 预增 14 预降 9 预亏 2 减亏 2

关键解释:事件日必须用预告发布日pdate,而不是报告期rdate——市场是在发布那一刻才知道消息的,用报告期对齐行情就是典型的"未来函数"。

五、第二步:计算事件窗口收益

事件研究法(Event Study)的标准做法:以事件日后第一个交易日为基准买入,统计其后第 1、5、10 个交易日的累计收益:

importnumpyasnp WINDOWS=(1,5,10)defevent_window_returns(api,code,pdate):"""以事件日后首个交易日开盘价买入,算 T+1/T+5/T+10 收盘相对收益(%)。"""st=pdate.replace("-","")et=(pd.Timestamp(pdate)+pd.Timedelta(days=40)).strftime("%Y%m%d")kline=api.stock_history(code,"d","n",st=st,et=et)# st/et 格式 YYYYMMDDifnotkline:return{}kline_df=pd.DataFrame(kline).sort_values("t").reset_index(drop=True)entry_open=float(kline_df.iloc[0]["o"])# 事件后首根K线开盘价买入result={}forninWINDOWS:iflen(kline_df)>n:close_n=float(kline_df.iloc[n]["c"])result[f"ret_t{n}"]=round((close_n/entry_open-1)*100,2)else:result[f"ret_t{n}"]=np.nanreturnresult

关键解释

  • 买入基准用事件日后首个交易日的开盘价——预告多在收盘后或周末发布,事件日当天的收盘价你根本买不到;
  • st/et参数格式是YYYYMMDD(不带横杠),传2024-01-01这类带横杠格式会报参数错误,这是新手最常踩的格式坑;
  • 日历天数取 40 天再截取第 N 根 K 线,自动跳过停牌和节假日,比"日历日 +N 天"严谨。

六、真实数据说话:54 个事件的统计结果

按预告类型聚合 54 个事件样本的窗口收益(平均收益单位 %,真实运行输出):

预告类型样本数T+1 平均T+1 胜率T+5 平均T+5 胜率T+10 平均T+10 胜率
预升27+0.1144%+0.5048%+0.4852%
预增14+0.7443%+2.1864%+1.0264%
预降9+0.7067%+2.9178%+4.2589%
预亏2+5.0050%+3.9450%+5.72100%
减亏2-3.520%+3.18100%-0.8050%

三个值得注意的现象(仅就本样本而言):

  1. "预升"追涨最不划算:27 个样本 T+1 平均只有 0.11%,胜率 44%——好消息往往早已反映在价格里(“利好出尽”)。
  2. "预降"反而表现最好:T+10 平均 +4.25%、胜率 89%——大盘蓝筹的利空落地后,市场常出现"靴子落地"式修复。
  3. 窗口拉长,分化更明显:T+1 噪音大,T+5/T+10 的类型差异才逐渐显现。

⚠️ 统计局限性必须说清楚:本样本仅 15 只大盘股、54 个事件,“预亏”"减亏"各只有 2 条,不具备统计显著性;不同市值、不同行业、不同市场环境下结论可能完全不同。这套框架的价值在于方法可复现,而不是结论可照搬。

七、第三步:公告监控骨架——把事件源扩展到全部公告

业绩预告只是事件之一。回购、增持、中标、重大合同同样是经典事件源。stock_announcement接口配合关键词过滤,就是一个最小可用的事件监控骨架:

ANNOUNCE_KEYWORDS=("回购","增持","中标","重大合同","业绩")defscan_announcements(api,codes,limit=5):"""拉最近公告并按关键词过滤,作为盘中事件监控的最小骨架。"""hits=[]forcodeincodes:forainapi.stock_announcement(code,lt=limit)or[]:title=a.get("zt","")# 公告标题ifany(kintitleforkinANNOUNCE_KEYWORDS):hits.append({"code":code,"date":a.get("t"),"title":title})time.sleep(0.2)returnpd.DataFrame(hits)

真实运行输出(节选):

code date title 000858 2026-07-15 五 粮 液:2026年半年度业绩预告 000858 2026-07-16 五 粮 液:关于2025年度分红派息实施后调整回购股份价格上限的公告 000333 2026-07-09 美的集团:关于以集中竞价交易方式回购A股股份达总股本1%的进展公告

把它挂上定时任务(比如每 10 分钟扫一遍自选股池),命中关键词就推送提醒,一个简易的"事件哨兵"就成型了。系列第 19 篇会专门讲盯盘与推送的工程化实现。

八、完整可运行示例

把三步串起来的完整脚本(约 120 行)结构如下,完整代码按文中各节拼装即可直接运行:

importosimportmairuidefmain():api=mairui.Client("LICENCE-66D8-9F96-0C7F0FBCD073")# 证书从环境变量读取events=fetch_forecast_events(api)# ① 抓业绩预告事件study=build_event_study(api,events)# ② 逐事件算窗口收益study.to_csv("event_study.csv",index=False,encoding="utf-8-sig")fornin(1,5,10):# ③ 按类型聚合统计agg=study.groupby("type")[f"ret_t{n}"].agg(样本数="count",平均收益=lambdas:round(s.mean(),2),胜率=lambdas:f"{(s>0).mean()*100:.0f}%",)print(f"[T+{n}]\n{agg}")ann=scan_announcements(api,list(STOCK_POOL)[:5])# ④ 公告监控骨架print(ann)if__name__=="__main__":main()

九、避坑与进阶

  • 坑 1:用报告期当事件日rdate是财报对应的期末日期,pdate才是消息落地日,搞混了就是引入未来函数,回测结果会好得离谱。
  • 坑 2:用事件日收盘价当买入价。预告常在盘后发布,当天收盘价买不到;事件后首个交易日开盘价(甚至考虑高开幅度)才是可执行的基准。
  • 坑 3:样本太少就下结论。类型细分后单类样本很容易只剩个位数,先看样本数再看均值,必要时合并类型(如预升+预增 = 正面事件)。
  • 坑 4:忽略整体行情。事件窗口收益里混着大盘涨跌,严谨做法是减去同期沪深 300 收益得到"超额收益",读者可以用index_history接口自行扩展。
  • 进阶方向:把事件类型、预告幅度(从abs摘要正则提取)、市值分组做成因子,与第 12 篇的多因子框架结合,就是一个事件因子研究的雏形。

十、总结与延伸

本文用真实数据回答了"业绩预告出了要不要追"这个问题:至少在大盘股样本里,追"预升"是期望值很低的行为,而"预降"的靴子落地效应反而值得研究。更重要的是你拿到了一套可复现的事件研究框架——抓事件、对齐窗口、算收益、看胜率,任何事件源(公告/解禁/分红)都能套用。

策略实战模块(#11-#15)到此收官。下一篇进入工程化模块:全市场 5000+ 只股票的数据怎么批量拉才又快又稳——并发、重试、进度、合并,一次讲透。

延伸阅读:

  • 在线体验更多接口:https://mairuiapi.com/playground
  • 查看完整 API 文档:https://mairuiapi.com/hsdata
  • SDK 文档与源码:https://github.com/MaiRuiApi/mairui
  • 关注公众号获取本系列更新

本文为技术演示,文中所有统计基于有限历史样本,不构成投资建议。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询