跳转至

M19 事件驱动与基本面量化

层级:L2 因子投资 | 优先级:★★★ | 预计学时:10 小时 对应原书:因子第 5 章的异象;Bernard 与 Thomas(1989) 前置知识:M08 的发表后衰减;M18 的收盘时点。 读书笔记:事件与公告时点 状态:reviewed 数据截至:2026-10|最近复核:2026-10-22


学习目标

  1. 把公告日跳空和公告之后的漂移分成两段,并只把下一段里你买得到的价格算进收益。
  2. 用手算季节性随机游走的 SUE,分母只用已经公布的意外。
  3. 用公告前的交易日估计异常收益,不把公告日放进 β。
  4. 把指数纳入从公告到生效、以及生效之后的价格,和 Greenwood 与 Sammon 的样本分开。
  5. 内部人成交日和 Form 4 申报日不是同一个可交易时点。

本章知识拓扑图

flowchart TB
    ANN["公告时点"] --> GAP["跳空:往往在你成交之前"]
    ANN --> HOLD["公告之后的持有期"]
    EPS["已公布的 EPS"] --> SUE["SUE"]
    SUE --> HOLD
    PRE["公告前的收益"] --> BETA["β"]
    BETA --> AR["异常收益"]
    FILE["申报日"] --> TRADE["下一可交易时段"]

学术上的多空事件组合有一条空头腿。只做多、杠杆上限 1.0 的账户只放得下正意外的那一边。


🟢 入门层:跳空和漂移不是同一笔钱

1. 公告在收盘之后

Ball 与 Brown(1968,Journal of Accounting Research 6(2))用会计盈余和之后的价格说明,盈余公布时价格没有把信息一次走完。Bernard 与 Thomas(1989,Journal of Accounting Research 27:1–36)把公告之后仍沿同一方向走的那一段叫做盈余公告后漂移。这一段不是公告那一下的跳空。

教学价格:公告在收盘之后,公告前收盘 100,下一交易日开盘 103,生效日收盘 104,再往后的收盘 102。

算法 收益
开盘相对公告前收盘 0.030000
假装用 100 买到、持有到 104 0.040000
用 103 的开盘买到、持有到 104 0.009709
用 103 买到、持有到 102 −0.009709

3 个百分点的跳空发生在开盘之前。能成交的人拿到的是 0.97 个百分点,不是 4 个百分点。再持有到 102,从开盘算是 −0.97 个百分点。把「收盘到收盘 +4%」写成策略收益,等于假设自己在公告之前已经买到。

左:4%、0.97% 和 −0.97%。右:同一笔 0.30 的意外,分母不含当期时 SUE 更高。

2. 指数纳入变薄,不是一条永久的公告日规律

Harris 与 Gurel(1986,Journal of Finance)记录标普 500 名单变动时的价格和成交量压力。Greenwood 与 Sammon 在《Journal of Finance》2024 年 12 月在线发表的结果里,纳入的平均异常收益从 1990 年代的 7.4% 降到近十年不到 1%;2010–2020 年剔除的平均异常收益约 0.1%。M02 已经写过:指数基金变大之后,可预测的冲击没有同步变大。

公告日到生效日是一段,生效之后的价格是另一段。上面的 104 和 102 只说明这两段的符号可以相反。它们不是 Greenwood 与 Sammon 的 7.4% 或 0.1%。


季报常常在收盘之后才出来。从昨晚收盘 100 到今早开盘 103 的 3 个点,你已经买不到。能成交的是开盘之后的那一段。 把「收盘到收盘 +4%」写成策略收益,等于假设公告之前已经持有。负的意外若靠做空去赚,只做多账户没有那条腿,至多是不持有。 去年同一季才是淡旺季对得上的预期。除以这只股票自己的历史惊喜波动,弹性大的公司和弹性小的公司才在同一把尺子上。 内部人自己的成交日,通常早于你能看到的申报日。用成交日去改当天的动量,用的是当时还没公开的信息。 指数纳入的跳空会变薄。近十年的幅度不能拿一篇旧论文的合计直接下单。进阶层把 SUE、β 的窗口和事件研究的五步分开。

公告前收盘 100,次日开盘 103,生效日收盘 104,再往后 102。开盘相对 100 是 3 个百分点,这一段在你看到公告时已经走完。 用 103 买到、持有到 104,是 0.97 个百分点。持有到 102,从开盘算是 −0.97 个百分点。把收盘到收盘的 4 个百分点写成策略收益,等于假设公告之前已经买到。

纳入标普 500 的平均异常收益,从 1990 年代的 7.4% 降到近十年不到 1%。2010–2020 年剔除大约 0.1%。104 和 102 只说明两段的符号可以相反,不是这组样本均值。 只做多账户上,负的意外至多是不持有。权重不因为空不出去就摊给正意外的名字。

