高频数据里最让人头疼的一件事:一根一分钟 bar 涨了 1.5%,这到底是「真实跳变(jump)」——有信息、有方向、值得反应——还是只是微观结构噪声 + 当时本来就大的波动里抖了一下? 把噪声当跳变去追,等于在噪音里高频交易、交手续费;把真跳变当噪声忽略,又会在重大事件上裸奔。
本文聊一个干净又实用的检验:Lee & Mykland (2008) 的日内跳跃检测。它的核心只有一句话——用「过去 K 分钟局部波动率」去缩放这一分钟的收益,得到统计量 L(r_t)=r_t/ŝ_{t-1};在无跳变假设下 L 近似标准正态 N(0,1),于是 |L| 超过正态临界值就判为跳变。难点不在公式,而在「局部波动率」——它让检验在动荡市自动抬高门槛,不像固定阈值那样在波动大的时候疯狂误报。
我们用一天 390 分钟(9:30–16:00)的分钟级对数收益、三段波动 regime(平静 σ=0.07% / 正常 σ=0.14% / 动荡 σ=0.30%)、人工注入 5 个真跳变来验证:LM 命中 5/5、仅 1 误报;而朴素固定阈值(用平静市 4σ 标定)虽然也命中 5/5,却误报 10 个,其中 9 个全在动荡段。
一、为什么「固定阈值」不行#
最直觉的做法:用全样本或平静市的 σ 定一个阈值(比如 4σ),|r_t|>4σ 就当跳变。它的问题在于波动率会聚类——一天里平静段和动荡段 σ 能差 4 倍。用平静市 σ 去标定,动荡段里「正常的波动抖动」会大量越过 4σ,检测器在下午(动荡)狂报假跳变;反过来用动荡市 σ 标定,平静段的真跳变又被淹没。
Lee-Mykland 的改进在于:阈值不是固定的,而是「这一分钟相对它自己附近的波动率」有多大。它问的不是「r_t 绝对值大不大」,而是「r_t 相对当前本地波动是不是异常大」。这才是「区分跳变 vs 噪声」该问的问题。
二、统计量与检验#
记第 t 分钟对数收益为 r_t。LM 先估计一个局部波动率 ŝ_{t-1}:用过去 K 分钟(不含当前)的平方收益滚动求标准差。
import numpy as np
def lee_mykland(ret, K=30):
"""Lee-Mykland 跳跃检测。返回 L 统计量、局部波动率、被标记时刻。"""
n = len(ret)
locvol = np.full(n, np.nan)
for t in range(K, n):
locvol[t] = np.sqrt(np.mean(ret[t - K:t] ** 2)) # ŝ_{t-1}
L = np.full(n, np.nan)
for t in range(K, n):
L[t] = ret[t] / locvol[t] # 局部缩放的检验量
return L, locvol
def norm_ppf(p):
"""标准正态分位函数(Acklam 逼近,纯 numpy 实现)。"""
a = [-3.969683028665376e+01, 2.209460984245205e+02, -2.759285104469687e+02,
1.383577518672690e+02, -3.066479806614716e+01, 2.506628277459239e+00]
b = [-5.447609879822406e+01, 1.615858368580409e+02, -1.556989798598866e+02,
6.680131188771972e+01, -1.328068155288572e+01]
c = [-7.784894002430293e-03, -3.223964580411365e-01, -2.400758277161838e+00,
-2.549732539343734e+00, 4.374664141464968e+00, 2.938163982698783e+00]
d = [7.784695709041462e-03, 3.224671290700398e-01, 2.445134137142996e+00,
3.754408661907416e+00]
plow, phigh = 0.02425, 1 - 0.02425
if p < plow:
q = np.sqrt(-2 * np.log(p))
return (((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) / \
((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1)
if p > phigh:
q = np.sqrt(-2 * np.log(1 - p))
return -(((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) / \
((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1)
q = p - 0.5; r = q * q
return (((((a[0]*r+a[1])*r+a[2])*r+a[3])*r+a[4])*r+a[5]) * q / \
(((((b[0]*r+b[1])*r+b[2])*r+b[3])*r+b[4])*r+1)
# 多重检验校正(Bonferroni):一天 n_eff 个检验,整体 α 下每步阈值收紧
n_eff = N - K
alpha = 0.05
c = norm_ppf(1 - alpha / (2 * n_eff)) # c ≈ 3.81
detected = [t for t in range(K, N) if abs(L[t]) > c]python为什么 L≈N(0,1): 在「无跳变、局部波动近似恒定」的假设下,r_t≈σ_t·ε_t 且 ε_ti.i.d. 近似标准正态;用 ŝ_{t-1}(上一窗口的 σ 估计)缩放后,L_t≈ε_tN(0,1)。一旦某分钟有真实跳变,r_t 多了一大块离散位移,L_t 就会被撑得很大,远离正态分布——这就是检验能抓到的「异常」。
多重检验校正: 一天要做 n_eff 次检验(每次一分钟一个),若每次用 5% 单点阈值,一天预期误报 n_eff×5%≈18 次。LM 用 Bonferroni 把整体 α 摊到每一步:c=Φ⁻¹(1−α/(2·n_eff))。当 n_eff=360、α=0.05 时,c≈3.81(比单点 1.96 严得多),把「纯噪声随机越界」的预期次数压到接近 α。
三、数据:埋 5 个跳,跨三段 regime#
一天 390 分钟,波动率聚类成平静/正常/动荡三段;在第 45、150、250、330、370 分钟注入 5 个真实跳变(±1.2%~2.0%):

红点就是注入的跳变。注意第 330、370 分钟那两个跳变落在动荡段——背景波动本身就大,这正是「固定阈值最容易误报」的区域。
四、LM 统计量:5 个跳变清清楚楚#
把 L 统计量画出来,叠加 ±c 临界带:

| 真实跳变(分钟) | 幅度 | L 值 | LM 抓到? |
|---|---|---|---|
| 45 | +1.2% | +12.80 | ✅ |
| 150 | −1.6% | −10.65 | ✅ |
| 250 | +2.0% | +12.68 | ✅ |
| 330 | −1.4% | −4.39 | ✅ |
| 370 | +1.3% | +4.07 | ✅ |
5 个真实跳变的 L 值全部远超临界值 c=3.81(最小也有 4.07),且一个都在动荡段的跳变也被稳稳抓住——因为 LM 用「本地波动」缩放,动荡段门槛自动抬高,跳变相对本地波动依然「异常大」。全天 LM 只标记了 6 次(5 真 + 1 误报),误报率极低。
五、局部波动率:门槛会随市场呼吸#
LM 的灵魂是 ŝ_{t-1}。下图把真实波动率和 LM 的局部估计叠一起:

估计的局部波动率(绿)紧贴真实波动率(黑)走——平静段低、动荡段高。这意味着检验的「标尺」在随市场呼吸:动荡时分母大、门槛自然抬高,普通抖动过不了关;平静时分母小、同样幅度的跳变更容易被识别。这就是它比固定阈值强的地方。
六、LM vs 朴素阈值:动荡段的屠杀#
用平静市 σ 标定一个固定 4σ 阈值(naive_thr≈0.0051),和 LM 同场对比:

| 检测器 | 命中真跳 | 误报 | 总标记 | 其中动荡段误报 |
|---|---|---|---|---|
| Lee-Mykland | 5/5 | 1 | 6 | 0 |
| 朴素固定阈值(4σ 平静市) | 5/5 | 10 | 15 | 9 |
同是命中 5/5 真跳,差距在误报:朴素阈值在动荡段(灰底区域)炸出 9 个误报——它用平静市的小 σ 去卡动荡市的大波动,当然满屏假信号。LM 因为局部缩放,动荡段几乎零误报。在实盘里,这 9 个误报意味着 9 次「假跳变触发交易」,每次都是白交手续费 + 可能的反向亏损。
七、五类真实陷阱#
- K 窗长两头难:K 太小(如 5),ŝ 估计抖、分母不稳,L 会被噪声放大,误报激增;K 太大(如 100),ŝ 跟不上波动率的 regime 切换,动荡初段门槛还停在平静水平,漏检真跳。K=30(约半小时)是经验甜点,但必须按你的数据频率样本外调,别照搬。
- 局部波动率自身在跳变时被污染:我们用的是 ŝ_{t-1}(不含当前 t 的窗口),这点很关键——若把当前分钟算进窗口,真跳变会撑大 ŝ_t,反而把 L_t 压小、导致漏检。务必用 t 之前 的窗口。
- 微结构噪声让 r_t 本身不干净:真实 Tick/分钟收益含买卖价差跳动(bid-ask bounce)、非同步交易,r_t 的「噪声」比纯 GBM 大。这会系统性抬高 ŝ,使 L 偏小、漏检温和跳变。落地前常先做 中位数去趋势 / 用超高频收益构造已实现方差 再喂进 LM。
- 多重检验校正的代价:Bonferroni 把 c 从 1.96 拉到 3.81,误报是少了,但小跳变(L 在 2~3.8 之间)会被漏掉。若你关心的是「中等强度跳变」,可换 BH 校正或分时段独立校正,在误报和漏检间取折中。
- 合成数据局限:本序列是「分段恒定 σ + 高斯噪声 + 离散跳」的自洽合成,只为干净演示 LM 的局部缩放优势。真实高频收益有厚尾、波动聚集更强、且跳变常伴随后续反转(jumper 之后的均值回复);落地须接入真实 Tick/分钟数据、先做去噪、并对检测结果做样本外召回率/精确率评估,且单日跳变检测不该直接当交易信号,应作为风控/事件标记层。
八、结语#
Lee-Mykland 给高频研究一个「廉价又稳健」的跳变探针:
- L(r_t)=r_t/ŝ_{t-1} 用本地波动缩放,把「绝对值大」换成「相对本地异常大」,动荡市自动抬高门槛;
- Bonferroni 校正多重检验,c≈3.81 把全天纯噪声误报压到接近 α;
- 在我们的合成里,它命中 5/5 真跳、仅 1 误报,而朴素固定阈值误报 10 个(动荡段 9 个)。
和之前聊过的 VPIN(订单流毒性)、HMM 市场状态识别放一起看:VPIN 度量「订单流性质」、HMM 识别「regime 切换」、LM 定位「瞬时跳变时刻」——三者都在「价格之外的维度」上帮你看清市场到底发生了什么。LM 最适合当事件标记层:把跳变分钟打上标签,喂给波动率模型(跳变日要降杠杆)、事件研究(跳变后是否反转)、或实时风控(异常跳变触发预警)。
注:本文收益序列为自洽合成数据(分段恒定波动率 + 高斯噪声 + 离散跳变),用于演示 LM 的局部波动率缩放与多重检验校正;真实高频数据需先做买卖价差去噪、用 t 之前窗口估计 ŝ、并对检测结果做样本外精确率/召回率评估。