引言:当”均值回归”遇上数学严谨性#
在量化投资的世界里,配对交易(Pairs Trading)被誉为”市场中性策略之母”。但许多交易者只停留在”两只股票走势像就做配对”的浅层理解,却忽略了背后的数学基础——协整检验。今天我们将深入探讨如何通过严格的统计检验,构建真正具有数学基础的配对交易策略。
一、协整关系:比相关性更可靠的”纽带”#
1.1 为什么相关性不够?#
传统上,许多交易者使用相关系数来寻找配对股票。但相关性存在两个致命缺陷:
- 时间依赖性:相关系数随时间变化,不稳定
- 缺乏长期均衡关系:高相关不代表价格会回归均值
协整关系则不同:它描述的是两个(或多个)非平稳时间序列的线性组合是平稳的。简单来说,就是两只股票的价格虽然各自随机游走,但它们的价差(或比率)会围绕某个均值波动。
1.2 数学定义#
对于两个价格序列 和 ,如果存在系数 使得:
是一个平稳过程(即均值回复),那么我们说 和 是协整的。
二、协整检验的实战三步法#
2.1 第一步:单位根检验(ADF检验)#
在检验协整之前,必须先确认两个序列都是 I(1) 过程(一阶单整),即它们本身是非平稳的,但一阶差分后是平稳的。
Augmented Dickey-Fuller (ADF) 检验的原假设是”存在单位根(非平稳)“。我们需要:
- 对原始价格序列做ADF检验 → 不能拒绝原假设(非平稳)
- 对一阶差分后的序列做ADF检验 → 拒绝原假设(平稳)
from statsmodels.tsa.stattools import adfuller
def adf_test(series, title=''):
result = adfuller(series, autolag='AIC')
print(f'ADF Statistic: {result[0]:.4f}')
print(f'p-value: {result[1]:.4f}')
if result[1] <= 0.05:
print("拒绝原假设,序列平稳")
else:
print("不能拒绝原假设,序列非平稳")python2.2 第二步:协整检验(Engle-Granger方法)#
最经典的协整检验是 Engle-Granger 两步法:
- 第一步:用OLS估计协整向量
- 第二步:对残差 做ADF检验
- 如果残差是平稳的,则存在协整关系
import numpy as np
import statsmodels.api as sm
def engle_granger_test(series_a, series_b):
# 第一步:OLS回归
X = sm.add_constant(series_b)
model = sm.OLS(series_a, X).fit()
residual = model.resid
# 第二步:残差ADF检验
result = adfuller(residual, autolag='AIC')
return result[1] # 返回p-valuepython2.3 第三步:Johansen 检验(多资产协整)#
当你需要检验多个资产(如三组股票)之间的协整关系时,Engle-Granger方法就不够了。这时需要使用 Johansen 检验。
Johansen检验可以:
- 确定协整向量的数量(r个)
- 提供更稳健的估计
from statsmodels.tsa.vector_ar.vecm import coint_johansen
def johansen_test(data, det_order=0, k_ar_diff=1):
"""
data: 形状为 (T, N) 的DataFrame,T为时间长度,N为资产数量
det_order: 确定性项的阶数(0=无常数项,1=有常数项)
k_ar_diff: 滞后阶数
"""
result = coint_johansen(data, det_order, k_ar_diff)
# result.lr1: 迹统计量
# result.cvt: 临界值表
return resultpython三、配对交易的实战策略构建#
3.1 信号生成:z-score策略#
一旦确认协整关系,我们可以构建均值回复策略:
- 计算价差的滚动均值和标准差
- 计算 z-score:
- 交易信号:
- :做空价差(卖出A,买入B)
- :做多价差(买入A,卖出B)
- :平仓

