15. 多项式拟合:使用多项式回归对波动率曲面进行全局拟合
说实话,刚入行那会儿,我最头疼的就是波动率曲面上的那些「毛刺」。市场数据嘛,总是不完美的。有的期限上报价稀疏,有的执行价附近流动性差,曲面看起来坑坑洼洼的。
后来我接触到多项式拟合,一下子觉得——嗯,这路子靠谱。用一条光滑的多项式曲面,把整个波动率表面「兜」住。说白了,就是用数学公式去逼近真实市场数据。
15.1 为什么需要全局拟合?
你想想看,我们拿到的期权报价,通常只有有限个执行价和期限。比如只有 10 个执行价、5 个到期日,总共 50 个点。但做定价或风控时,我们需要任意执行价、任意期限上的波动率。
插值当然可以,但插值有个毛病——它只保证经过已知点,不保证整体形状合理。我见过有人用线性插值,结果曲面出现奇怪的「折角」,Delta 对冲信号都是错的。
全局拟合就不一样了。它用一个统一的函数形式,去拟合所有数据点。这样得到的曲面天然光滑,而且外推时也相对稳定。
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 画了一张流程图,把多项式拟合波动率曲面的完整流程串起来。你可以把它当作操作手册。
15.7 什么时候该用,什么时候不该用?
多项式拟合不是万能的。我总结了一下它的适用场景:
| 场景 | 推荐使用 | 原因 |
|---|---|---|
| 数据点密集且均匀 | ✅ 是 | 多项式能很好地捕捉整体趋势 |
| 需要快速计算 | ✅ 是 | 解析表达式,计算极快 |
| 数据稀疏或分布不均 | ❌ 否 | 容易在稀疏区域过拟合 |
| 需要精确拟合局部特征 | ❌ 否 | 多项式是全局拟合,局部细节差 |
| 外推需求强烈 | ⚠️ 谨慎 | 低阶多项式可外推,高阶不行 |
我个人习惯是:先用二阶多项式快速跑一遍,看看残差分布。如果残差有明显的模式(比如 U 型),再考虑加三阶项或改用样条。
好了,多项式拟合这块就聊到这儿。记住一点:工具是死的,市场是活的。别迷信任何一个模型,多看看残差图,多问问自己「这个曲面合理吗?」