去年同一季的预期才和淡旺季对齐。SUE 再除以这只股票自己的历史惊喜波动。β 的估计窗停在公告之前,异常收益才是事件窗减去事先拟合的正常收益。

同一笔 0.30 的意外,分母只用已经公布的惊喜时,SUE 是 2.705926。把当期 0.30 也放进标准差,SUE 降到 2.095291。正在公布的数字不能用来缩小自己的信号。 公告前最后一次一致预期若是 1.45、实际盈余 1.50,意外是 0.05。用四个季度以前没有更新的 1.20,会制造 0.30 的假意外。 内部人的成交日通常早于申报日。用成交日去改当天的持仓,用的是当时还没公开的信息。

🟡 进阶层:SUE 的分母和 β 的窗口

3. 季节性随机游走

零售的第四季通常高于第三季。用上一季当预期,会把淡旺季当成意外。 去年同一季 Eₜ₋₄ 在公告之前已经公布,季节对得上。 意外还要除以这只股票自己的历史惊喜波动 σ。弹性大的公司,同样 0.30 的意外没有弹性小的公司那么意外。 SUE 把不同弹性拉到同一把尺子上。分母若混进当期,波动会被这次惊喜自己抬高。

一季的未预期盈余用四季之前的每股盈余做预期:

uₜ = Eₜ − Eₜ₋₄

符号 含义
Eₜ 刚刚公布的这一季每股盈余
Eₜ₋₄ 四个季度之前已经公布的每股盈余
uₜ 季节性意外
σ 更早若干期 u 的样本标准差,分母是 n − 1
SUE uₜ/σ

已经公布的四期意外是 0.10、−0.05、0.20、0.00。当期意外是 0.30。样本标准差是 0.110868。

SUE = 0.30/0.110868 = 2.705926

若把 0.30 也放进标准差,σ 变成 0.143178,SUE 变成 2.095291。当期意外还没公布时,这个更大的 σ 并不存在。用它做分母,是在用正在公布的数字去缩小自己的信号。

分析师一致预期要带时间戳。实际盈余 1.50。公告前最后一次预期若是 1.45,意外是 0.05。若误用四个季度以前、没有更新的 1.20,意外变成 0.30。若数据库里的「当季一致预期」在公告之后被改成 1.50,意外变成 0,信号被事后修订吃掉。能用的是公告时点之前已经发布的那一版。

4. 异常收益的 β 停在公告之前

β 的窗口必须在公告之前结束。用公告之后的收益去拟合「正常」,等于用结果解释结果。 异常收益是事件窗收益减去这个事先拟合的正常收益。跳空和漂移仍要分开记。 教学例子里,含进公告日之后,β 会从 1.035294 变成 1.192344。窗口停在公告之前,才是 0.032824 那一格。 负意外的空头仍不在只做多账户里。

公告前五日,市场收益是 0.01、−0.02、0.015、0.005、−0.01,股票收益是 0.012、−0.018、0.020、0.004、−0.008。带截距的回归给出截距 0.002000、β 1.035294。公告日股票收益 0.04、市场收益 0.005 时,异常收益是

0.04 − (0.002000 + 1.035294 × 0.005) = 0.032824

把公告日放进同一条回归,β 变成 1.192344,异常收益变成 0.026699。公告日自己把 β 拉高,再把自己的异常收益压低。估计窗口要在公告之前结束。

空头腿仍放不进只做多的账户。负 SUE 的一边若留成现金,组合就不是论文里的多空。费用按 M13 的每一元成交计算。公告日换仓通常比月度因子更密,不能把未扣费的异常收益当成净值。

5. 事件研究是五步,不是一根收益柱

MacKinlay(1997,Journal of Economic Literature)把事件研究收成一条可以复述的流程:先定义事件,再用事件之前的窗口估计正常收益,然后在事件窗里减掉正常收益,加总成累计异常收益,最后看这个累计值相对估计误差大不大。本章只用上面那五天和公告日做手算,不重做他文中的任何样本。

五步落到这组教学价格上,是这样:

  1. 事件与事件窗。 盈余在收盘之后公布。学术上常把下一交易日收盘相对公告前收盘的收益当作事件日收益,这里就是 100 到 104 的 0.040000。能下单的人在开盘已经面对 103,所以可交易的事件窗从 103 起算,到 104 只有 0.009709。两段都要写出来。写成一根「事件收益」而不说是哪一个价格,会把跳空记进持仓。
  2. 估计窗。 正常收益用公告之前的五个交易日。市场模型带截距:R = a + b Rₘ + e。估计结果是截距 0.002000、斜率 1.035294。公告日不在这五天里。
  3. 事后窗。 生效收盘 104 之后再到 102,原始收益是 102/104 − 1 = −0.019231。这五天的市场收益没有覆盖这一段,所以这里只有价格变化,没有异常收益。事后窗用来看漂移有没有留在成交之后,不能拿估计窗的 β 去编一个没有市场收益的异常值。
  4. 异常收益与累计异常收益。 事件日的市场收益是 0.005。正常收益是 0.002000 + 1.035294 × 0.005,异常收益是 0.032824。事件窗只有这一天时,累计异常收益就等于这一天的异常收益。窗口拉长之后才是逐日相加。相加的每一天都要用当天已经实现的市场收益,不能用事后才知道的平均市场。
  5. 显著性。 估计窗残差是 −0.000353、0.000706、0.002471、−0.003176、0.000353,和为 0。残差平方和除以 T − 2 = 3,标准差是 0.002376。预测这一天时,标准误还要乘上

