3. 相关性分析实战:外汇与黄金、原油、铜等大宗商品的统计相关性、协整检验与套利窗口识别

做跨市场套利,说白了就是找「亲戚」。外汇和黄金、原油、铜这些大宗商品,它们之间到底有没有血缘关系?关系有多铁?什么时候会翻脸?这就是我们今天要啃的硬骨头。

我个人习惯,拿到任何品种对,第一件事不是跑模型,而是先画一张相关性热力图。就像医生看病人,先量体温,再查血常规。你想想看,如果两个品种长期走势像双胞胎,突然有一天闹别扭了,那不就是套利机会吗?

核心逻辑:统计相关性 ≠ 套利可行性。高相关是入场的前提,协整关系才是套利的保障。

3.1 外汇与大宗商品的「亲戚关系」图谱

先说说我这些年观察到的几个铁律。当然,市场没有绝对,但这些规律在80%的时间里是有效的。

品种对 典型相关性 逻辑解释 我的实战经验
EUR/USD vs 黄金 正相关(0.6~0.8) 欧元和黄金都是美元的对手盘 2015年希腊危机时,这对关系一度破裂,我吃过亏
USD/CAD vs 原油 负相关(-0.7~-0.9) 加拿大是石油出口国,油价涨加元强 这个关系最稳定,我拿它做过主力策略
AUD/USD vs 铜 正相关(0.5~0.7) 澳大利亚是铜矿出口大国 注意:铁矿石比铜更敏感,但铜的数据更干净
USD/JPY vs 黄金 负相关(-0.3~-0.5) 避险逻辑,但日元有时也避险 这个关系最不稳定,我建议新手别碰

避坑指南:我曾经以为USD/CHF和黄金也是强相关,结果回测亏了三个月。后来发现,瑞士央行干预市场时,什么相关性都是浮云。

3.2 统计相关性计算:别只看皮尔逊

很多人一上来就用皮尔逊相关系数。嗯,这里要注意,皮尔逊假设数据是正态分布且线性相关。但金融数据哪有那么乖?

我一般会同时算三种相关性:

  1. 皮尔逊相关系数:衡量线性关系,适合快速筛查
  2. 斯皮尔曼秩相关系数:衡量单调关系,对异常值不敏感
  3. 肯德尔相关系数:更稳健,适合小样本

举个例子,我拿EUR/USD和黄金的日线数据跑一下:

import pandas as pd
import numpy as np
from scipy.stats import pearsonr, spearmanr, kendalltau

# 假设df包含两列:'EURUSD' 和 'GOLD'
# 先算收益率,别用价格直接算
df['ret_EUR'] = df['EURUSD'].pct_change()
df['ret_GOLD'] = df['GOLD'].pct_change()

# 去掉NaN
clean_df = df[['ret_EUR', 'ret_GOLD']].dropna()

# 三种相关性
pearson_r, p_pearson = pearsonr(clean_df['ret_EUR'], clean_df['ret_GOLD'])
spearman_r, p_spearman = spearmanr(clean_df['ret_EUR'], clean_df['ret_GOLD'])
kendall_r, p_kendall = kendalltau(clean_df['ret_EUR'], clean_df['ret_GOLD'])

print(f"皮尔逊: {pearson_r:.3f} (p值: {p_pearson:.4f})")
print(f"斯皮尔曼: {spearman_r:.3f} (p值: {p_spearman:.4f})")
print(f"肯德尔: {kendall_r:.3f} (p值: {p_kendall:.4f})")

你看,如果三种相关性方向一致,那这个关系就比较靠谱。如果皮尔逊和斯皮尔曼符号相反,说明存在非线性关系,这时候要小心。

重要提醒:千万别用价格序列直接算相关性!价格是随机游走,两个随机游走算出来的相关性是伪相关。一定要用收益率或者对数差分。

3.3 协整检验:找到真正的「长期伴侣」

相关性高不代表能套利。为什么?因为相关性是短期的,协整才是长期的。我打个比方:相关性就像两个人今天心情都很好,协整就像两个人这辈子注定要在一起。

