4、相关系数与距离:皮尔逊相关系数、欧氏距离法、如何筛选初始配对候选池

好,咱们进入正题。

配对交易的第一步,也是最关键的一步——选对股票

你想想看,如果选的两个标的本就不相关,那后面做回归、算残差、设阈值,全都是白费功夫。说白了,配对交易的核心逻辑就是「价差回归」,前提是这两个东西历史上确实有某种稳定的联动关系。

那怎么量化这种「联动关系」呢?

我个人习惯用两种方法:皮尔逊相关系数欧氏距离。它们各有侧重,配合使用效果更好。

4.1 皮尔逊相关系数:衡量线性关系的亲密程度

皮尔逊相关系数,大家应该不陌生。它衡量的是两个变量之间的线性相关程度,取值范围在 [-1, 1] 之间。

  • 1 表示完全正相关
  • -1 表示完全负相关
  • 0 表示没有线性关系

在配对交易里,我们通常找相关系数接近 1 的股票对。比如 0.8 以上,甚至 0.9 以上。

但这里有个坑——相关系数高,不代表价差一定可交易。

⚠️ 我曾经踩过的坑:
有一年我筛选出相关系数高达 0.98 的两只银行股,兴冲冲地开始跑策略。结果发现它们的价差根本不回归,反而越走越远。后来一查,原来是因为其中一只股票刚经历了除权除息,价格被硬生生拉低了。
教训:相关系数只看「形状」,不看「绝对水平」。一定要做价格调整(复权),否则就是白忙活。

计算皮尔逊相关系数的代码很简单:

import numpy as np
import pandas as pd

# 假设 df 是包含两只股票日收益率的数据框
# 注意:用收益率算相关系数,比用价格更稳定
corr = df['stock_a'].corr(df['stock_b'])
print(f'皮尔逊相关系数: {corr:.4f}')

我个人习惯用滚动窗口算相关系数,比如过去 60 天或 120 天。这样能动态观察相关性是否稳定。

# 滚动 60 天相关系数
rolling_corr = df['stock_a'].rolling(60).corr(df['stock_b'])
print(rolling_corr.tail())

如果滚动相关系数忽高忽低,甚至出现负值,那这对股票就不太适合做配对交易。稳定性比绝对值更重要。

4.2 欧氏距离法:另一种视角

相关系数看的是「走势是否同步」,而欧氏距离看的是「价格是否接近」。

你可能会问:价格接近有什么用?

嗯,这里要注意——欧氏距离法最早是由 Gatev、Goetzmann 和 Rouwenhorst 在 2006 年的经典论文中提出的。他们的思路很简单:

  1. 把两只股票的历史价格标准化(比如归一化到 [0,1] 区间)
  2. 计算它们标准化后价格的欧氏距离
  3. 距离越小,说明历史上价格走势越「贴合」

公式长这样:

距离 = sqrt( sum( (P_a_i - P_b_i)^2 ) )

其中 P_a_i 和 P_b_i 是两只股票在第 i 天的标准化价格。

代码实现:

from sklearn.preprocessing import MinMaxScaler

# 标准化价格到 [0,1]
scaler = MinMaxScaler()
price_norm = scaler.fit_transform(df[['stock_a', 'stock_b']])

# 计算欧氏距离
euclidean_dist = np.sqrt(np.sum((price_norm[:, 0] - price_norm[:, 1])**2))
print(f'欧氏距离: {euclidean_dist:.4f}')
💡 我的经验:
欧氏距离法对「价格水平」敏感。如果两只股票价格长期在一个区间内波动,距离就会小。但要注意,如果它们只是偶然在某段时间价格接近,距离也会小——所以一定要用足够长的历史数据(至少 1-2 年)。

4.3 两种方法的对比

维度 皮尔逊相关系数 欧氏距离法
关注点 走势方向是否一致 价格水平是否接近
对价格水平敏感 不敏感 敏感
对异常值敏感 较敏感 非常敏感
适用场景 同行业、同板块 同行业、同市值区间
计算复杂度

你看,两种方法各有千秋。我一般会先用相关系数粗筛,再用欧氏距离精筛。

4.4 如何构建初始配对候选池

好了,理论讲完,咱们来点实战的。怎么从几千只股票里,快速找到潜在的配对候选?

我的流程是这样的:

  1. 行业过滤:先限定在同一行业或同一板块。跨行业的配对不是不能做,但逻辑上更难解释,风险也更大。
  2. 流动性过滤:剔除日均成交额低于 5000 万的股票。流动性差的股票,交易成本高,滑点大,策略很难执行。
  3. 相关系数粗筛:计算所有股票对之间的皮尔逊相关系数,保留 top 10% 或相关系数 > 0.8 的对。
  4. 欧氏距离精筛:对粗筛后的股票对,计算标准化价格的欧氏距离,保留距离最小的前 50-100 对。
  5. 人工复核:最后,我会手动看一下这些股票对的走势图,排除那些有明显结构变化(比如并购、重组、退市风险)的。
🎯 核心要点:
候选池不是越大越好。我见过有人搞出几千个候选对,结果大部分都是噪音。我个人习惯控制在 30-50 对,精挑细选,后面做协整检验时成功率会高很多。

代码实现一个简单的筛选流程:

import itertools
import numpy as np
import pandas as pd

def build_candidate_pool(price_df, industry_col, min_corr=0.8, top_n=100):
    """
    构建初始配对候选池
    price_df: 包含多只股票日收盘价的 DataFrame
    industry_col: 行业标签列
    """
    candidates = []
    stocks = price_df.columns
    
    # 只考虑同一行业的股票对
    for (s1, s2) in itertools.combinations(stocks, 2):
        if industry_col[s1] != industry_col[s2]:
            continue
        
        # 计算相关系数
        corr = price_df[s1].corr(price_df[s2])
        if corr < min_corr:
            continue
        
        # 计算欧氏距离
        scaler = MinMaxScaler()
        norm_prices = scaler.fit_transform(price_df[[s1, s2]])
        dist = np.sqrt(np.sum((norm_prices[:, 0] - norm_prices[:, 1])**2))
        
        candidates.append((s1, s2, corr, dist))
    
    # 按距离排序,取前 top_n
    candidates = sorted(candidates, key=lambda x: x[3])[:top_n]
    return pd.DataFrame(candidates, columns=['stock_a', 'stock_b', 'corr', 'euclidean_dist'])

4.5 知识体系总览

下面这张图,帮你把本章的核心逻辑串起来:

初始配对候选池筛选流程 原始股票池 行业过滤 流动性过滤 皮尔逊相关系数粗筛 欧氏距离精筛 初始配对候选池
📌 补充一句:
这个流程不是死的。如果你做的是高频配对交易,可能还要加入波动率过滤、交易成本估算等步骤。但作为入门,这套流程已经够用了。

好了,关于相关系数和距离的筛选方法,就聊到这儿。记住一句话:候选池的质量,决定了你后面所有工作的上限。花时间把这一步做扎实,后面会省很多事。

专注资料整理