4. 价差建模与均值回归:价差的平稳性检验(ADF检验)、均值回归速度(半衰期)、Ornstein-Uhlenbeck过程简介
做统计套利,说白了就是赌价差会回归。但有个前提——你得先确认这个价差确实有回归的“习惯”。如果它像脱缰的野马一样越跑越远,那你的策略就变成接飞刀了。
我个人习惯,拿到一组价差数据后,第一件事不是建模,而是问三个问题:
- 它平稳吗?
- 回归速度有多快?
- 用什么模型来描述它?
这一章,我们就来逐一拆解。
4.1 平稳性检验:ADF检验
平稳性,简单理解就是价差的统计性质(均值、方差)不随时间变化。如果价差均值一直在漂移,那你没法判断当前是“高”还是“低”。
ADF检验(Augmented Dickey-Fuller Test)是最常用的方法。它的原假设是:序列存在单位根(即不平稳)。
我在项目中遇到过很多次,新手看到p值小于0.05就欢呼“平稳了”,其实没那么简单。ADF检验对滞后阶数很敏感,选错了阶数,结果可能完全相反。
- p值 < 0.05:拒绝原假设,序列平稳
- p值 ≥ 0.05:无法拒绝原假设,序列不平稳
- 同时检查检验统计量是否小于1%、5%、10%临界值
下面是我常用的Python实现:
import statsmodels.tsa.stattools as ts
def check_stationarity(spread):
result = ts.adfuller(spread, autolag='AIC')
print(f'ADF统计量: {result[0]:.4f}')
print(f'p值: {result[1]:.4f}')
print(f'临界值:')
for key, value in result[4].items():
print(f' {key}: {value:.4f}')
if result[1] < 0.05:
print('结论:价差平稳,适合做均值回归策略')
else:
print('结论:价差不平稳,需要差分或换标的')
4.2 均值回归速度:半衰期
假设价差现在是偏离的,它需要多久才能回到均值?这个时间就是半衰期(Half-life)。
为什么要关心这个?你想想看,如果半衰期是3天,那你可以放心持仓几天。如果半衰期是3个月,那你的资金成本可能就把利润吃掉了。
半衰期的计算,其实来自Ornstein-Uhlenbeck过程。具体公式是:
半衰期 = ln(2) / θ
其中θ是回归速度参数,可以通过线性回归估计。
我曾经踩过一个坑:用日线数据算出的半衰期是5天,但实际交易中发现价差经常2天就回归了。后来才发现,是因为数据频率太低,丢失了日内回归的细节。所以,半衰期要跟你实际交易的频率匹配。
计算代码:
import numpy as np
from sklearn.linear_model import LinearRegression
def half_life(spread):
spread_lag = spread.shift(1).dropna()
spread_diff = spread.diff().dropna()
# 对齐数据
y = spread_diff.values
x = spread_lag.values.reshape(-1, 1)
model = LinearRegression().fit(x, y)
theta = -model.coef_[0]
hl = np.log(2) / theta
return hl, theta
hl, theta = half_life(spread)
print(f'回归速度θ: {theta:.4f}')
print(f'半衰期: {hl:.2f} 天')
4.3 Ornstein-Uhlenbeck过程简介
Ornstein-Uhlenbeck过程(简称OU过程),是描述均值回归最经典的数学模型。它的数学形式是:
dX(t) = θ(μ - X(t))dt + σdW(t)
别被公式吓到,我来拆解一下:
- θ(μ - X(t))dt:这是“拉力”项。当X偏离均值μ时,θ会把X往回拉。θ越大,拉力越强。
- σdW(t):这是随机扰动项。市场总有噪音,σ控制噪音的大小。
说白了,OU过程就是:价差像一个被弹簧拴住的球,弹簧想把它拉回中心,但市场噪音一直在推它。
我在实盘中使用OU过程时,最关注三个参数:
| 参数 | 含义 | 对策略的影响 |
|---|---|---|
| μ(均值) | 长期均衡价差 | 决定开仓和平仓的参考线 |
| θ(回归速度) | 回归的强度 | 决定持仓周期和资金占用 |
| σ(波动率) | 随机噪音大小 | 决定止损宽度和仓位大小 |
拟合OU参数,除了前面提到的线性回归法,还可以用最大似然估计(MLE)。我个人更推荐MLE,因为它能同时估计所有参数,而且统计性质更好。
import numpy as np
from scipy.optimize import minimize
def ou_mle(spread):
def neg_log_likelihood(params):
mu, theta, sigma = params
n = len(spread)
dt = 1
# 计算似然函数
ll = 0
for i in range(1, n):
x_prev = spread[i-1]
x_curr = spread[i]
mean = x_prev * np.exp(-theta*dt) + mu * (1 - np.exp(-theta*dt))
var = (sigma**2 / (2*theta)) * (1 - np.exp(-2*theta*dt))
ll += -0.5 * np.log(2*np.pi*var) - (x_curr - mean)**2 / (2*var)
return -ll
# 初始值
init_params = [np.mean(spread), 0.5, np.std(spread)]
result = minimize(neg_log_likelihood, init_params, method='L-BFGS-B')
mu, theta, sigma = result.x
hl = np.log(2) / theta
return {
'mu': mu,
'theta': theta,
'sigma': sigma,
'half_life': hl
}
4.4 知识体系总览
下面这张图,把本章的核心逻辑串起来了:
从这张图可以看得很清楚:价差序列进来后,经过ADF检验确认平稳性,再计算半衰期评估回归速度,最后用OU过程完整建模。三个步骤缺一不可。
嗯,这一章的内容就到这里。记住一句话:没有经过平稳性检验的均值回归策略,就是在赌博。下一章我们会聊如何用这些参数构建具体的交易信号,到时候见。
公众号:蓝海资料掘金营,微信deep3321