协整检验的核心思想:两个非平稳序列的线性组合可能是平稳的。说白了,就是它们虽然各自乱跑,但差距不会无限扩大。

我常用的方法是Engle-Granger两步法:

import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller

# 第一步:回归
X = df['GOLD']  # 自变量
y = df['EURUSD']  # 因变量
X = sm.add_constant(X)
model = sm.OLS(y, X).fit()
residuals = model.resid

# 第二步:对残差做ADF检验
adf_result = adfuller(residuals, maxlag=1)
print(f"ADF统计量: {adf_result[0]:.4f}")
print(f"p值: {adf_result[1]:.4f}")

# p值小于0.05,说明残差平稳,存在协整关系
if adf_result[1] < 0.05:
    print("存在协整关系,可以构建套利策略")
else:
    print("不存在协整关系,别浪费时间")

我在项目中遇到过最坑的一次:用日线数据做协整检验,p值0.03,看起来很美。结果换成周线数据,p值变成0.15。后来发现是数据频率的问题。我的建议是:至少用两种频率验证,日线和周线都跑一遍。

3.4 套利窗口识别:什么时候动手?

找到协整关系后,下一步就是找入场点。我习惯用「Z-score」方法,说白了就是看当前价差偏离均值几个标准差。

具体步骤:

  1. 计算价差:spread = y - β * x(β是回归系数)
  2. 计算价差的均值和标准差(用滚动窗口,我一般用60天)
  3. 计算Z-score = (当前价差 - 均值) / 标准差
  4. 设定阈值:|Z-score| > 2 时开仓,回归到0附近平仓
# 计算滚动Z-score
window = 60
spread = y - model.params['GOLD'] * X['GOLD']
rolling_mean = spread.rolling(window=window).mean()
rolling_std = spread.rolling(window=window).std()
z_score = (spread - rolling_mean) / rolling_std

# 生成交易信号
entry_threshold = 2.0
exit_threshold = 0.5

# 做空价差(卖EURUSD,买黄金)
short_signal = (z_score > entry_threshold) & (z_score.shift(1) <= entry_threshold)
# 做多价差(买EURUSD,卖黄金)
long_signal = (z_score < -entry_threshold) & (z_score.shift(1) >= -entry_threshold)
# 平仓信号
exit_signal = (abs(z_score) < exit_threshold)

我的小技巧:阈值不是死的。波动率大的时候,我会把阈值调到2.5;波动率小的时候,1.8就够了。你可以用ATR(平均真实波幅)来动态调整。

3.5 实战案例:USD/CAD vs 原油

这个组合是我最拿手的。加拿大是石油出口国,USD/CAD和原油价格长期负相关。我记得2020年4月,原油期货跌到负值那天,USD/CAD瞬间暴涨,但第二天就回归了。这就是典型的套利窗口。

具体操作流程:

  • 数据准备:取USD/CAD和WTI原油的日线数据,时间跨度至少2年
  • 相关性验证:收益率相关性应该在-0.7以下
  • 协整检验:p值小于0.05
  • 参数优化:滚动窗口用30天、60天、90天分别测试
  • 回测验证:至少跑3年数据,看夏普比率是否大于1.5

我曾经犯过一个错误:只用了2018-2019年的数据做回测,结果2020年3月流动性危机时,策略直接爆仓。后来我学乖了,一定要包含极端行情的数据

3.6 本章知识体系

下面这张图是我自己总结的,每次做新策略前都会过一遍:

跨市场套利相关性分析流程 第一步:品种筛选 经济逻辑 + 历史规律 第二步:相关性计算 皮尔逊+斯皮尔曼+肯德尔 第三步:协整检验 Engle-Granger两步法 协整成立? 换品种对 第四步:参数优化 第五步:回测验证 套利策略上线

这张图我用了好几年,每次做新策略都按这个流程走一遍。你会发现,大部分品种对在「协整检验」那一步就被淘汰了。没关系,宁缺毋滥。

最后说一句:相关性分析不是一劳永逸的。市场结构会变,我每个月都会重新跑一遍相关性矩阵。如果发现某个品种对的关系变了,我会立刻暂停策略,重新评估。


公众号:蓝海资料掘金营,微信deep3321