√(1 + 1/T + ((Rₘ − ¯Rₘ)²) / (Σ (Rₘ − ¯Rₘ)²))

得到 0.002635。0.032824 相对未四舍五入的标准误,t = 12.4575。五天样本里残差只有几十个基点,3.28 个百分点的异常收益当然会得到一个很大的 t。它说明这一天相对这五天很极端,不是 Bernard 与 Thomas 的横截面检验,也不能外推到别的公告。

预测标准误比残差标准差大,是因为正常收益本身是估出来的。只拿残差标准差去除异常收益,会把 t 报得更高。横截面上还要把不同公司、同一天的公告放在一起看标准误,单只股票的 t 代替不了那一步。

6. 漂移为什么会留下:三条机制,三条都不能直接下单

盈余公告后漂移是公告之后价格仍沿意外的方向走。跳空已经在开盘完成的那一部分,不属于漂移。文献里反复出现的形成原因可以收成三条。本章不重估它们的幅度。

第一条是反应不足,而且在个人投资者更多的股票上更明显。公告很多,注意力有限,小公司、同一天扎堆公布的盈余,价格走得更慢。这解释的是漂移的存在,不给出你下一季还能拿到的百分点。

第二条是预期粘着。分析师不会在一个晚上把模型改到新盈余上。上面的 1.20 和 1.45 就是同一笔实际盈余 1.50 的两个时点:过期预期制造 0.30 的假意外,公告前最后一版只剩 0.05。粘着若是真的,信号必须用公告之前已经印出来的那一版预期。用公告之后被修订成 1.50 的「一致预期」,意外变成 0,漂移的预测也被删掉了。

第三条是套利限制。异常收益要变成组合收益,还要付买卖价差、冲击、借券费用,并且承受个股残差波动。负意外的一边在论文里是空头。只做多、杠杆上限 1.0 的账户没有这条腿,负 SUE 至多是不持有。不持有并不等于做空,论文里的多空收益因此不能记到这个账户上。费用仍按 M13 的成交额计算。公告日集中换仓时,未扣费的 0.032824 更不能写成净值。

三条可以同时成立。注意力解释谁没交易,粘着解释信号从哪一个预期来,套利限制解释为什么没被立刻抹平。把其中一条写成「所以 PEAD 还在」是越界:McLean 与 Pontiff 说的是发表之后预测变量的平均收益下降,指数纳入从 7.4% 到不到 1% 是这种适应的例子。PEAD 没有一个可以乘上去的固定折扣,也没有被本章证明仍然存在。

7. 九项财务信号加总成 F-Score,加总不是收益

Piotroski(2000,Journal of Accounting Research)在高账面市值比的股票里,用九个 0/1 财务信号把后续表现分开。本章只拆这九项怎么打分,不重估他的高分减低分组合。每一项只用已经申报的报表,申报日之前的价格不能提前使用这个分数。

组 信号 记 1 的条件
盈利 总资产收益率 当期 ROA 为正
盈利 经营现金流 经营现金流为正
盈利 盈利变化 ROA 高于去年同期
盈利 应计 经营现金流大于净利润
杠杆与流动性 杠杆 长期负债除以资产低于去年
杠杆与流动性 流动比率 流动比率高于去年
杠杆与流动性 发股 当期没有增发股票
效率 毛利率 毛利率高于去年
效率 资产周转 资产周转率高于去年

九项都是与自己的历史比,不是与行业比。应计这一项记 1,表示利润里现金的成分更高,而不是表示利润更高。发股记 1 表示没有在外融资,融资本身不一定是坏消息,只是这个分数把「没有发行」当成更干净的内部资金。分数是九项之和,从 0 到 9。Piotroski 在高账面市值比的子集里使用它。拿到全市场、不先做价值筛选,就不是他的检验。

教学打分,不是某家公司的报表:ROA 为正、现金流为正、ROA 上升、杠杆下降、流动比率上升、毛利率上升,这六项记 1。现金流低于净利润、当期有增发、周转率下降,这三项记 0。F-Score 是 6。6 只说明九项里过了六项。它不是预期收益,也不能和 SUE 的 2.705926 相加。两个信号若要合成,权重是 M10 的问题,合成之前还要确认它们的公布日都已经过去。

