4. 回归法策略:OLS回归估计对冲比率、残差序列的均值回复特性、Z-score信号生成

好,咱们进入配对交易的核心环节——回归法策略。

说实话,很多新手一上来就搞协整检验,觉得高大上。但我个人习惯,先跑一遍OLS回归,看看两个资产到底什么关系。这就像谈恋爱,先看看性格合不合,再谈能不能结婚。

4.1 OLS回归估计对冲比率

配对交易的本质,是找到两个价格走势高度相关的资产。它们之间的价差,就是我们的利润来源。

怎么量化这个关系?用OLS回归。

假设我们有两支股票,A和B。我们想用A去对冲B。那么回归方程就是:

B = α + β × A + ε

这里的β,就是我们要找的对冲比率。说白了,就是每持有1股A,需要持有多少股B,才能把系统性风险对冲掉。

核心要点:β不是随便算的。它代表的是两个资产之间的线性关系。如果β=1.5,意味着A涨1块钱,B平均涨1.5块钱。反过来,你空头1股A,就要多头1.5股B,才能保持中性。

代码实现其实很简单。我拿Python的statsmodels库举个例子:

import statsmodels.api as sm
import numpy as np

# 假设price_A和price_B是两列价格序列
X = sm.add_constant(price_A)  # 加截距项
model = sm.OLS(price_B, X)
results = model.fit()

beta = results.params[1]  # 这就是对冲比率
alpha = results.params[0] # 截距项
residuals = results.resid  # 残差序列

print(f"对冲比率 β = {beta:.4f}")
print(f"截距 α = {alpha:.4f}")

嗯,这里要注意一点。我刚开始做的时候,直接拿原始价格去回归,结果发现β一直在变。后来才意识到,价格序列是非平稳的,回归结果不可靠。

避坑指南:我曾经用日频数据跑OLS,β算出来是1.2,觉得挺稳。结果换到周频数据,β变成了0.8。为什么?因为时间频率不同,两个资产的相关性结构会变。我建议你至少用3种频率验证一下,看看β是否稳定。

4.2 残差序列的均值回复特性

回归做完,我们得到了残差序列。这个残差,就是价差——两个资产之间没有被对冲掉的部分。

配对交易赚钱的逻辑,就是赌这个残差会回归到均值。说白了,残差高了就做空,残差了就做多。

但问题来了:残差真的会均值回复吗?

不一定。有些配对,残差跑着跑着就飞了,再也不回来。这就是所谓的「伪配对」。

怎么判断?我一般看两个指标:

  • 自相关系数(ACF):如果残差的一阶自相关系数接近0,说明它像个白噪声,没有记忆性,很难回复。如果一阶自相关系数在0.5以上,说明有较强的回复倾向。
  • 半衰期(Half-life):残差偏离均值后,回到均值一半所需的时间。半衰期越短,回复越快,交易机会越多。

我举个例子。假设残差序列的一阶自相关系数是0.7,半衰期是5天。这意味着,如果今天残差偏离均值1个标准差,大约5天后它会回到0.5个标准差的位置。这个速度,做短线交易是够的。

个人经验:我一般要求半衰期在3-10天之间。太短了(比如1天),交易成本太高,赚不到钱。太长了(比如30天),资金占用太久,性价比低。

计算半衰期的代码:

import numpy as np
from statsmodels.tsa.stattools import adfuller

def half_life(residuals):
    # 对残差做ADF检验,得到滞后项的系数
    lag = 1
    X = residuals[:-lag]
    y = residuals[lag:]
    X = sm.add_constant(X)
    model = sm.OLS(y, X)
    results = model.fit()
    rho = results.params[1]  # 滞后项系数
    
    # 半衰期 = ln(0.5) / ln(rho)
    hl = np.log(0.5) / np.log(rho)
    return abs(hl)

hl = half_life(residuals)
print(f"半衰期 = {hl:.2f} 天")

4.3 Z-score信号生成

好,现在我们知道残差有均值回复特性了。下一步,就是怎么生成交易信号。

最常用的方法,就是Z-score

Z-score的计算公式很简单:

Z = (当前残差 - 残差均值) / 残差标准差

Z-score告诉我们,当前价差偏离均值多少个标准差。如果Z=2,说明价差比均值高了2个标准差,大概率会跌回来。这时候就该做空价差。

具体怎么用?我一般设两个阈值:

Z-score范围 信号 操作
Z > 2.0 做空信号 空头价差(卖B,买A)
Z < -2.0 做多信号 多头价差(买B,卖A)
|Z| < 0.5 平仓信号 平掉所有头寸
|Z| > 3.0 止损信号 强制平仓,防止价差不回归

你想想看,为什么设2.0和-2.0?因为正态分布下,Z-score超过2的概率只有5%左右。也就是说,95%的情况下价差会在±2个标准差以内波动。超过这个范围,就是小概率事件,大概率会回归。

代码实现:

def generate_signals(residuals, entry_threshold=2.0, exit_threshold=0.5):
    # 计算Z-score
    mean = np.mean(residuals)
    std = np.std(residuals)
    z_scores = (residuals - mean) / std
    
    # 生成信号
    signals = pd.Series(index=residuals.index, data=0)
    
    # 做空信号
    signals[z_scores > entry_threshold] = -1
    # 做多信号
    signals[z_scores < -entry_threshold] = 1
    # 平仓信号
    signals[abs(z_scores) < exit_threshold] = 0
    
    # 注意:这里需要处理持仓状态,不能频繁开平仓
    # 实际交易中,需要加入持仓状态机逻辑
    return signals, z_scores

我曾经踩过的坑:一开始我直接用Z-score的实时值开仓,结果一天之内信号来回变,手续费亏死。后来我加了持仓状态机——只有当前没有持仓时,才根据Z-score开仓。持仓期间,Z-score再高也不开新仓。这样交易次数降下来了,胜率反而上去了。

4.4 知识体系总览

说了这么多,我画个图帮你理清思路。整个回归法策略的流程,其实就三步:

回归法策略核心流程 步骤1:OLS回归 估计对冲比率 β 步骤2:残差分析 均值回复特性检验 步骤3:信号生成 Z-score阈值判断 OLS回归细节 • 价格序列平稳性检验 • 截距项 α 估计 • 对冲比率 β 计算 • 残差序列提取 残差分析细节 • 自相关系数计算 • 半衰期估算 • ADF单位根检验 • 均值回复强度评估 信号生成细节 • Z-score计算 • 开仓阈值判断 • 平仓阈值判断 • 止损逻辑 核心:β决定对冲方向,残差决定交易时机,Z-score决定开平仓

这个图把整个流程串起来了。你从左边开始,先做OLS回归,算出β。然后看残差有没有均值回复特性。最后用Z-score生成信号。每一步都有对应的细节,缺一不可。

我的建议:刚开始做的时候,别急着上实盘。先用历史数据回测,看看Z-score的阈值设多少最合适。我一般用2.0作为默认值,但不同配对、不同市场环境,最优阈值可能不一样。多试几次,找到最适合你策略的参数。

好了,回归法策略的核心内容就这些。说白了,就是三步走:回归算β,残差看回复,Z-score定买卖。每一步都有坑,但踩过去之后,你会发现配对交易其实没那么神秘。

专注资料整理