4、配对交易策略:协整配对的选择、价差计算、交易信号生成

配对交易,说白了就是找两个「长得像」的股票,赌它们之间的价差会回归。我做了这么多年量化,觉得这是最接地气的统计套利策略之一。今天咱们就把它拆开揉碎了讲清楚。

4.1 协整配对:不是所有「长得像」都靠谱

很多人一上来就计算相关系数,觉得相关系数高的两个股票就能做配对。嗯,这里要注意——相关系数高不代表它们会一起回来

举个例子,两个股票都涨了10%,但一个是因为业绩好,一个是因为游资炒作。第二天业绩好的继续涨,游资炒作的跌回去了。这种「假相关」在配对交易里就是坑。

我个人的习惯是:先做协整检验,再做相关性分析。协整检验才是判断两个时间序列是否「长期绑定」的核心工具。

核心概念:协整关系意味着两个序列的线性组合是平稳的。说白了,就是它们之间的价差会围绕一个均值来回波动,不会越跑越远。

4.1.1 协整检验的步骤

  1. 选候选对:同行业、同板块、市值相近的股票。我一般先从沪深300里挑,流动性好,不容易滑点。
  2. 做ADF检验:对两个序列的残差做单位根检验。p值小于0.05,说明残差平稳,存在协整关系。
  3. 确定协整系数:用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 完整流程:一张图看懂

下面这张图是我自己总结的配对交易全流程,从选股到平仓,一步不落。

配对交易策略完整流程 步骤1:选候选对 同行业、市值相近 步骤2:协整检验 ADF检验,p<0.05 步骤3:计算价差 标准化处理 步骤4:设置阈值 开仓±2σ,平仓±0.5σ 步骤5:生成信号 突破开仓,回归平仓 步骤6:执行交易 买入/卖出对应股票 步骤7:风控止损 硬止损±3σ 步骤8:平仓 价差回归或止损触发 注:所有参数(σ值、窗口期)需根据具体品种和回测结果调整

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%,实盘亏成狗 用滚动窗口,参数不要固定死
交易成本 频繁交易,手续费吃掉利润 加一个最小价差阈值,减少无效交易

嗯,今天就先聊到这儿。配对交易的核心就是这些——选对股票、算准价差、信号简单、风控严格。你回去拿数据跑一跑,有什么问题咱们下次再聊。


专注资料整理