import numpy as np


def seasonal_sue(history: np.ndarray, current: float) -> float:
    """分母只用已经公布的意外。current 不进入标准差。"""
    return current / float(history.std(ddof=1))


def event_ar(market: np.ndarray, stock: np.ndarray, mkt_event: float, stk_event: float) -> tuple[float, float, float]:
    """估计窗不含公告日。返回截距、β 和公告日异常收益。"""
    x = np.column_stack([np.ones(len(market)), market])
    alpha, beta = np.linalg.lstsq(x, stock, rcond=None)[0]
    normal = alpha + beta * mkt_event
    return float(alpha), float(beta), float(stk_event - normal)


def f_score(flags: dict[str, bool]) -> int:
    """九项各是 0 或 1。缺一项就拒绝,避免用 0 填上还没公布的科目。"""
    keys = (
        "roa_positive", "cfo_positive", "roa_up", "cfo_gt_roa", "leverage_down",
        "current_up", "no_issue", "margin_up", "turnover_up",
    )
    return sum(int(flags[key]) for key in keys)


history = np.array([0.10, -0.05, 0.20, 0.00])
sue = seasonal_sue(history, 0.30)
alpha, beta, ar = event_ar(
    np.array([0.01, -0.02, 0.015, 0.005, -0.01]),
    np.array([0.012, -0.018, 0.020, 0.004, -0.008]),
    0.005,
    0.04,
)
score = f_score({
    "roa_positive": True, "cfo_positive": True, "roa_up": True, "cfo_gt_roa": False,
    "leverage_down": True, "current_up": True, "no_issue": False,
    "margin_up": True, "turnover_up": False,
})

九项的名字对应盈利、杠杆和效率。教学计数是 6。当期意外不进入 history,否则 SUE 会从 2.705926 降到 2.095291。

8. 时点财务数据:报表期间结束,不等于你已经看见

利润表上的季度结束日、公司把文件交给交易所或证监会的申报日、数据商把数字放进库的可用日,是三个日期。回测要用的是可用日。可用日晚于期间结束日。用期间结束日去对齐第二天的收益,等于假设报表在季度最后一天下午已经公开。

修订使得同一期间可以有两套数字。第一次申报的每股盈余若是 1.50,后来重述成 1.62,在重述公开之前,信号只能用 1.50。数据库若只保留最新值,把 1.62 填回旧日期,就是财务上的前视,和价格上用未来复权因子改写历史是同一类错误。M18 的总收益因子要求公司行动按除权日向前累加;财务信号要求数字按可用日向后才进入特征。方向相反,原则相同:当天不存在的信息不能进当天的决策。

本仓库的 return_factors 处理的是拆股、股息和分拆现金,不读取利润表,也没有一份时点财务报表。因此 F-Score 和 SUE 在这里只能用手算教学输入。接到外部财务库时,至少留下四列再谈排序:证券、报表期间、可用时间戳、当时的数值。排序键是可用时间戳,不是报表期间。同一期间的重述是新的一行,而不是覆盖旧行。分析师预期另有一列发布时间,必须早于或等于公告时点,不能用公告之后的修订版去填公告之前的行。

季节性随机游走和分析师预期是两种 SUE,不要混成一个字段。

季节性随机游走 分析师一致预期
预期从哪来 四个季度前已公布的每股盈余 公告前最后一版一致预期
教学分子 0.30,来自历史意外的当期 1.50−1.45=0.05
分母 更早意外的样本标准差 0.110868 可以是预期离差,也可以只看差额
最容易的前视 把当期 0.30 放进 σ,SUE 变成 2.095291 用修订后的 1.50,意外变成 0
过期信息 用上一季代替去年同季,把季节当成意外 用四个季度前的 1.20,假意外 0.30

两种构造可以同时算。它们回答的问题不同:随机游走问「相对去年同季」,一致预期问「相对公告前市场已经听到的数字」。把 0.30 和 0.05 写成同一笔意外的两种算法,只有在 1.20 仍是当时的一致预期时才成立。否则 0.30 只属于随机游走或过期预期,不属于当天的一致预期。


🔴 深入层:申报日晚于成交日

9. 回购和内部人

公开市场回购的公告是一个时点,实际买回往往拉得很长。回测若在公告之前的收盘买入,用的是当时还没公开的决定。Ikenberry、Lakonishok 与 Vermaelen(1995,Journal of Financial Economics)讨论市场对公开市场回购反应不足。

