4、配对交易策略(Pairs Trading)

配对交易,说白了就是找两个「长得像」的资产,等它们价格跑偏了再出手。我刚开始做量化那会儿,觉得这玩意儿挺玄乎的——两个不同的股票,凭什么说它们会回归?后来踩过几次坑才明白,核心不在于它们长得像不像,而在于它们之间有没有稳定的数学关系。

配对交易的核心思想

配对交易的核心思想其实很简单:找一对相关性高的资产,做多弱的,做空强的。你想想看,如果两个资产长期走势高度一致,那它们之间的价差就应该在一个合理范围内波动。一旦价差跑出这个范围,就说明市场出现了暂时的定价错误——这时候就是我们的机会。

我记得2018年做过一个银行股的配对,招商银行和兴业银行。那段时间两者价差突然拉大,我按策略做多便宜的、做空贵的,结果一周内价差就回归了。嗯,那种感觉确实挺爽的。

核心逻辑: 配对交易是市场中性策略,不关心大盘涨跌,只关心配对资产之间的相对价格关系。

如何选择配对

选配对是个技术活。我见过有人随便找两个同行业的股票就开干,结果亏得底裤都不剩。选配对,主要看两个指标:相关性协整性

相关性

相关性衡量的是两个资产价格变动的同步程度。取值范围在-1到1之间,越接近1说明同涨同跌越明显。但这里有个坑——相关性高不代表协整性高

我曾经用两个科技股做过测试,相关系数高达0.95,结果一跑协整检验,P值0.3,根本不协整。为什么?因为相关性可能只是巧合,比如两者都受大盘影响,但本身没有稳定的长期关系。

相关系数范围 含义
0.8 - 1.0 强正相关
0.5 - 0.8 中等正相关
0.0 - 0.5 弱相关或无相关

协整性

协整性才是配对交易的灵魂。它检验的是两个时间序列之间是否存在长期稳定的线性关系。说白了,就是看它们的价差是否会在偏离后回归均值。

常用的检验方法是Engle-Granger两步法

import statsmodels.api as sm
import numpy as np

# 假设price_a和price_b是两个资产的价格序列
# 第一步:回归
X = sm.add_constant(price_b)
model = sm.OLS(price_a, X).fit()
residuals = model.resid

# 第二步:对残差做单位根检验
from statsmodels.tsa.stattools import adfuller
result = adfuller(residuals)
print(f'ADF统计量: {result[0]:.4f}')
print(f'P值: {result[1]:.4f}')

# P值小于0.05,说明残差平稳,即存在协整关系
if result[1] < 0.05:
    print('恭喜!这两个资产存在协整关系')
else:
    print('不协整,换个配对试试吧')
我的经验: 选配对时,我一般先看相关性筛选出候选池(相关系数>0.7),然后再做协整检验。这样能避免做太多无意义的协整测试,节省时间。

价差序列的构建与标准化

找到协整对之后,下一步就是构建价差序列。价差说白了就是两个资产价格的线性组合。用回归得到的系数,我们可以算出:

# 构建价差序列
spread = price_a - model.params[0] - model.params[1] * price_b

# 或者直接用回归系数
hedge_ratio = model.params[1]  # 对冲比率
spread = price_a - hedge_ratio * price_b

价差序列构建好之后,我们需要对它做标准化。为什么要标准化?因为不同时间段的价差波动幅度不一样,直接用原始价差没法设统一的阈值。

标准化的方法就是Z-score:

# 计算Z-score
mean_spread = spread.rolling(window=60).mean()
std_spread = spread.rolling(window=60).std()
z_score = (spread - mean_spread) / std_spread

Z-score告诉我们当前价差偏离均值多少个标准差。比如Z-score=2,说明当前价差比均值高了两个标准差,属于比较极端的情况。

注意: 滚动窗口的选择很关键。窗口太短,均值不稳定;窗口太长,反应迟钝。我个人习惯用60个交易日,也就是大约3个月的数据。

交易信号的生成(Z-score阈值)

有了Z-score,交易信号就水到渠成了。我们设定两个阈值:开仓阈值平仓阈值

常见的做法是:

  • 开仓信号: 当Z-score超过2或低于-2时,开仓
  • 平仓信号: 当Z-score回归到0附近(比如±0.5)时,平仓
  • 止损信号: 当Z-score超过3或低于-3时,止损

具体怎么操作?举个例子:

# 生成交易信号
def generate_signals(z_score, entry_threshold=2.0, exit_threshold=0.5):
    signals = []
    position = 0  # 0:空仓, 1:做多价差, -1:做空价差
    
    for z in z_score:
        if position == 0:
            if z > entry_threshold:
                position = -1  # 价差过高,做空价差(做空贵的,做多便宜的)
            elif z < -entry_threshold:
                position = 1   # 价差过低,做多价差(做多贵的,做空便宜的)
        else:
            if abs(z) < exit_threshold:
                position = 0   # 价差回归,平仓
            elif abs(z) > 3.0:
                position = 0   # 极端情况,止损
        signals.append(position)
    
    return signals

这里要注意一个细节:开仓方向。当Z-score为正且很大时,说明价差偏高,意味着price_a相对price_b太贵了。这时候我们应该做空price_a、做多price_b,赌价差会缩小。反过来也一样。

避坑指南: 我曾经犯过一个低级错误——把开仓方向搞反了。Z-score为正时做多了价差,结果价差继续扩大,亏得我直拍大腿。所以一定要搞清楚:Z-score正,做空价差;Z-score负,做多价差。

知识体系总览

下面这张图是我自己整理的配对交易知识体系,你可以对照着看:

配对交易策略知识体系 核心思想 做多弱的,做空强的 如何选择配对 相关性筛选 → 协整性检验 相关性 衡量同步程度 协整性 检验长期稳定关系 价差序列构建与标准化 回归残差 → Z-score标准化 交易信号生成 Z-score阈值:开仓/平仓/止损

嗯,以上就是配对交易策略的核心内容。从选配对到构建价差,再到生成信号,每一步都有讲究。我个人觉得,配对交易最难的不是技术实现,而是找到真正协整的配对——这需要大量的数据挖掘和行业知识积累。

最后说一句: 别指望每个配对都能赚钱。我做过统计,大概只有60%-70%的配对在实盘中能跑出正收益。关键是做好风控,别让一两个失败的配对把整体收益拖垮。

公众号:蓝海数据掘金营,微信deep3321