【Python量化实战 #15】业绩预告一出就追涨?用 Python 算清事件窗口真实收益(事件驱动实战)

本文是「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 获取股票数据,安装方法如下:

pip install mairui

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

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

运行环境:

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

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

import os
import mairui

# 证书从环境变量读取(官网注册后获取)
api = mairui.Client(os.environ["MAIRUI_LICENCE"])

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

三、什么是事件驱动策略

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

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

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

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

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

import time
import pandas as pd

# 示例股票池:跨行业大盘股样本(固定池便于复现,实际可换成指数成分股)
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"

def fetch_forecast_events(api):
    """拉取股票池的业绩预告,抽取事件四要素:代码 / 发布日 / 类型 / 摘要。"""
    rows = []
    for code, name in STOCK_POOL.items():
        for item in api.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)]
    return events.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 个交易日的累计收益:

import numpy as np

WINDOWS = (1, 5, 10)

def event_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 格式 YYYYMMDD
    if not kline:
        return {}
    kline_df = pd.DataFrame(kline).sort_values("t").reset_index(drop=True)
    entry_open = float(kline_df.iloc[0]["o"])     # 事件后首根K线开盘价买入
    result = {}
    for n in WINDOWS:
        if len(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.nan
    return result

关键解释

  • 买入基准用事件日后首个交易日的开盘价——预告多在收盘后或周末发布,事件日当天的收盘价你根本买不到;
  • 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 = ("回购", "增持", "中标", "重大合同", "业绩")

def scan_announcements(api, codes, limit=5):
    """拉最近公告并按关键词过滤,作为盘中事件监控的最小骨架。"""
    hits = []
    for code in codes:
        for a in api.stock_announcement(code, lt=limit) or []:
            title = a.get("zt", "")               # 公告标题
            if any(k in title for k in ANNOUNCE_KEYWORDS):
                hits.append({"code": code, "date": a.get("t"), "title": title})
        time.sleep(0.2)
    return pd.DataFrame(hits)

真实运行输出(节选):

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

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

八、完整可运行示例

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

import os
import mairui

def main():
    api = mairui.Client(os.environ["MAIRUI_LICENCE"])   # 证书从环境变量读取

    events = fetch_forecast_events(api)          # ① 抓业绩预告事件
    study = build_event_study(api, events)       # ② 逐事件算窗口收益
    study.to_csv("event_study.csv", index=False, encoding="utf-8-sig")

    for n in (1, 5, 10):                         # ③ 按类型聚合统计
        agg = study.groupby("type")[f"ret_t{n}"].agg(
            样本数="count",
            平均收益=lambda s: round(s.mean(), 2),
            胜率=lambda s: 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
  • 关注公众号获取本系列更新

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值