美国内部人的 Form 4,在 2002 年《萨班斯–奥克斯利法》之后,一般要在交易日后的第二个营业日结束前申报。交易日当天的市场还不知道这份申报。用交易日的收盘去交易「内部人买入」,是前视。能用的是申报已经公开之后的下一档。港股董事权益的披露晚于成交,具体截止以当时的《证券及期货条例》和交易所规则为准,不在这里写成一个固定天数。

McLean 与 Pontiff 的发表后衰减说的是预测变量的平均收益下降,不是把 PEAD 的回测乘上一个固定折扣。指数纳入从 7.4% 到不到 1%,是这种适应的一个例子,不是所有事件的同一个折扣。

10. 中低频多头组合的财报窗口规避与跳空风控

M04 的止损假设缺口还没跳过你的价。案例 2 里,甲骨文 2025-09-10 的隔夜是 +32.16%,摩根大通 2020-03-16 的隔夜是 −18.01%。这两段都已经在开盘价里。月度选股若在调仓日之后的 K 个交易日内会碰到财报,开盘可以一次走完止损距离。

三条规则一起用。K 在下面的教学里取 5。

  1. 新开仓:财报日当天暂缓,权重记 0。窗口内但还不是公告当天,权重折半,并且不超过单名 10%。
  2. 已持仓:不为了躲开财报去付一趟双边印花税清仓。权重高于 max_position_pct = 10% 的部分截到 10%。
  3. 财报前夕不挂紧贴现价的止损。隔夜假跳空会在开盘把止损扫掉,成交价却是跳空之后的价格。

教学四行:新开仓距财报 0 天、原权重 0.08,动作 defer,权重 0.00。距财报 3 天,动作 halve,权重 0.04。已持仓距财报 2 天、原权重 0.16,动作 hold,权重 0.10。窗口内的止损单,动作 blocked,原权重 0.08 留在账上,不发出这张止损。

# goal8-check
"""财报窗口:新开仓折半或暂缓,已持仓只截单名上限。离线。"""

from decimal import Decimal

CAP = Decimal("0.10")


def earnings_blackout(
    intent: str, days_to_report: int, k: int, weight: Decimal
) -> tuple[str, Decimal]:
    # 窗口含公告当天。K 是调仓日之后还要看的交易日数。
    inside = 0 <= days_to_report <= k
    if intent == "stop" and inside:
        return "blocked", weight
    if intent == "open" and days_to_report == 0:
        return "defer", Decimal("0.00")
    if intent == "open" and inside:
        return "halve", min(weight / 2, CAP)
    if intent == "hold":
        return "hold", min(weight, CAP)
    return "allow", min(weight, CAP)


cases = (
    ("open", 0, 5, Decimal("0.08")),
    ("open", 3, 5, Decimal("0.08")),
    ("hold", 2, 5, Decimal("0.16")),
    ("stop", 1, 5, Decimal("0.08")),
)
for intent, days, k, weight in cases:
    action, out = earnings_blackout(intent, days, k, weight)
    print(intent, days, action, f"{out:.2f}")

真实案例

100、103、104 和两个 SUE 仍是正文里的手算。下面用文献里已经报告的纳入收益,以及本仓库种子里的真实开盘,看跳空落在哪一个时点。

案例 1:纳入的 7.4% 没有留在近十年

Greenwood 与 Sammon(2024,Journal of Finance)报告,标普 500 纳入的平均异常收益在 1990 年代是 7.4%,近十年不到 1%;2010–2020 年剔除约 0.1%。这是他们的样本。指数基金规模变大之后,可预测的冲击没有同步变大。Form 4 在 2002 年之后一般要到交易日后的第二个营业日才公开,内部人的成交日仍然不是信号日。

案例 2:季度里最大的隔夜跳空,开盘已经把它走完

每个日历季度取隔夜跳空 |Oₜ/Cₜ₋₁ − 1| 最大、且绝对值至少 3% 的那一天。前收买进、拿到当天收盘,是看见跳空之后才去用前收,属于前视。开盘买进只拿到 Cₜ/Oₜ − 1。再晚一个交易日,拿到的是次日开盘到次日收盘。

甲骨文用 technical_indicators_lab/seed_data/US.ORCL_bars.csv 的未复权开盘和收盘。这样的季度有 62 个,上涨和下跌各 31 个。上涨那 31 天,隔夜平均 +8.72%,前收到收盘平均 +7.45%,开盘到收盘平均 −1.21%,次日开盘到次日收盘平均 +0.05%。下跌那 31 天,隔夜平均 −6.27%,前收到收盘平均 −5.82%,开盘到收盘平均 +0.49%,次日大约是 0。最大的一跳在 2025-09-10:隔夜 +32.16%,日内只有 +2.86%,前收到收盘 +35.95%,次日开盘到收盘是 −6.81%。

