第十三章:曲面套利策略八:主成分分析(PCA)在曲面套利中的应用,分解曲面变动的主因子
说实话,做波动率曲面套利做到一定阶段,你会发现一个很头疼的问题——曲面每天都在动,但到底是谁在驱动它?是整体水平变了?是 skew 在偏移?还是某个期限出了幺蛾子?
我早年做期权做市的时候,每天盯着曲面看,感觉它像个活物。后来我意识到,光靠肉眼盯盘不行,得用数学工具把曲面拆开来看。这就是 PCA 的用武之地。
为什么是 PCA?
你想想看,波动率曲面本质上是一个高维数据。假设我们有 10 个期限、10 个行权价,那就是 100 个点。每天这 100 个点一起动,但它们的运动不是随机的——背后一定有少数几个“主因子”在驱动。
PCA 就是干这个的。它能把高维数据降维,找到那几个最重要的方向。说白了,就是把曲面的变动分解成几个正交的“模式”。
核心思想: 波动率曲面的变动,90% 以上可以由前 3 个主成分解释。第一个是“水平因子”(整体平移),第二个是“倾斜因子”(skew 变化),第三个是“曲度因子”(微笑变化)。
我在项目中遇到过最典型的案例:某次市场暴跌,曲面整体上移了 5 个 vol,但 skew 几乎没变。这就是典型的“水平因子”主导。如果你当时做的是 skew 套利,那基本就是白忙活。
PCA 的数学框架
嗯,这里我们简单过一下数学,别怕,不复杂。
假设我们有 T 个时间点,每个时间点观测到 N 个曲面点(比如 10 个期限 × 10 个行权价 = 100 个点)。我们构造一个 T × N 的矩阵 X,每一行是一个时间点的曲面快照。
PCA 的步骤:
- 去均值: 对每个曲面点,减去它的时间序列均值。得到去均值后的矩阵 X̃。
- 计算协方差矩阵: C = (1/(T-1)) X̃ᵀ X̃,维度是 N × N。
- 特征值分解: 对 C 做特征值分解,得到特征值 λ₁ ≥ λ₂ ≥ ... ≥ λ_N 和对应的特征向量 v₁, v₂, ..., v_N。
- 主成分: 第 k 个主成分就是 X̃ × vₖ,它是一个长度为 T 的时间序列。
- 载荷: 特征向量 vₖ 就是第 k 个主成分的载荷,它告诉我们每个曲面点在这个主成分上的权重。
我的习惯: 做 PCA 之前,我通常会先对数据做标准化(减去均值除以标准差)。这样能避免那些 vol 绝对值大的点(比如短期 ATM)主导结果。不过标准化之后,解释起来会稍微麻烦一点,你自己权衡。
代码实现:用 Python 做曲面 PCA
直接上代码。我习惯用 sklearn 的 PCA,省事。
import numpy as np
import pandas as pd
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
# 假设我们有一个曲面数据矩阵
# 行:时间点(比如过去 500 个交易日)
# 列:曲面点(比如 10 个期限 × 10 个行权价 = 100 个点)
# 这里我们生成模拟数据做演示
np.random.seed(42)
T = 500 # 时间点
N = 100 # 曲面点
# 模拟曲面数据:三个主成分 + 噪声
time = np.arange(T)
level = np.sin(time * 0.02) * 2 # 水平因子
skew = np.cos(time * 0.03) * 1.5 # 倾斜因子
curvature = np.sin(time * 0.05 + 1) * 0.8 # 曲度因子
# 构造载荷矩阵(随机生成,但保证正交性)
np.random.seed(42)
loadings = np.random.randn(N, 3)
loadings, _ = np.linalg.qr(loadings) # 正交化
# 生成曲面数据
X = np.outer(level, loadings[:, 0]) + \
np.outer(skew, loadings[:, 1]) + \
np.outer(curvature, loadings[:, 2]) + \
np.random.randn(T, N) * 0.1 # 加一点噪声
# 做 PCA
pca = PCA(n_components=5)
X_pca = pca.fit_transform(X)
# 查看解释方差比
print("解释方差比(前5个主成分):")
for i, ratio in enumerate(pca.explained_variance_ratio_[:5]):
print(f"PC{i+1}: {ratio:.4f} ({ratio*100:.2f}%)")
print(f"\n前3个主成分累计解释:{pca.explained_variance_ratio_[:3].sum():.4f} ({pca.explained_variance_ratio_[:3].sum()*100:.2f}%)")
运行这段代码,你会发现前 3 个主成分通常能解释 95% 以上的方差。这就是为什么我们敢说“曲面变动主要由三个因子驱动”。
PCA 在曲面套利中的具体应用
知道了主成分之后,怎么用它做套利?我总结了几种玩法:
- 因子剥离套利: 把曲面变动投影到主成分上,得到每个主成分的得分。如果某个主成分的得分偏离历史均值超过 2 个标准差,就认为这个因子被“定价错误”了,可以做回归套利。
- 残差套利: 用前 K 个主成分重构曲面,计算残差(实际曲面 - 重构曲面)。残差大的点,就是被主成分模型“解释不了”的点,这些点往往存在套利机会。
- 风险对冲: 如果你持有一个期权组合,可以用 PCA 把风险映射到主成分上。这样你只需要对冲前 3 个主成分,就能消除大部分曲面风险。剩下的残差风险很小,可以忽略。
我曾经踩过的坑: 有一次我用 PCA 做残差套利,发现某个期限的残差持续为正,我以为发现了套利机会,重仓进去。结果发现是因为那个期限的流动性太差,报价不连续,导致 PCA 模型拟合不好。所以,做 PCA 之前一定要先清洗数据,把那些流动性差的点剔除掉,或者做插值平滑。
曲面变动的主因子可视化
光看数字不够直观。我习惯把前三个主成分的载荷画出来,看看它们长什么样。
# 假设我们有 10 个期限和 10 个行权价
tenors = np.array([0.1, 0.2, 0.3, 0.5, 0.7, 1.0, 1.5, 2.0, 3.0, 5.0])
strikes = np.linspace(-3, 3, 10) # 用标准化后的行权价
# 把载荷矩阵 reshape 成 10x10
loadings_matrix = pca.components_.reshape(5, 10, 10)
# 画前三个主成分的载荷热力图
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
titles = ['PC1: 水平因子', 'PC2: 倾斜因子', 'PC3: 曲度因子']
for i in range(3):
im = axes[i].imshow(loadings_matrix[i], aspect='auto', cmap='RdBu',
extent=[strikes.min(), strikes.max(), tenors.max(), tenors.min()])
axes[i].set_title(titles[i])
axes[i].set_xlabel('标准化行权价')
axes[i].set_ylabel('期限(年)')
plt.colorbar(im, ax=axes[i])
plt.tight_layout()
plt.show()
你会看到:
- PC1(水平因子): 所有曲面点的载荷都是正的,而且大小差不多。这说明它驱动的是曲面整体上下移动。
- PC2(倾斜因子): 左边(虚值看跌)是负的,右边(虚值看涨)是正的。这说明它驱动的是 skew 的陡峭程度。
- PC3(曲度因子): 中间(平值)是正的,两边是负的。这说明它驱动的是微笑的曲度。
一个小技巧: 如果你发现某个市场的 PC2 和 PC3 的形态跟标准的不一样,别慌。不同市场的曲面结构不同。比如股指期权市场的 skew 通常比外汇期权市场更明显。我建议你先跑一遍数据,看看你的市场长什么样,再决定怎么用。
实战中的注意事项
最后,说几个实战中容易忽略的点:
- 滚动窗口: PCA 的载荷不是一成不变的。市场结构会变,我建议用滚动窗口(比如 250 个交易日)重新计算 PCA,而不是用全历史数据一次算完。
- 数据频率: 用日线数据做 PCA 和用分钟线数据做 PCA,结果可能完全不同。日线数据反映的是“慢变量”,分钟线数据反映的是“快变量”。你要根据你的交易频率选择合适的频率。
- 不要过度拟合: 前 3 个主成分已经能解释 95% 的方差了,再加第 4 个、第 5 个,解释力提升有限,而且容易引入噪声。我一般只用前 3 个。
好了,PCA 在曲面套利中的应用就讲到这里。说白了,它就是一把手术刀,帮你把曲面这个“活物”解剖开,看清楚里面到底在动什么。下一节我们会聊怎么用这些主成分构建具体的交易信号。