halo 的技术博客

返回

你花了两周筛出一对「看起来锁在一起」的股票:招商银行和平安银行,走势像照镜子。你算出 Y ≈ 0.9·X + 残差,然后盯着那条残差——它今天跑到 +5%,你心里痒:该不该反手做空价差、赌它跌回 0?

这个「赌它回去」的冲动,就是统计套利的全部。但「回去」是一个动词,它藏着三个必须量化的子问题:

  1. 真的会回去吗?(是均值回复,还是只是随机游走碰巧回来?)
  2. 多快回去?(今天 +5%,是明天回 0,还是三年后回 0?)
  3. 怎么做这个「回去」?(信号、仓位、平仓、成本)

把这三个问题装进一个数学身体,就是 Ornstein-Uhlenbeck(OU)过程。本文把整套逻辑从方程推到可运行的回测,并用自包含合成数据把「配对到底赚不赚钱」跑出数字来。

一、OU 过程:把「均值回复」写成一个方程#

随机游走(比如股价)的离散形式是:

x_t = x_{t-1} + σ·ε_t          # 没有向心力,走哪算哪
plaintext

OU 过程多了一根「弹簧」——一个把 x 拉回中枢 θ 的力:

dx_t = κ·(θ − x_t)·dt + σ·dW_t
plaintext
  • θ:长期中枢(价差围绕它波动)
  • κ(kappa):回归速度,弹簧的刚度。κ 越大,拉回越快
  • σ:冲击强度(噪声)
  • dW_t:标准布朗运动

离散化(令步长 dt=1)就是大家最熟悉的自回归形式:

x_t = θ + b·(x_{t-1} − θ) + σ·ε_t ,   b = exp(−κ·dt)
plaintext

注意 b 是 AR(1) 的系数。b < 1 就是均值回复的铁证;如果 b ≈ 1,那就是随机游走(单位根),「回去」只是幻觉。

半衰期:给「回去」一把尺子#

b = exp(−κ·dt) 反推:

κ = −ln(b) / dt
半衰期 HL = ln(2) / κ = −dt·ln(2) / ln(b)
plaintext

半衰期 = 价差偏离后,回落到一半距离所需的平均时间。 这是配对交易里最重要的一个数字:HL 太短(比如几天),信号频繁但你被手续费吃死;HL 太长(比如一年),你赌的是一笔三年后才兑现的钱。

协整对:Y 锁定在 beta·X 附近,偏离即价差,价差服从 OU 过程

上图:顶部是协整对 X/Y 的价格(长期锁定),中间是价差 spread(围绕 0 上下波动的 OU 过程),底部是把价差标准化成的 z-score。注意中间那条的「弹簧感」——偏离 0 越远,被拉回的力越明显。

二、从数据里把 κ 和 HL 挖出来#

你拿到一条真实的价差序列,怎么知道它是不是 OU、κ 多大?用 AR(1) 斜率法

数字对得上我们注入的真值(κ=0.05、HL≈13.9 步),误差来自有限样本噪声——这正是后面「小样本偏差」陷阱要聊的。

三、把「回去」做成交易信号#

有了 z-score,信号规则极简:偏离越远越该反手,回到中枢附近就平仓。

三个设计要点:

  • 信号在 t-1 生成、t 执行:图里看到的 z 值不能「穿越」到同根 K 线用,否则就是前视偏差(最致命的陷阱,见第五节)。
  • 对称阈值±2 开仓、±0.5 平仓,留一个缓冲带避免反复左右打脸(尚未回到中枢就又触发)。
  • 盯市结算:持仓期间每天按价差变化记盈亏,空价差在价差回落时赚钱,逻辑闭环。

OU 均值回复交易:|z|>2 开仓、|z|<0.5 平仓,绿色点为开仓、蓝色三角为平仓

上图:z-score 在 ±2 阈值外开仓(绿点),回到 ±0.5 内平仓(蓝三角)。能看出开平仓点大多落在 z 的极值附近——这正是「均值回复」在吃波动的钱。

四、回测结果:从零漂移里榨出收益#

pos, strat_eq = backtest(spread, entry=2.0, exit_z=0.5)
print(f"策略终值={strat_eq[-1]:.3f}")   # 1.098

