第三章:因子与商品的相关性分析

做量化对冲,最核心的问题是什么?

说白了就一句话:你选的因子,跟商品价格到底有没有关系?

我见过太多人,回测曲线漂亮得不行,一上实盘就崩。为什么?因为相关性是假的,是偶然的。今天我们就来彻底解决这个问题。

3.1 相关性分析:量化交易的基石

相关性分析,听起来高大上,其实就干三件事:

  • 找关系:因子涨,商品是不是也跟着涨?
  • 测强度:这个关系有多紧密?是铁哥们还是点头之交?
  • 看方向:是同向还是反向?

我个人习惯,拿到任何因子数据,第一件事就是算相关性。不是跑模型,不是调参数,就是算相关系数。这一步能帮你过滤掉80%的垃圾因子。

核心观点:相关性 ≠ 因果性,但没关系肯定没因果。这是因子筛选的第一道门槛。

3.2 皮尔逊相关系数:最常用的线性关系度量

皮尔逊相关系数,你肯定见过。它的公式长这样:

r = Σ((xᵢ - x̄)(yᵢ - ȳ)) / √(Σ(xᵢ - x̄)² · Σ(yᵢ - ȳ)²)

取值范围在 -1 到 1 之间。1 表示完全正相关,-1 表示完全负相关,0 表示没关系。

但我要提醒你:皮尔逊有个大坑——它只捕捉线性关系。

举个例子。我几年前分析一个库存因子和螺纹钢价格的关系。算出来皮尔逊系数只有 0.12,我以为这因子没用。后来一个老前辈提醒我:你画个散点图看看。结果发现,库存和价格是明显的U型关系——库存极低或极高时,价格都会涨。皮尔逊完全没捕捉到。

避坑指南:我曾经就因为只看皮尔逊系数,错过了一个好因子。现在我的习惯是:先画散点图,再算相关系数。永远不要跳过可视化。

Python里算皮尔逊很简单:

import pandas as pd
import numpy as np
from scipy import stats

# 假设 factor 和 price 是两个 Series
r, p_value = stats.pearsonr(factor, price)
print(f"皮尔逊相关系数: {r:.4f}, p值: {p_value:.4f}")

3.3 斯皮尔曼秩相关系数:非线性关系的救星

斯皮尔曼系数,说白了就是皮尔逊的升级版。它不关心具体数值,只关心排名。

公式也很简单:

ρ = 1 - (6 * Σdᵢ²) / (n * (n² - 1))

其中 dᵢ 是每个样本的排名差。

为什么需要它?因为大宗商品市场充满了非线性关系。

你想想看,原油价格和航空股的关系。油价从30涨到60,航空股跌得很惨。但从60涨到90,航空股可能已经通过套保消化了成本,跌幅就没那么大了。这就是非线性。

斯皮尔曼能捕捉这种关系。我建议你:任何时候都同时算皮尔逊和斯皮尔曼。如果两者差异很大,说明关系是非线性的,需要进一步分析。

# 斯皮尔曼相关系数
rho, p_value = stats.spearmanr(factor, price)
print(f"斯皮尔曼相关系数: {rho:.4f}, p值: {p_value:.4f}")

# 对比两者
print(f"差异: {abs(r - rho):.4f}")

3.4 滚动窗口相关性:捕捉动态变化

静态相关性有个致命问题:它假设关系是稳定的。

但市场不是这样的。因子和商品的关系会变,而且变得很快。

我记得2020年疫情刚爆发时,很多因子的相关性在一周内就发生了翻转。如果你用的是静态相关性,那段时间的策略会亏得很惨。

滚动窗口相关性就是解决这个问题的。它的思路很简单:

  • 选一个窗口大小(比如60个交易日)
  • 在每个时间点,只取窗口内的数据算相关性
  • 窗口不断向前滑动

这样你就能看到相关性随时间的变化轨迹。

def rolling_correlation(factor, price, window=60):
    """计算滚动窗口相关性"""
    df = pd.DataFrame({
        'factor': factor,
        'price': price
    })
    
    # 滚动计算
    rolling_corr = df['factor'].rolling(window=window).corr(df['price'])
    
    return rolling_corr

# 使用示例
rolling_corr = rolling_correlation(factor, price, window=60)
print(rolling_corr.tail())

