4、配对交易策略:协整配对的选择、价差计算、交易信号生成
配对交易,说白了就是找两个「长得像」的股票,赌它们之间的价差会回归。我做了这么多年量化,觉得这是最接地气的统计套利策略之一。今天咱们就把它拆开揉碎了讲清楚。
4.1 协整配对:不是所有「长得像」都靠谱
很多人一上来就计算相关系数,觉得相关系数高的两个股票就能做配对。嗯,这里要注意——相关系数高不代表它们会一起回来。
举个例子,两个股票都涨了10%,但一个是因为业绩好,一个是因为游资炒作。第二天业绩好的继续涨,游资炒作的跌回去了。这种「假相关」在配对交易里就是坑。
我个人的习惯是:先做协整检验,再做相关性分析。协整检验才是判断两个时间序列是否「长期绑定」的核心工具。
核心概念:协整关系意味着两个序列的线性组合是平稳的。说白了,就是它们之间的价差会围绕一个均值来回波动,不会越跑越远。
4.1.1 协整检验的步骤
- 选候选对:同行业、同板块、市值相近的股票。我一般先从沪深300里挑,流动性好,不容易滑点。
- 做ADF检验:对两个序列的残差做单位根检验。p值小于0.05,说明残差平稳,存在协整关系。
- 确定协整系数:用OLS回归,得到两个股票的权重。这个权重就是配对的比例。
避坑指南:我曾经遇到过两个股票ADF检验通过了,但实际回测一塌糊涂。后来发现是样本期选得太短,刚好赶上它们「假协整」。现在我至少用2年的日线数据做检验,稳健很多。
4.2 价差计算:把两个股票「拧成一股绳」
协整关系确认后,下一步就是算价差。价差就是两个股票按协整系数加权后的差值。
公式其实很简单:
价差 = 股票A价格 - β × 股票B价格
其中β就是协整系数。你想想看,如果两个股票长期绑定,这个价差就应该在0附近来回晃悠。
但实际中,价差往往不是0均值的。所以我习惯先做标准化处理:
标准化价差 = (当前价差 - 历史均值) / 历史标准差
标准化后的价差,单位就是「几个标准差」。这样设置阈值就方便多了——比如±2个标准差开仓,±0.5个标准差平仓。
注意:历史均值和标准差要用滚动窗口计算。我一般用60个交易日,太长反应慢,太短容易过拟合。这个参数我调了不下50次才找到感觉。
4.3 交易信号生成:什么时候进场,什么时候跑
信号生成是策略的灵魂。我见过太多人把信号搞得太复杂,结果回测漂亮,实盘一塌糊涂。
我的原则是:信号越简单,越不容易过拟合。
4.3.1 开仓信号
- 价差突破上轨(比如+2σ):做空价差,即卖出股票A,买入股票B
- 价差跌破下轨(比如-2σ):做多价差,即买入股票A,卖出股票B
为什么会这样?因为价差偏离均值太远,大概率会回归。你想想看,两个协整的股票,价差不可能永远在天上飘着。
4.3.2 平仓信号
- 价差回归到均值附近(比如±0.5σ):平仓离场
- 价差继续偏离,触发止损(比如±3σ):认赔出局
个人经验:止损线一定要设。我曾经有一笔配对交易,价差从2σ一直跑到4σ,亏了8%才回来。从那以后,我所有策略都加上了硬止损。
4.4 完整流程:一张图看懂
下面这张图是我自己总结的配对交易全流程,从选股到平仓,一步不落。
4.5 代码实现:从理论到实战
光说不练假把式。下面是我常用的配对交易信号生成代码,Python写的,直接用:
import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import adfuller
import statsmodels.api as sm
def find_cointegrated_pairs(data, p_value_threshold=0.05):
"""
寻找协整配对
data: DataFrame,每列是一只股票的价格序列
"""
n = data.shape[1]
pairs = []
for i in range(n):
for j in range(i+1, n):
# OLS回归
X = sm.add_constant(data.iloc[:, i])
y = data.iloc[:, j]
model = sm.OLS(y, X).fit()
# 对残差做ADF检验
residuals = model.resid
adf_result = adfuller(residuals)
p_value = adf_result[1]
if p_value < p_value_threshold:
pairs.append((data.columns[i], data.columns[j], model.params[1], p_value))
return pairs
def generate_signals(price_a, price_b, beta, window=60, entry_z=2.0, exit_z=0.5):
"""
生成交易信号
price_a: 股票A价格序列
price_b: 股票B价格序列
beta: 协整系数
"""
# 计算价差
spread = price_a - beta * price_b
# 滚动标准化
spread_mean = spread.rolling(window=window).mean()
spread_std = spread.rolling(window=window).std()
z_score = (spread - spread_mean) / spread_std
# 生成信号
signals = pd.DataFrame(index=price_a.index)
signals['z_score'] = z_score
signals['position'] = 0
# 开仓条件
signals.loc[z_score > entry_z, 'position'] = -1 # 做空价差
signals.loc[z_score < -entry_z, 'position'] = 1 # 做多价差
# 平仓条件
signals.loc[abs(z_score) < exit_z, 'position'] = 0
# 止损条件
signals.loc[z_score > 3.0, 'position'] = 0
signals.loc[z_score < -3.0, 'position'] = 0
return signals
避坑指南:我曾经直接用原始价差做信号,结果回测曲线漂亮得像假的一样。后来才发现,价差的均值和标准差会随时间漂移。用滚动窗口标准化后,信号才真正稳定下来。
4.6 实战中的几个坑
做配对交易这几年,我踩过的坑比走过的路还多。挑几个典型的说说:
| 坑 | 表现 | 解决方案 |
|---|---|---|
| 协整关系失效 | 价差越跑越远,不回均值 | 定期重新检验协整关系,比如每季度一次 |
| 滑点太大 | 信号出来了,但成交价差很多 | 用限价单,或者只做流动性好的股票 |
| 过拟合参数 | 回测年化50%,实盘亏成狗 | 用滚动窗口,参数不要固定死 |
| 交易成本 | 频繁交易,手续费吃掉利润 | 加一个最小价差阈值,减少无效交易 |
嗯,今天就先聊到这儿。配对交易的核心就是这些——选对股票、算准价差、信号简单、风控严格。你回去拿数据跑一跑,有什么问题咱们下次再聊。