15. 多项式拟合:使用多项式回归对波动率曲面进行全局拟合

说实话,刚入行那会儿,我最头疼的就是波动率曲面上的那些「毛刺」。市场数据嘛,总是不完美的。有的期限上报价稀疏,有的执行价附近流动性差,曲面看起来坑坑洼洼的。

后来我接触到多项式拟合,一下子觉得——嗯,这路子靠谱。用一条光滑的多项式曲面,把整个波动率表面「兜」住。说白了,就是用数学公式去逼近真实市场数据。

15.1 为什么需要全局拟合?

你想想看,我们拿到的期权报价,通常只有有限个执行价和期限。比如只有 10 个执行价、5 个到期日,总共 50 个点。但做定价或风控时,我们需要任意执行价、任意期限上的波动率。

插值当然可以,但插值有个毛病——它只保证经过已知点,不保证整体形状合理。我见过有人用线性插值,结果曲面出现奇怪的「折角」,Delta 对冲信号都是错的。

全局拟合就不一样了。它用一个统一的函数形式,去拟合所有数据点。这样得到的曲面天然光滑,而且外推时也相对稳定。

核心思想: 用低阶多项式(通常是 2 阶或 3 阶)去逼近波动率关于执行价和期限的函数关系。

15.2 数学模型长什么样?

假设我们想拟合波动率 \(\sigma(K, T)\),其中 \(K\) 是执行价,\(T\) 是剩余期限。一个常用的二阶多项式形式是:

σ(K, T) = β₀ + β₁·K + β₂·K² + β₃·T + β₄·T² + β₅·K·T

这里有个交叉项 \(K·T\),它捕捉了「执行价效应随期限变化」的特性。我在实际项目中试过,不加交叉项的话,短期和长期曲面的形状差异拟合不好。

当然,你也可以用三阶甚至更高阶。但我个人建议,别超过三阶。为什么?

  • 过拟合风险: 高阶多项式会在数据稀疏区域「乱抖」
  • 外推灾难: 超出数据范围后,高阶项会迅速发散
  • 经济含义模糊: 高阶项很难解释成某种市场特征
我的经验: 对于股票期权,二阶多项式通常就够了。对于外汇期权,有时需要三阶来捕捉更复杂的偏斜形态。

15.3 代码实现:从数据到曲面

下面我给出一个完整的 Python 实现。我们用最小二乘法来求解系数。

import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

# 假设我们有市场数据
# K: 执行价数组, T: 期限数组, sigma: 对应的波动率
K = np.array([0.95, 1.00, 1.05, 0.95, 1.00, 1.05])
T = np.array([0.25, 0.25, 0.25, 0.50, 0.50, 0.50])
sigma = np.array([0.22, 0.20, 0.23, 0.21, 0.19, 0.22])

# 构造特征矩阵:包含 K, T, K², T², K·T
X = np.column_stack([K, T])
poly = PolynomialFeatures(degree=2, include_bias=True)
X_poly = poly.fit_transform(X)

# 拟合模型
model = LinearRegression()
model.fit(X_poly, sigma)

# 查看系数
print("截距项:", model.intercept_)
print("系数:", model.coef_)

# 预测新点上的波动率
K_new = np.array([1.02])
T_new = np.array([0.30])
X_new = poly.transform(np.column_stack([K_new, T_new]))
sigma_pred = model.predict(X_new)
print("预测波动率:", sigma_pred)

这段代码很直接。但要注意一点:PolynomialFeatures 默认会生成所有交互项和幂次项。如果你只想用特定的项,可以手动构造特征矩阵。

15.4 可视化:看看拟合效果

光看数字不够直观。我习惯把拟合曲面和原始数据点画在一起,一眼就能看出问题。

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# 生成网格用于绘制曲面
K_grid = np.linspace(0.90, 1.10, 20)
T_grid = np.linspace(0.20, 0.60, 20)
K_mesh, T_mesh = np.meshgrid(K_grid, T_grid)

# 展平并预测
X_grid = np.column_stack([K_mesh.ravel(), T_mesh.ravel()])
X_grid_poly = poly.transform(X_grid)
sigma_grid = model.predict(X_grid_poly).reshape(K_mesh.shape)

# 绘图
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(K_mesh, T_mesh, sigma_grid, alpha=0.6, cmap='viridis')
ax.scatter(K, T, sigma, color='red', s=50, label='市场数据')
ax.set_xlabel('执行价 K')
ax.set_ylabel('期限 T')
ax.set_zlabel('波动率 σ')
plt.title('多项式拟合波动率曲面')
plt.legend()
plt.show()

看到图了吗?红色点是原始数据,彩色曲面是拟合结果。如果曲面在数据点附近「穿」得比较好,说明拟合质量不错。

15.5 避坑指南:我踩过的几个坑

做多项式拟合,有几个地方特别容易翻车。我一个个说。

坑一:数据标准化

我曾经直接用原始执行价(比如 3500 点)和期限(比如 0.5 年)做拟合。结果数值差异太大,导致矩阵条件数爆炸,系数估计极不稳定。

后来我养成了习惯:先把 K 和 T 标准化到 [0, 1] 区间。这样多项式项之间的量级一致,拟合稳定得多。

坑二:交叉验证

别只看 R²。R² 高不一定好。我见过有人用五阶多项式,R² 高达 0.99,但一外推就飞到天上去了。

我的做法是:留出 20% 的数据做验证集,看验证集上的误差。如果训练集误差远小于验证集误差,那就是过拟合了。

坑三:边界效应

多项式在数据边界附近往往表现很差。尤其是执行价两端,数据点少,拟合曲线容易「翘起来」。

我建议:如果边界附近有交易需求,最好用样条插值做局部修正,或者给边界点加权重。

15.6 知识体系:一张图说清楚

下面我用 SVG 画了一张流程图,把多项式拟合波动率曲面的完整流程串起来。你可以把它当作操作手册。

多项式拟合波动率曲面流程 步骤1:数据准备 收集 K, T, σ 市场数据 步骤2:预处理 标准化 K 和 T 到 [0,1] 步骤3:构造特征 生成多项式项 步骤4:最小二乘拟合 求解多项式系数 β 步骤5:评估与验证 检查 R²、残差、外推表现 步骤6:曲面生成 预测任意 K, T 的波动率

15.7 什么时候该用,什么时候不该用?

多项式拟合不是万能的。我总结了一下它的适用场景:

场景 推荐使用 原因
数据点密集且均匀 ✅ 是 多项式能很好地捕捉整体趋势
需要快速计算 ✅ 是 解析表达式,计算极快
数据稀疏或分布不均 ❌ 否 容易在稀疏区域过拟合
需要精确拟合局部特征 ❌ 否 多项式是全局拟合,局部细节差
外推需求强烈 ⚠️ 谨慎 低阶多项式可外推,高阶不行

我个人习惯是:先用二阶多项式快速跑一遍,看看残差分布。如果残差有明显的模式(比如 U 型),再考虑加三阶项或改用样条。

好了,多项式拟合这块就聊到这儿。记住一点:工具是死的,市场是活的。别迷信任何一个模型,多看看残差图,多问问自己「这个曲面合理吗?」


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