摩根大通用 jpm_lab/seed_data/US.JPM_bars.csv。上涨 22 天:隔夜平均 +6.13%,前收到收盘 +7.74%,开盘到收盘 +1.48%,次日 −0.48%。下跌 13 天:隔夜 −5.75%,前收到收盘 −5.89%,开盘到收盘 −0.10%,次日 −0.35%。最大的一跳在 2020-03-16:隔夜 −18.01%,当天开盘之后反弹 +3.71%,前收到收盘 −14.96%,次日开盘到收盘 +3.16%。−18% 在开盘价里,开盘之后的反弹补不回这一段。

momentum_plus_lab/seed_data/adj_close.csv 里的 AAPL 只有复权收盘,拆不开隔夜和日内。47 个日历季度里,收盘到收盘绝对值至少 3% 的极值日,绝对涨跌平均 6.01%,下一天的收盘到收盘平均 +0.13%。最大的一天是 2025-04-09,复权收盘 +15.33%,下一天 −4.24%。这只说明极值日没留到第二天,不能写成开盘已经含了多少。三只股票共同的读法是:能成交的价格在跳空之后。前收到收盘把已经发生的缺口记进了策略。


方法论来源

概念 / 模型 核心文献或规范出处
事件研究五步 MacKinlay (1997), Journal of Economic Literature
盈余公告后漂移 Bernard & Thomas (1989), JAR 27: 1–36
季节性 SUE 相对四季之前的盈余;分母只用已公布意外
F-Score Piotroski (2000);本章教学计数是 6
指数纳入 Harris & Gurel (1986), JF;Greenwood & Sammon, JF,2024-12 在线
时点财务 排序用可用时间戳;重述另起一行
图 figures/M19/plot_events.py
纳入收益 Greenwood & Sammon (2024), JF;7.4% 与不到 1% 是论文报告的样本
跳空时点 ORCL、JPM 的实验室 K 线;AAPL 只用 adj_close.csv 的复权收盘

💡 思考回顾与巩固

核心认知纠偏

误区 反例 用法
公告日收益就是策略收益 从 100 到 104 是 4%,从 103 买到 104 是 0.97% 成交价在跳空之后
漂移等于跳空 开盘之后到 102 是 −0.97% 两段分开
SUE 的 σ 用上当期更稳 2.705926 变成 2.095291 分母只用已公布的意外
一致预期没有日期 1.20 和 1.45 给出 0.30 和 0.05 用公告前的最后一版
公告日可以参加 β 的估计 β 从 1.035294 升到 1.192344,异常收益从 0.032824 降到 0.026699 窗口在公告前结束
单日 t = 12.4575 是论文里的显著性 估计窗只有五天,残差标准差 0.002376 这是教学诊断,不是横截面检验
事后窗 −0.019231 已经是异常收益 没有那天的市场收益 只记原始价格变化
F-Score 为 6 就是预期收益 九项里六项为 1 分数不是收益,也不和 SUE 相加
季度结束日的报表已经公开 可用日晚于期间结束日 用可用时间戳排序
重述后的 1.62 可以填回旧日期 重述公开前只能用 1.50 新行,不覆盖
负 SUE 可以在只做多账户里做空 杠杆上限 1.0,信号不接受负权重 空头腿留成现金
1990 年代的 7.4% 仍是近十年的纳入收益 近十年不到 1% 用 Greenwood 与 Sammon 的后一段
内部人交易日的收盘可以用 Form 4 一般在其后第二个营业日 用申报公开之后
发表后衰减等于把 PEAD 减半 M08 拒绝这个折扣 衰减不是一个乘数
本章的 0.97% 是 Bernard 与 Thomas 的漂移 那是 100、103、104 这组教学价 论文收益没有重算

费曼自测

  1. 为什么从 100 持有到 104 的 4%,不能记在收盘后才看到公告的账户上?
  2. 把当期意外放进 σ,SUE 为什么变小?
  3. 公告日进入回归之后,异常收益为什么从 0.032824 降到 0.026699?
  4. 指数基金规模变大,为什么纳入的平均异常收益反而变小?
自测要点 1. 公告在收盘之后,100 已经不能买。下一笔的价格是 103。4% 里的 3 个点是开盘跳空,不是持仓的收益。 2. 0.30 比历史意外更极端。放进样本之后,标准差从 0.110868 升到 0.143178,分子不变,SUE 从 2.705926 降到 2.095291。这个更大的标准差在公布之前并不知道。 3. 公告日股票收益 0.04 远高于市场 0.005。放进回归会把 β 从 1.035294 抬到 1.192344。用更高的 β 去解释同一天,剩下的异常收益就变小。这一天不该参加自己的基准估计。 4. 纳入一旦被写进很多账户,价格会在生效日之前被提前交易掉。Greenwood 与 Sammon 的近十年不到 1%,对照的是 1990 年代的 7.4%。规模变大没有把冲击同步放大。

四维习题

