第四章:协整与配对交易
配对交易,说白了就是找两个「长得像」的品种,等它们价格走偏了再出手。我做了这么多年套利,最深的体会就是——相关性高不代表能套利。真正能赚钱的,是协整关系。
这一章,咱们就把协整检验、配对选择、对冲比率、交易框架这几个核心问题,一个一个拆开讲。
4.1 协整检验:ADF与Johansen
先问个问题:为什么不能用相关性选配对?
我举个例子。茅台和五粮液,相关性可能高达0.9。但如果你直接做多茅台、做空五粮液,很可能亏得底裤都不剩。为什么?因为它们的价差可能不回归。
协整检验,就是判断两个(或多个)时间序列的线性组合是否平稳。说白了,就是看它们的价差会不会「跑偏了再回来」。
4.1.1 ADF检验(单对协整)
ADF检验是最常用的单对协整检验方法。我个人习惯用两步法:
- 第一步:用OLS回归估计协整系数(对冲比率)
- 第二步:对残差做ADF检验,看是否平稳
代码实现很简单:
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller
# 假设price_x和price_y是两个价格序列
# 第一步:OLS回归
X = sm.add_constant(price_x)
model = sm.OLS(price_y, X).fit()
residuals = model.resid
# 第二步:ADF检验残差
adf_result = adfuller(residuals, maxlag=1)
print(f'ADF统计量: {adf_result[0]:.4f}')
print(f'p值: {adf_result[1]:.4f}')
# p值小于0.05,说明残差平稳,存在协整关系
if adf_result[1] < 0.05:
print('存在协整关系,可以配对交易')
else:
print('不存在协整关系,放弃这个配对')
4.1.2 Johansen检验(多对协整)
如果你手里有3个以上品种,想找它们之间的协整关系,ADF就不够用了。这时候要用Johansen检验。
Johansen检验的好处是:一次能检验多个协整向量。比如你选了4只银行股,Johansen检验能告诉你它们之间有几个稳定的线性组合。
from statsmodels.tsa.vector_ar.vecm import coint_johansen
# 假设data是包含多个价格序列的DataFrame
# 注意:Johansen检验要求数据是DataFrame格式
result = coint_johansen(data, det_order=0, k_ar_diff=1)
# 输出迹统计量和最大特征值统计量
print('迹统计量:', result.lr1)
print('迹统计量临界值(95%):', result.cvt)
print('最大特征值统计量:', result.lr2)
print('最大特征值临界值(95%):', result.cvm)
# 判断协整关系个数
r = 0
for i in range(len(result.lr1)):
if result.lr1[i] > result.cvt[i, 1]: # 95%置信水平
r += 1
print(f'存在{r}个协整关系')
4.2 配对选择方法
选对了配对,交易就成功了一半。我见过太多人随便选两个品种就开干,结果亏得稀里哗啦。
这里分享几个我实战中验证过的方法:
4.2.1 距离法
最简单粗暴的方法。把两个价格序列标准化,然后算欧氏距离。距离越小,说明走势越像。
from sklearn.preprocessing import StandardScaler
import numpy as np
# 标准化
scaler = StandardScaler()
price_x_scaled = scaler.fit_transform(price_x.values.reshape(-1, 1)).flatten()
price_y_scaled = scaler.fit_transform(price_y.values.reshape(-1, 1)).flatten()
# 计算欧氏距离
distance = np.sqrt(np.sum((price_x_scaled - price_y_scaled) ** 2))
print(f'距离: {distance:.4f}')
距离法快是快,但有个问题:它不考虑时间序列的动态特性。两个品种可能距离很近,但价差根本不回归。
4.2.2 协整法
这才是正路子。用ADF或Johansen检验,选出真正有协整关系的配对。
我个人习惯的做法是:
- 先用距离法初筛,选出距离最小的前50对
- 再用ADF检验,选出p值小于0.05的配对
- 最后用回测验证,看策略表现
4.2.3 最小化价差方差法
这个方法比较冷门,但我个人觉得很好用。思路是:找到使价差方差最小的对冲比率。
from scipy.optimize import minimize
def hedge_ratio_variance(ratio, price_x, price_y):
spread = price_y - ratio * price_x
return np.var(spread)
# 优化对冲比率
result = minimize(hedge_ratio_variance, x0=1.0, args=(price_x, price_y))
optimal_ratio = result.x[0]
print(f'最优对冲比率: {optimal_ratio:.4f}')
4.3 对冲比率计算
对冲比率,就是做多1单位品种A,同时做空多少单位品种B。这个比率算错了,整个策略就废了。
常用的计算方法有三种:
| 方法 | 原理 | 适用场景 |
|---|---|---|
| OLS回归 | 用最小二乘法估计系数 | 最常用,简单快速 |
| 最小方差法 | 使价差方差最小 | 价差波动大时效果好 |
| Kalman滤波 | 动态估计对冲比率 | 协整关系随时间变化时 |
我一般先用OLS算一个基准比率,然后用滚动窗口更新。比如:
# 滚动窗口计算对冲比率
window = 60 # 60个交易日
ratios = []
for i in range(window, len(price_x)):
X = sm.add_constant(price_x[i-window:i])
model = sm.OLS(price_y[i-window:i], X).fit()
ratios.append(model.params[1]) # 斜率就是对冲比率
# 用最近的对冲比率
current_ratio = ratios[-1]
print(f'当前对冲比率: {current_ratio:.4f}')
4.4 配对交易策略框架
好了,前面讲了检验、选对、算比率,现在该说怎么交易了。一个完整的配对交易框架,包含以下几个步骤:
4.4.1 策略流程
- 数据准备:获取两个品种的价格数据,清洗、对齐
- 协整检验:用ADF或Johansen确认协整关系
- 计算价差:spread = price_y - hedge_ratio * price_x
- 标准化价差:z-score = (spread - mean) / std
- 设置阈值:通常用±1.5或±2个标准差
- 开仓信号:z-score超过上阈值,做空价差;低于下阈值,做多价差
- 平仓信号:z-score回归到0附近
- 止损:z-score超过±3个标准差,强制平仓
4.4.2 代码实现
import pandas as pd
import numpy as np
class PairTrading:
def __init__(self, entry_z=1.5, exit_z=0.5, stop_z=3.0):
self.entry_z = entry_z
self.exit_z = exit_z
self.stop_z = stop_z
self.position = 0 # 1: 多头价差, -1: 空头价差, 0: 空仓
def generate_signals(self, price_x, price_y, hedge_ratio):
# 计算价差
spread = price_y - hedge_ratio * price_x
# 标准化
z_score = (spread - spread.rolling(60).mean()) / spread.rolling(60).std()
signals = pd.Series(index=price_x.index, data=0)
for i in range(1, len(z_score)):
if self.position == 0:
# 开仓信号
if z_score.iloc[i] > self.entry_z:
signals.iloc[i] = -1 # 做空价差
self.position = -1
elif z_score.iloc[i] < -self.entry_z:
signals.iloc[i] = 1 # 做多价差
self.position = 1
elif self.position == -1:
# 平仓或止损
if z_score.iloc[i] < self.exit_z:
signals.iloc[i] = 1 # 平空仓
self.position = 0
elif z_score.iloc[i] > self.stop_z:
signals.iloc[i] = 1 # 止损
self.position = 0
elif self.position == 1:
if z_score.iloc[i] > -self.exit_z:
signals.iloc[i] = -1 # 平多仓
self.position = 0
elif z_score.iloc[i] < -self.stop_z:
signals.iloc[i] = -1 # 止损
self.position = 0
return signals
4.4.3 风险控制
配对交易不是稳赚不赔的。我吃过亏,所以特别强调风控:
- 仓位管理:单笔配对交易不超过总资金的5%
- 止损:价差超过3个标准差,无条件平仓
- 再检验:每季度重新做一次协整检验,如果协整关系消失,立即停止交易
- 多配对分散:同时持有5-10个配对,降低单一配对风险
知识体系总览
下面这张图,把本章的核心逻辑串起来了:
这张图把整个流程串起来了。从数据准备开始,到协整检验,再到配对选择、对冲比率、交易框架,最后落到风控。每一步都环环相扣,缺一不可。
嗯,这一章的内容就到这儿。协整与配对交易,说白了就是找规律、算比率、设阈值、控风险。你把这些吃透了,多市场套利就算入门了。