实战技巧:窗口大小的选择很关键。太短(比如20天)噪声大,太长(比如250天)反应慢。我个人习惯用60天和120天两个窗口同时看。60天看短期变化,120天看趋势。

3.5 实战案例:原油库存因子与价格分析

光说不练假把式。我们拿真实数据走一遍流程。

假设我们有一个原油库存因子(EIA周度数据)和原油期货价格。目标是分析两者的相关性。

import pandas as pd
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

# 模拟数据(实际使用时替换为真实数据)
np.random.seed(42)
dates = pd.date_range('2020-01-01', periods=500, freq='D')
inventory = np.random.randn(500) * 0.5 + 0.1 * np.sin(np.linspace(0, 10, 500))
price = -0.3 * inventory + np.random.randn(500) * 0.2

# 1. 静态相关性
r_pearson, p_pearson = stats.pearsonr(inventory, price)
r_spearman, p_spearman = stats.spearmanr(inventory, price)

print(f"皮尔逊: {r_pearson:.4f} (p={p_pearson:.4f})")
print(f"斯皮尔曼: {r_spearman:.4f} (p={p_spearman:.4f})")

# 2. 滚动相关性
window = 60
rolling_corr = pd.Series(inventory).rolling(window).corr(pd.Series(price))

# 3. 可视化
plt.figure(figsize=(12, 8))

# 子图1:散点图
plt.subplot(2, 2, 1)
plt.scatter(inventory, price, alpha=0.5)
plt.xlabel('库存因子')
plt.ylabel('价格')
plt.title('散点图')

# 子图2:滚动相关性
plt.subplot(2, 2, 2)
plt.plot(dates, rolling_corr)
plt.axhline(y=0, color='r', linestyle='--')
plt.xlabel('日期')
plt.ylabel('滚动相关性')
plt.title(f'滚动窗口相关性 (窗口={window})')

plt.tight_layout()
plt.show()

运行这段代码,你会看到:

  • 散点图显示负相关关系(库存涨,价格跌)
  • 滚动相关性曲线在 -0.4 到 -0.2 之间波动
  • 某些时间段相关性会变弱甚至转正

这就是为什么静态相关性不够用。如果你在相关性转正的那段时间还按原来的逻辑交易,会出大问题。

3.6 相关性分析的完整框架

说了这么多,我总结一下自己的分析流程:

  1. 数据清洗:处理缺失值、异常值、对齐时间戳
  2. 可视化初探:画散点图、时间序列叠加图
  3. 静态相关性:同时算皮尔逊和斯皮尔曼,对比差异
  4. 滚动相关性:用60天和120天窗口,看稳定性
  5. 统计检验:看p值是否显著(一般要求p < 0.05)
  6. 经济逻辑验证:问自己,这个关系有道理吗?

记住:统计显著 ≠ 经济显著。我见过p值0.001的相关性,但实际交易中根本赚不到钱。因为那个关系太微弱了,交易成本就能吃掉所有收益。

3.7 常见陷阱与应对

陷阱 表现 应对方法
伪相关 两个无关序列偶然相关 做差分或去趋势后再算
滞后相关 因子领先或滞后于价格 计算不同滞后的相关性
结构突变 相关性在某时间点突变 用滚动窗口检测突变点
异常值干扰 少数极端值拉高相关性 用斯皮尔曼或剔除异常值

嗯,这里要注意。伪相关是大宗商品分析里最常见的坑。比如,你发现某个因子和铜价相关性高达0.8,但仔细一看,两者都在2020年因为疫情暴跌过。去掉那段时间,相关性直接掉到0.1。这就是伪相关。

我的做法是:分段验证。把数据分成牛市、熊市、震荡市三段,分别算相关性。如果三段都稳定,那才是真关系。

个人经验:我习惯在算完相关性后,做一个简单的回测。用因子信号交易,看实际收益。如果收益曲线和相关性分析对不上,那一定是哪里出了问题。

3.8 本章小结

相关性分析不是终点,是起点。它帮你快速筛选因子,但别指望它告诉你全部真相。

记住三个要点:

  • 皮尔逊看线性,斯皮尔曼看排名,两者互补
  • 滚动窗口看动态变化,别用静态数据骗自己
  • 统计显著不等于能赚钱,最终要看实盘表现

下一章,我们会把这些相关性分析应用到多因子组合中,看看如何构建一个真正稳健的量化对冲策略。


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