# 基准:持有现金(价差本身零漂移,持有它无收益)
cash = np.ones_like(strat_eq)
python

净值对比:策略从零漂移价差里榨出约 9.8% 收益,而持有现金不动

在这个 600 步的合成样本里,价差本身围绕 0 波动、长期零漂移(你持有它不会赚钱),而 OU 配对策略把偏离-回复的波动转化成了约 9.8% 的累计净值。这就是统计套利的本质:不赌方向,只赌「涨太狠会跌回来、跌太狠会弹回去」这个结构性的弹簧效应

关键点:策略的 PnL 来自价差围绕中枢的往复,与 X/Y 的共同趋势无关——它是市场中性的。所以你不能拿「买入持有 Y 涨了 300%」来比,那是在比方向暴露,不是比套利。

五、四类真实陷阱(不写这些就是骗自己)#

陷阱 1:协整关系会断(最致命)#

本文合成数据假设 β 和协整关系永远成立。真实世界里,两只股票的基本面会在某天分叉:一家重组、一家暴雷,价差从此「漂走」不再回来。你盯着 +5% 做空,它涨到 +50% 才停——因为弹簧断了。 对策:用滚动窗口重估协整(Engle-Granger / Johansen),关系断裂(p-value 失效或 β 跳变)立刻止损平仓,绝不和一条不回来的价差死磕。

陷阱 2:β 是时变的,固定对冲比会漏风#

Y = β·X + spread 里的 β 真实是缓慢漂移的。用全样本固定 β 锁价差,等于用一根会伸缩的尺子量长度——价差里混进了 X 的方向性波动,你的「中性」其实是「半裸多头」。 对策:用卡尔曼滤波(状态空间模型)在线更新 β,让对冲比逐日跟踪(本专栏有《卡尔曼滤波配对交易》专文)。

陷阱 3:手续费和冲击吞掉薄利#

本文 fee=0.0008(单边万八)。统计套利的每笔收益本来就薄(几个 bp),一旦你把 entry 阈值调小、交易变频繁,手续费会把正期望啃成负的。真实里还有买卖价差、冲击成本、做空借券费。 对策:把全成本(佣金+价差+冲击+借券)写进回测,并用「换手率 × 单笔成本」反推能 cover 的最低 HL——HL 太短的策略,光成本就判了死刑。

陷阱 4:前视偏差(look-ahead)#

最容易犯也最隐蔽:用「全样本均值/标准差」去标准化 z-score,等于让第 1 天的信号偷偷看到了第 600 天的信息。对策:所有统计量(均值、std、甚至 β)都必须用截至 t-1 的滚动窗口计算,信号在 t-1 收盘后生成、t 开盘执行——和本文代码里的 z[t-1] 一致。

六、落地路径(从玩具到实盘)#

本文数字来自自洽合成(OU 离散化),用于演示机制。真要做,路径是:

  1. 选对:用距离法/协整得分从同行业筛候选对,再用 ADF/KPSS 验证残差是平稳 OU(不是单位根)。
  2. 估参数:滚动 AR(1) 拿 b → κ → HL,HL 落在「几周到几个月」的对子才值得做。
  3. 建信号:用滚动 z-score(window=60~120),开仓 ±2~2.5、平仓 ±0.5。
  4. 控成本:把借券费、冲击写进回测;按波动率目标化控制每对敞口。
  5. 防断裂:监控协整 p-value 与 β 漂移,断则撤。
  6. 多对组合:单对容量小、回撤尖,用 20~50 对低相关对子分散,把单对断裂的坑抹平。

小结:OU 过程给统计套利提供了一把尺子——κ 量回归速度、HL=ln2/κ 量回去要多久。但尺子量的是历史,真实价差会断、β 会漂、成本会吞利。把这套方程跑通只是第一步,把四类陷阱写进回测,才算摸到了实盘的门槛。

最后更新:2026-07-18

OU 过程均值回复:用 Ornstein-Uhlenbeck 给配对价差做统计套利
https://blog.halo26812.eu.org/blog/mean-reversion-ou
Author halo
Published at 2026年7月18日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