A. 概念题

  1. 盈余公告后漂移和公告日跳空,各自由哪一个价格区间衡量?
  2. 季节性随机游走的预期为什么是 Eₜ₋₄,而不是上一季的 Eₜ₋₁?
  3. 分析师预期在公告之后被修订,用修订后的数字做 SUE 会怎样?
  4. Form 4 的申报日和内部人的成交日,哪一天才能进入信号?

B. 计算题

  1. 收盘 100、开盘 103、其后收盘 104 和 102。四段收益:跳空、收盘到 104、开盘到 104、开盘到 102。
  2. 历史意外 0.10、−0.05、0.20、0.00,当期 0.30。两种 SUE 各是多少?
  3. 实际 1.50,预期 1.45 和 1.20。两笔意外各是多少?
  4. 公告前 β 是 1.035294、截距 0.002000。公告日股票 0.04、市场 0.005。异常收益是多少?把公告日放进回归后,异常收益变成多少?
  5. 估计窗残差标准差 0.002376,预测标准误 0.002635。相对 0.032824 的 t 是多少?104 到 102 的原始收益是多少?九项里六项为 1 时 F-Score 是多少?

C. 编程实战题

  1. 运行 figures/M19/plot_events.py。核对 0.030000、0.040000、0.009709、−0.009709、2.705926、2.095291、0.032824、0.026699。
  2. 把开盘从 103 改成 100,说明「收盘到生效收盘」和「开盘到生效收盘」为什么变成同一个数。不要把改完的价格写成某家公司的财报。
  3. 写信号时,预期的时间戳必须早于或等于公告时点,β 的样本必须在公告日之前结束。不要在开发机为了验证事件而下单。

D. 个人实战思考题

  1. 你只能做多。一篇多空 PEAD 论文的收益,哪一条腿你不能下单?
  2. 指数公司在收盘后宣布纳入,生效日在五天之后。你准备在宣布当晚的收盘价回测买入。错在哪一个时点?
  3. 董事在周一买入,Form 4 在周三公开。你周一收盘的动量信号要不要把这笔买入算进去?
完整参考答案与解析 **A1.** 跳空是公告前最后一笔能成交的价格,到公告后第一笔能成交的价格。收盘后公告时,就是 100 到 103。漂移从成交价再往前量,教学例子里是 103 到 104,或者 103 到 102。4% 把两段绑在一起,还假装 100 可以买。 **A2.** 季报有季节性。零售的第四季通常高于第三季,用 Eₜ₋₁ 会把季节当成意外。Eₜ₋₄ 用的是去年同一季,而且那个数字在 t 之前已经公布。 **A3.** 修订后的预期已经含有公告里的盈余。实际 1.50 对修订后的 1.50,意外是 0,当天的信号消失。这是用公告之后的信息解释公告之前的仓位。 **A4.** 申报公开之后的下一档。成交日当天,市场还没有这份 Form 4。2002 年之后,申报一般不晚于交易日后的第二个营业日结束。港股董事权益的天数以当时的规则为准,原则同样是申报晚于成交。 **B5.** 跳空 103/100 − 1 = 0.030000。收盘到 104 是 0.040000。开盘到 104 是 104/103 − 1 = 0.009709。开盘到 102 是 102/103 − 1 = −0.009709。 **B6.** 不含当期:σ = 0.110868,SUE = 2.705926。含当期:σ = 0.143178,SUE = 2.095291。 **B7.** 1.50 − 1.45 = 0.05。1.50 − 1.20 = 0.30。0.30 只有在 1.20 仍是公告前最后一版预期时才能用。 **B8.** 0.04 − (0.002000 + 1.035294 × 0.005) = 0.032824。含进公告日之后是 0.026699。β 从 1.035294 变成 1.192344。 **B9.** t = 12.4575,用的是未四舍五入的预测标准误,不是用 0.002635 再除一次。102/104 − 1 = −0.019231,这是原始收益。F-Score 是 6。 **C10–C12.** 价格和盈余是教学输入,不是历史财报。开盘改成 100 时跳空为 0。预期时间戳和 β 窗口是两条独立约束。
import numpy as np