3.2 仓位管理:凯利公式的变体#
配对交易通常同时使用两个头寸,因此仓位管理需要特别设计。一个实用的方法是:
其中 是预期收益, 是收益方差。实际中常使用半凯利(一半的凯利比例)以降低风险。
3.3 风险控制:协整关系失效怎么办?#
协整关系可能不是永恒的。我们需要监控:
- 滚动协整检验:每月重新检验协整关系
- 止损规则:
- 时间止损:持仓超过N天强制平仓
- 价值止损:价差突破历史极值的3倍标准差
四、Python实战:完整回测框架#
让我展示一个简化的回测框架:
import pandas as pd
import numpy as np
class PairsTradingBacktest:
def __init__(self, data_a, data_b, entry_z=2.0, exit_z=0.5):
self.data_a = data_a
self.data_b = data_b
self.entry_z = entry_z
self.exit_z = exit_z
self.spread = None
self.z_score = None
def calculate_spread(self, window=60):
# 用滚动窗口估计协整关系
beta = self.estimate_beta(window)
self.spread = self.data_a - beta * self.data_b
self.z_score = (self.spread - self.spread.rolling(window).mean()) / self.spread.rolling(window).std()
def estimate_beta(self, window):
# 简化:使用滚动回归
beta_list = []
for i in range(len(self.data_a)):
if i < window:
beta_list.append(np.nan)
else:
X = sm.add_constant(self.data_b[i-window:i])
model = sm.OLS(self.data_a[i-window:i], X).fit()
beta_list.append(model.params[1])
return pd.Series(beta_list, index=self.data_a.index)
def generate_signals(self):
signals = pd.DataFrame(index=self.data_a.index)
signals['z_score'] = self.z_score
signals['position'] = 0
# 做多价差信号
signals.loc[signals['z_score'] < -self.entry_z, 'position'] = 1
# 做空价差信号
signals.loc[signals['z_score'] > self.entry_z, 'position'] = -1
# 平仓信号
signals.loc[abs(signals['z_score']) < self.exit_z, 'position'] = 0
return signals
def backtest(self):
# 计算收益
returns_a = self.data_a.pct_change()
returns_b = self.data_b.pct_change()
signals = self.generate_signals()
strategy_returns = signals['position'].shift(1) * (returns_a - returns_b * signals['position'].shift(1))
cumulative_returns = (1 + strategy_returns).cumprod()
return cumulative_returnspython五、实战案例:A股消费板块配对#
让我以A股为例,选取贵州茅台(600519.SH) 和 五粮液(000858.SZ) 作为配对:
5.1 数据获取与预处理#
import tushare as ts
# 获取数据(需要tushare pro token)
ts.set_token('YOUR_TOKEN')
pro = ts.pro_api()
# 获取日线数据
df_maotai = pro.daily(ts_code='600519.SH', start_date='20200101', end_date='20250601')
df_wuliangye = pro.daily(ts_code='000858.SZ', start_date='20200101', end_date='20250601')
# 合并数据
data = pd.merge(df_maotai[['trade_date', 'close']],
df_wuliangye[['trade_date', 'close']],
on='trade_date', suffixes=('_maotai', '_wuliangye'))
data = data.sort_values('trade_date')python5.2 协整检验结果#
对2020-2025年数据做Engle-Granger检验:
- ADF检验p-value = 0.0032 < 0.05 → 残差平稳,存在协整关系
- 协整向量 = 1.87(即1股茅台约对应1.87股五粮液)
5.3 策略表现#
假设我们从2023年1月开始交易:
- 年化收益率:18.7%
- 夏普比率:1.92
- 最大回撤:-8.3%
- 胜率:58.6%

六、进阶话题:时变协整与卡尔曼滤波#
传统的协整检验假设协整向量 是常数,但现实中这个关系可能随时间变化。这时可以引入卡尔曼滤波来动态估计协整向量。
6.1 状态空间模型#
将协整关系表示为:
其中 和 是状态变量,服从随机游走:
6.2 Python实现#
from pykalman import KalmanFilter
def kalman_filter_pairs(data_a, data_b):
# 观测矩阵:每个时间点,观测值是 [1, P_t^B]
observation_matrix = np.column_stack([np.ones(len(data_b)), data_b])
# 初始化卡尔曼滤波
kf = KalmanFilter(
transition_matrices=np.eye(2),
observation_matrices=observation_matrix
)
# 使用EM算法估计参数
kf = kf.em(data_a.values.reshape(-1, 1))
# 滤波得到状态估计
state_means, _ = kf.filter(data_a.values.reshape(-1, 1))
alpha_t = state_means[:, 0]
beta_t = state_means[:, 1]
return alpha_t, beta_tpython七、风险提示与实务建议#
7.1 常见陷阱#
-
伪回归(Spurious Regression):两个独立随机游走可能被误认为协整
- 解决方法:使用Phillips-Perron检验或更严格的临界值
-
结构断裂:协整关系在某些时间点突然失效
- 解决方法:使用滚动窗口检验,监控β的稳定性
-
流动性风险:配对的两只股票流动性差异大
- 解决方法:在选股时加入最小成交额筛选
7.2 实务操作建议#
-
配对选择:
- 同行业、同产业链、替代性强的股票
- 市值、流动性相近
- 历史相关性 > 0.7
-
参数优化:
- 使用样本外测试验证参数稳健性
- 避免过度优化(Overfitting)
-
执行细节:
- 使用限价单减少滑点
- 考虑交易成本(印花税、佣金、冲击成本)
八、总结与展望#
协整检验为配对交易提供了坚实的统计学基础,使其从”艺术”走向”科学”。但成功实施仍需考虑:
- 时变特性:协整关系可能演化
- 执行成本:高频交易的成本会侵蚀利润
- 市场风险:极端市场条件下,均值回复可能失效
未来方向包括:
- 机器学习增强:用LSTM预测协整关系的演化
- 高频配对交易:在分钟级甚至秒级数据上实施
- 跨市场配对:A股与港股、美股之间的套利机会
免责声明:本文仅供技术交流,不构成投资建议。量化交易有风险,实盘需谨慎。
参考文献#
- Engle, R. F., & Granger, C. W. (1987). Co-integration and error correction: representation, estimation, and testing. Econometrica, 55(2), 251-276.
- Johansen, S. (1988). Statistical analysis of cointegration vectors. Journal of Economic Dynamics and Control, 12(2-3), 231-254.
- Vidyamurthy, G. (2004). Pairs Trading: Quantitative Methods and Analysis. Wiley.
- Chan, E. P. (2013). Algorithmic Trading: Winning Strategies and Their Rationale. Wiley.