某 私募 事件驱动组的盘后会议。Q3 季报披露季刚开始,组里负责 沪深300 的研究员准备做一条 PEAD 信号:盈利惊喜后 60 个交易日的漂移。负责数据的同事翻出来一份 Wind 的盈利数据库,里面 actual_eps、consensus_eps、announcement_date 三列齐全;研究员说,把 SUE 算出来,按 announcement_date join 到日度面板就行。会议室里有个老人轻轻摇头:"你说的 announcement_date 是哪一天?业绩快报日?预披露日?正式财报披露日?如果是 2023-11-10 16:30 收盘后才发的公告,你今天交易日就能用?"研究员愣了一下。这就是事件驱动信号与公式化信号最本质的区别:事件驱动信号靠稀疏事件面板,常见 bug 全在事件可用日的精确口径上,而不在公式上。本课把规范的事件驱动信号库与另类数据信号库都搬上来,并且把 availability_date 这一根贯穿全课的红线焊死。
七大事件类型
事件驱动信号最规范的七个事件类型,按学术与工业界的成熟度排列:
(1) earnings_surprise (SUE) = (actual_eps - consensus_eps) / std(consensus_eps) —— 标准化盈利惊喜(Foster-Olsen-Shevlin 1984)。PEAD 漂移期约 60 个交易日(Bernard & Thomas 1989)。
(2) merger_arb_spread = (deal_price - current_price) / current_price —— 现金并购套利价差,跨度到交易完成日。
(3) buyback_announcement —— 公开市场回购公告(Ikenberry-Lakonishok-Vermaelen 1995),漂移期约 4 年。
(4) index_inclusion_event —— 指数纳入效应(Shleifer 1986),跨度到下一次成分调整。
(5) dividend_initiation_or_increase —— 股利初次发放或上调(Lintner 1956)。
(6) insider_buying —— 大股东增持 / Form 4 内部人买入,漂移期 6 到 12 个月。
(7) analyst_revision —— 卖方分析师评级修订(Womack 1996),漂移期约 60 个交易日。
每一条事件信号都有(a)事件检测规则,(b)信号定义(通常是事件日上的一个二元或分级指示量加一条衰减曲线),(c)典型漂移期,(d)典型 look-ahead bug(事件日与可用日之间的差)。
七大另类数据家族
另类数据按数据形态分七族,每族写一家代表性数据商:
(1) 新闻与电话会议 NLP 情绪 —— Refinitiv MarketPsych / RavenPack / Bloomberg 是新闻情绪;FinBERT 跑电话会议文本是话术与情绪。(Tetlock 2007)
(2) 卫星影像 —— Orbital Insight / RS Metrics,零售商停车场车辆计数;原油储罐液位。
(3) 信用卡 / 借记卡交易面板 —— Yipit / Earnest / Second Measure,消费支出 nowcasting。
(4) 网站流量 / App 下载 —— SimilarWeb / App Annie,DAU / MAU 当作收入代理。
(5) 招聘岗位数 —— LinkUp / Burning Glass,公司层级成长代理。
(6) 专利申请数 —— PatentSight / Lens.org,创新管线代理。
(7) ESG 评分 —— MSCI / Sustainalytics / RepRisk。
every alternative-data signal must use the release-date timestamp, never the period-covered timestamp —— 任何一条另类数据信号都按数据发布时点用,绝不能按数据所覆盖的时点用。一份"Q3 2023 消费支出"的报告在 10 月底才发出来,信号面板上只能从 10 月底开始填,而不是从 9 月 30 日就填。这是另类数据 PIT 纪律的核心 bug 源头。
三种衰减函数形状
事件信号的核心机制是 衰减函数:事件日的指示量在事件后的窗口内逐渐衰减归零。规范的衰减形状三种:
decay_linear(indicator, d)—— 从事件日开始的d天线性衰减,PEADd=60与卖方修订d=60都用这种。indicator * exp(-t / tau)—— 指数衰减,半衰期tau;情绪类信号tau=5-10 days是规范。step_function(indicator, d)——d天内常数、之后归零;回购d=252*4与指数纳入d=下次成分调整都用这种。
指数衰减形状在解析上最方便:
衰减函数的选择是超参数,规范纪律是对三种形状做一次敏感性测试:真信号在三种形状上都应该跑得动。每多扫一个形状,4.2.1 第 3 课的 trial counter 就要加一。
事件面板 join 的三类 bug
事件面板 join 的常见 bug 远比公式化信号的两类更微妙,本课聚焦三类:
availability-date violation—— join 用event_date而不是availability_date。一份 2023-11-10 盘后发的公告,如果 2023-11-11 与 2023-11-12 是周末,实际最早可用就是 2023-11-13 开盘的集合竞价。修复办法是把面板里同时存event_date与availability_date,join 永远用后者。decay-window forward-fill omission—— 把衰减信号写出来后,忘了在事件后的窗口内 forward-fill 到非事件日。事件后drift_horizon_days个交易日内,信号每一天都要非零,不是只在事件日有值。multi-event aggregation omission—— 同一只 标的 在同一天有两个事件(盈利公告 + 股利公告),要写一条显式的聚合规则,不能让 join 引擎随机选一行。
这三条是事件面板 join 最常见的 bug,几乎每次代码审查都会逮到至少一类。
端到端 PEAD 工作例
把规范化的 PEAD 信号写成一段函数。函数签名固定,实现按六步走:
def pead_signal(event_panel: pd.DataFrame, daily_panel: pd.DataFrame, decay_days: int = 60) -> pd.DataFrame:
# 1. 计算 SUE
event_panel['sue'] = (event_panel['actual_eps'] - event_panel['consensus_eps']) / event_panel['consensus_eps_std']
# 2. winsorize SUE at [0.01, 0.99] 跨全部事件
sue = event_panel['sue']
lo, hi = sue.quantile(0.01), sue.quantile(0.99)
event_panel['sue'] = sue.clip(lower=lo, upper=hi)
# 3. 在每个日历周内做截面 rank
event_panel['sue_rank'] = event_panel.groupby(pd.Grouper(key='announcement_date', freq='W'))['sue'].rank(pct=True)
# 4. join 到 daily_panel —— 用 availability_date = 下一交易日
event_panel['availability_date'] = next_trading_day(event_panel['announcement_date'])
signal = daily_panel.copy()
signal[:] = 0.0
for _, ev in event_panel.iterrows():
signal.loc[ev['availability_date']:, ev['symbol']] = ev['sue_rank']
# 5. 在事件后窗口内应用 decay_linear,forward-fill
signal = apply_decay_linear(signal, decay_days)
# 6. T+1 滞后 —— 信号在 t+1 日开盘可用
return signal.shift(1)
pead_signal 把稀疏事件面板与 dense daily panel 桥接起来,产出每天每只 标的 一个值。把它跑在 沪深300 上,样本期 2018 到 2023,典型 60 日 rank IC 落在 0.04 到 0.08 之间,t 统计量 3 到 5。
衰减期敏感性
PEAD 的 60 日漂移期来自学术文献,但实际数据可能在 30 到 120 之间漂移。规范敏感性表如下:
# PEAD 衰减期敏感性 —— decay_days
decay_grid = [30, 45, 60, 90, 120]
for d in decay_grid:
ic = compute_rank_ic(pead_signal(events, daily, d), forward_returns_d)
print(d, ic)
诠释规则:"a genuine event-driven signal shows a monotone-and-smooth IC across the decay-horizon band; an overfit signal shows a sharp peak at the published value" —— 真信号在衰减期邻域上单调而平滑,过拟合的信号会在 60 日那一点上拱出一个尖峰。
工作例:盈利电话会议 NLP 情绪
把另类数据家族里覆盖最广的"电话会议情绪"展开讲一下。原始数据是 美股 / 沪深300 上市公司每季度业绩说明会的逐字稿,源头是 Wind / 同花顺 或者公司投关网站。规范的处理流水线:
(1) 把每份逐字稿用一段预训练 NLP 编码器(FinBERT、金融领域专用 BERT)做 sentence-level embedding;
(2) 用规则系统把逐字稿切成 准备发言(CEO 与 CFO 主稿)与 问答环节(分析师提问),后者的信号含量显著高于前者;
(3) 对 Q&A 段做情绪打分,典型聚合方式是 mean pooling 加上对"hesitation markers"(犹豫词、不确定语)的额外加权;
(4) release_date 就是电话会议结束时刻(中国上市公司通常在交易日盘后),availability_date 是下一个交易日的开盘。
注意 release_date 与文本所"涵盖"的财季 period_end_date 完全不一样:一份"2023 年 Q3 业绩说明会"的逐字稿 release_date 可能在 2023-11-10 19:00,但是它涵盖的财季是 2023-07-01 到 2023-09-30。信号面板上只能从 release_date 之后填,绝不能从 period_end_date 开始填。这条纪律没人提醒就一定会被违反一次。
与 PIT 数据基础设施模块的边界
事件驱动信号的可用日纪律,与基本面信号的公告日纪律,本质是同一件事:数据的"产生时点"与"市场可用时点"必须分开存。基本面信号的 PIT 已经在数据基础设施那一模块里讲过,本课在事件驱动家族下又把它强调一遍,是因为事件驱动信号几乎每一种都带有相同的口径风险。当你把基本面信号库与事件驱动信号库一起喂给 ML 模型时,这两套 PIT 纪律必须同时生效,缺一不可。这也是为什么下一课的特征矩阵审计要把每一个特征都按"它的可用日规则是什么"逐一核对。
与公式化家族的结构差
事件驱动家族与公式化家族最本质的差别在数据结构层。公式化信号消费的是 dense panel —— 每个 (symbol, date) 都有一行,没有事件也照常每天产出一个值。事件驱动信号消费的是 sparse event panel —— 只有 (symbol, announcement_date) 元组在表里出现,然后通过 join + decay forward-fill 投射回 dense 面板。绝大部分事件驱动 bug 都来自这个投射:join key 选错(event_date vs availability_date)、forward-fill 忘做(事件后窗口里非事件日的值丢了)、或多事件同日没有显式聚合规则。
事件驱动信号的另一个结构性问题是 clustering:盈利季的一周内可能有 300 只 标的 同时披露,事件之间的横截面相关性远比公式化信号高。这会让朴素的回测低估真实风险,但这是后面回测模块的问题,本课只点到为止。
A 股 的事件类型与公司治理与 美股 略有差异 ——证监会 的强制信披口径、创业板、科创板、沪深港通 各自的窗口期纪律、大股东 增减持申报的 T+2 披露规则——这些细节在 4.3.2 的"中国 A 股因子表现"里更深入地展开。本课只名义点到。
衔接
到这里你能(a)挑出七类规范事件,(b)把每一类事件写成稀疏事件面板上的信号,(c)把它通过 availability-date join + decay 投射到 dense daily panel,(d)做衰减期敏感性测试。下一课会把公式化信号与事件驱动信号一起作为特征喂给非线性模型,引出 ML-driven 家族——LightGBM ranker、purged-and-embargoed k-fold、四类 ML 专属泄露模式。
本课组件
Inline-code listing of the seven canonical event types:earnings_surprise (SUE)、merger_arb_spread、buyback_announcement、index_inclusion_event、dividend_initiation_or_increase、insider_buying、analyst_revision。Inline-code listing of the seven canonical alternative-data families:新闻 / 电话会议 NLP 情绪、卫星影像、信用卡 / 借记卡交易面板、网站流量 / App 下载、招聘岗位数、专利申请数、ESG 评分。Inline-code listing of the three canonical decay-function shapes:decay_linear(indicator, d)、indicator * exp(-t / tau)、step_function(indicator, d)。Inline-code listing of the three canonical event-panel join bugs:availability-date violation、decay-window forward-fill omission、multi-event aggregation omission。Fenced python 段:pead_signal 端到端函数(decay_days=60 缺省)与 [30, 45, 60, 90, 120] 五点衰减期敏感性表。一个 Exercise,两个 Hint。上面 KaTeX block 公式给指数衰减的形式化表达。
Region anchors:沪深300 universe 与 巨潮信息网 / 大股东 增减持申报。本课在词汇层覆盖的关键术语:动量(作为事件后漂移与漂移期相关的对照标签)、因子模型 框架(forward-pointing 到 4.3.1)、Alpha 衰减(事件信号的衰减期天数就是 Alpha 衰减 的直接构造)。监管与板块脉络上点到:A 股 上市,证监会(中国证监会)信披口径,创业板,科创板,沪深港通 跨境通道。这些 CN 监管细节都在 4.3.2 的"中国 A 股因子表现"模块里更深入展开,本课只名义引用。
Fenced python 提醒:每一条另类数据信号都按 release_date 时间戳用,绝不能按 period_covered 用。
练习
Exercise
在 沪深300 universe 上,使用 大股东 增减持申报数据,端到端实现 内部人买入信号,样本期 2023 全年。(i) 写出事件检测规则、信号定义(在申报日上的二元指示量按 transaction_value / market_cap 缩放)、衰减形状(线性 vs 指数 vs 阶跃)、衰减期 decay-horizon-days、横截面归一化口径。(ii) 说明事件的 PIT 可用规则:你用 transaction date 还是 filing date 来 join 日度面板?为什么?(iii) 写出清洗流水线 Python 函数:把稀疏事件面板按可用日 join 到 dense daily panel,在事件后窗口内 forward-fill 衰减,再做 winsorize -> rank -> z-score,最后做 T+1 滞后。(iv) 指出三个不小心就会引入的事件面板 bug —— availability-date、decay-window forward-fill、multi-event 聚合 —— 各写一个修复办法。(v) 给出五个衰减期值 [60, 120, 180, 252, 365] 天的敏感性表,并解释为什么单调 IC 是真信号的诊断信号。
提示
提示
transaction_value / market_cap,decay_days 换成 180 之类的更长窗口。重点是 join 上的 availability_date 与 forward-fill 步骤,别漏。