3. 距离法策略:最小化平方距离法、标准化价格序列、固定阈值开平仓规则

距离法,说白了就是配对交易里最朴素、最直观的一种思路。你想想看,两只股票长期走势差不多,那它们之间的价差就应该在一个合理范围内晃悠。一旦价差跑偏了,那就是机会——赌它迟早会回归。

我个人最早接触配对交易,就是从距离法入手的。那时候还没那么多花里胡哨的机器学习模型,大家就靠算距离、设阈值来赚钱。嗯,今天咱们就把这套经典玩法彻底讲透。

3.1 核心思想:价差回归

距离法的逻辑其实特别简单:

  • 找到两只相关性高的股票
  • 计算它们的价差(或价差的某种变形)
  • 价差偏离到一定程度就开仓,回归了就平仓

关键问题来了:怎么定义「价差」?怎么定义「偏离」?

我在项目中遇到过不少新手,上来就用原始价格算价差。结果呢?茅台和五粮液价格差了几百块,这能直接比吗?显然不行。所以我们需要对价格做标准化处理。

3.2 标准化价格序列

标准化的目的,就是把不同量级的股票价格拉到同一个尺度上。常用的方法有两种:

方法一:Z-Score 标准化

把价格序列减去均值,再除以标准差。公式很简单:

z = (price - mean(price)) / std(price)

这样处理后,两只股票的价格都变成了均值为0、标准差为1的序列。它们之间的差值,就是标准化的价差。

方法二:Min-Max 标准化

把价格压缩到 [0, 1] 区间:

norm_price = (price - min(price)) / (max(price) - min(price))

我个人习惯用 Z-Score,因为它保留了价格波动的统计特性,方便后面设阈值。

我的经验:标准化时一定要用滚动窗口,别用全量数据。比如用过去60天的均值和标准差来标准化今天的价格。否则你会引入未来信息,回测结果全是假的。

3.3 最小化平方距离法

标准化之后,我们怎么衡量两只股票的「距离」?最经典的做法就是最小化平方距离。

具体来说,我们假设两只股票的价格存在一个线性关系:

P_A = β * P_B + ε

其中 β 是 hedge ratio(对冲比率),ε 是残差(也就是价差)。

最小化平方距离,就是找到那个 β,让残差的平方和最小。说白了,就是做一次线性回归:

import numpy as np

def calculate_hedge_ratio(price_a, price_b):
    # 最小二乘法求对冲比率
    cov_matrix = np.cov(price_a, price_b)
    beta = cov_matrix[0, 1] / cov_matrix[1, 1]
    return beta

# 计算价差
beta = calculate_hedge_ratio(price_a, price_b)
spread = price_a - beta * price_b

这里有个坑,我曾经踩过:直接用原始价格回归,β 会随着时间漂移。所以我建议用滚动窗口来估计 β,比如每20个交易日重新算一次。

3.4 固定阈值开平仓规则

有了标准化的价差序列,接下来就是定规则了。固定阈值法是最简单粗暴的:

价差状态 操作 阈值条件
价差过大(正向) 做空价差:卖A买B spread > +2σ
价差过小(负向) 做多价差:买A卖B spread < -2σ
价差回归 平仓 |spread| < 0.5σ
止损 强制平仓 |spread| > 3σ

这里的 σ 是价差序列的标准差。2σ 和 0.5σ 是经验值,你可以根据回测结果调整。

注意:固定阈值最大的问题在于「阈值本身不固定」。价差的波动率会变,今天2σ是10块,下个月可能变成15块。所以实际中我建议用滚动窗口的σ,而不是全量数据的σ。

3.5 完整策略流程

把上面这些串起来,一个完整的距离法策略长这样:

def distance_strategy(prices_a, prices_b, window=60, entry_z=2, exit_z=0.5):
    # 1. 滚动标准化
    z_a = rolling_zscore(prices_a, window)
    z_b = rolling_zscore(prices_b, window)
    
    # 2. 滚动计算对冲比率
    beta = rolling_beta(prices_a, prices_b, window)
    
    # 3. 计算标准化价差
    spread = z_a - beta * z_b
    
    # 4. 生成信号
    signals = []
    position = 0  # 0:空仓, 1:多头, -1:空头
    
    for i in range(len(spread)):
        if position == 0:
            if spread[i] > entry_z:
                signals.append(-1)  # 做空价差
                position = -1
            elif spread[i] < -entry_z:
                signals.append(1)   # 做多价差
                position = 1
            else:
                signals.append(0)
        else:
            if abs(spread[i]) < exit_z:
                signals.append(0)   # 平仓
                position = 0
            else:
                signals.append(position)
    
    return signals

3.6 知识体系总览

下面这张图,把距离法策略的核心逻辑串起来了:

距离法策略核心逻辑 第一步:数据准备 第二步:标准化 第三步:计算距离 • 选取高相关性股票对 • 获取历史价格数据 • 确定滚动窗口大小 • 通常60-120个交易日 • Z-Score标准化 • 滚动均值和标准差 • 消除量纲影响 • 使价格可比较 • 最小二乘法求β • 计算价差序列 • spread = zA - β*zB • 滚动更新β值 第四步:固定阈值开平仓判断 第五步:执行交易 & 风险控制 开仓: |spread|>2σ 平仓: |spread|<0.5σ 止损: |spread|>3σ

3.7 避坑指南

距离法看着简单,但坑不少。我把自己踩过的坑列出来,你注意避开:

  • 不要用全量数据标准化——这会导致未来信息泄露,回测漂亮,实盘拉胯
  • β值要滚动更新——股票的相关性会变,固定β会让你亏到怀疑人生
  • 阈值要动态调整——市场波动率在变,固定阈值等于刻舟求剑
  • 注意交易成本——配对交易频繁开平仓,手续费和滑点能吃掉你大部分利润

核心要点:距离法策略的精髓不在于「距离怎么算」,而在于「什么时候算、用什么窗口算」。滚动窗口的大小、更新频率、阈值参数,这些才是决定策略生死的关键。我建议你从60天窗口、2σ开仓、0.5σ平仓开始调参,然后逐步优化。

好了,距离法就讲到这里。这套方法虽然老,但它是理解配对交易的基石。后面更复杂的协整法、机器学习法,本质上都是在距离法的基础上做改进。把今天的内容吃透,后面的路就好走了。

专注资料整理