history = np.array([0.10, -0.05, 0.20, 0.00])
sigma = history.std(ddof=1)
sue = 0.30 / sigma
sigma_leaky = np.array([0.10, -0.05, 0.20, 0.00, 0.30]).std(ddof=1)
sue_leaky = 0.30 / sigma_leaky
# 估计窗不含公告日。残差标准差用 T-2。
market = np.array([0.01, -0.02, 0.015, 0.005, -0.01])
stock = np.array([0.012, -0.018, 0.020, 0.004, -0.008])
design = np.column_stack([np.ones(5), market])
alpha, beta = np.linalg.lstsq(design, stock, rcond=None)[0]
ar = 0.04 - (alpha + beta * 0.005)
resid = stock - design @ np.array([alpha, beta])
sigma_e = np.sqrt(np.sum(resid**2) / 3)
# 预测标准误还要加上公告日市场点离估计窗均值的距离,见第 5 节的 0.002635。
脚本 `figures/M19/plot_events.py` 打印跳空和两个 SUE,不打印 t。上面这段把 t 的分子留下,分母仍按第 5 节用未四舍五入的预测标准误。 **D13.** 不能下单的是负意外的空头腿。只做多时,负 SUE 至多是不持有,权重不摊给正 SUE 的名字,除非规则事先写明要摊。论文的多空收益包含空头腿的贡献,不能直接写成你的净值。 **D14.** 收盘后的宣布,不能再用当晚已经印出的收盘价成交。最早是下一交易日的开盘或你的券商允许的下一档。生效日五天之后的价格是另一段。把宣布日收盘到生效日收盘全部记成策略收益,把跳空和持有混在了一起。 **D15.** 不要算进去。周一收盘时 Form 4 还没有公开。周三公开之后,下一档才能使用这笔申报。周一的动量若因此改变,就是用了内部人自己的成交日,而不是你能看到的申报日。

📌 章节知识总结

一句话核心洞察

  1. 收盘后公告时,从 100 到 103 的 3% 不是持仓收益。从 103 到 104 只有 0.97%。
  2. SUE 的分母只用已经公布的季节性意外。0.30 对 σ = 0.110868 是 2.705926;把当期放进分母变成 2.095291。
  3. β 用公告前的五日,异常收益是 0.032824。公告日放进回归后变成 0.026699。
  4. 纳入的平均异常收益从 1990 年代的 7.4% 降到近十年不到 1%。这不是本章教学价格的 4%。
  5. Form 4 一般在交易日后的第二个营业日。成交日的收盘不能用来交易这笔内部人买入。
  6. 单日 t = 12.4575 只相对于五天估计窗。事后窗 −0.019231 是原始收益。F-Score 为 6 不是收益。

公式与样本

对象 数值 前提
跳空 0.030000 100 到 103,公告在收盘后
收盘到生效 0.040000 假装能用 100 买到
开盘到生效 0.009709 103 到 104
开盘到其后 −0.009709 103 到 102
SUE 2.705926 历史四期,σ = 0.110868,n − 1
含当期的 SUE 2.095291 σ = 0.143178
预期差 0.05 与 0.30 1.50 对 1.45 和 1.20
异常收益 0.032824 β = 1.035294,截距 0.002000
含公告日 0.026699 β = 1.192344
事后窗原始收益 −0.019231 104 到 102,无市场收益,不是异常收益
残差标准差 0.002376 五天,除以 T − 2 = 3
预测标准误 0.002635 含估计窗不确定性
单日 t 12.4575 只相对这五天,不是论文检验
F-Score 6 九项教学打分,六项为 1
纳入 7.4% 到不到 1% Greenwood 与 Sammon,本章未重估
剔除 2010–2020 约 0.1% 同一篇,未重估
Form 4 第二个营业日 2002 年之后的一般期限

跨模块

结论 交给 位置
收盘后不能按收盘成交 时段 M18
纳入收益变薄 范式 M02
不把发表后衰减当成减半 陷阱 M08
空头腿和费用 换手 M13
复算 图 ../figures/M19/plot_events.py

学习指引

概念卡片:SUE 与盈余公告后漂移

  • 精读:Bernard 与 Thomas(1989)把漂移和风险溢价放在一起讨论。读的时候把公告日跳空从他们的持有期里分开。原文见 JAR 第 27 卷,页码 1–36。
  • 对照:4%、0.97% 和两个 SUE 以脚本为准。7.4% 与不到 1% 以 Greenwood 与 Sammon 的样本为准,不把教学价格说成他们的估计。
  • 下一步:M20 若用期权表达事件,权利金的时点同样要在公告之后。事件多空的空头腿仍然受杠杆上限 1.0 约束。

自检清单

  • 能把 100、103、104、102 算成 3%、4%、0.97% 和 −0.97%
  • 能说明 4% 里有 3 个点发生在开盘之前
  • 能把 SUE 算成 2.705926,并说明含当期分母时是 2.095291
  • 能把异常收益算成 0.032824,并说明公告日不进 β
  • 能把 1.50 对 1.45 和 1.20 分成 0.05 和 0.30
  • 能说出纳入收益从 7.4% 降到近十年不到 1%,以及 Form 4 晚于成交日
  • 能把预测标准误 0.002635 和 t = 12.4575 说成五天样本上的诊断
  • 能把 104 到 102 算成 −0.019231,并说明它还不是异常收益
  • 能把九项里六项为 1 的 F-Score 写成 6,并且不和 SUE 相加
  • 能把报表期间、申报日和可用日分开,重述公开前仍用 1.50