第6章:插值方法入门:线性插值、样条插值在曲面构建中的应用
各位同学,欢迎来到第六章。
前面几章我们聊了波动率曲面的概念,也看了市场上实际的数据长什么样。但有个现实问题摆在我们面前:市场上能看到的期权报价,永远是不完整的。
你打开Bloomberg或者Wind,能看到50个、100个行权价的报价。但你的定价模型需要任意行权价、任意期限的波动率。怎么办?
答案就是——插值。
说白了,插值就是「猜」。用已知的点,去推断未知的点。但怎么猜得准,这就是门学问了。我在做期权做市商系统的时候,吃过不少插值的亏,今天把这些经验都倒给你们。
6.1 为什么需要插值?
先看一个实际场景。
假设今天是2024年1月15日,某股票当前价格100元。市场上能看到的期权报价是这样的:
| 行权价 | 90 | 95 | 100 | 105 | 110 |
|---|---|---|---|---|---|
| 隐含波动率 | 32% | 28% | 25% | 27% | 31% |
现在,客户要交易一个行权价97.5的期权。你报多少波动率?
这就是插值要解决的问题。我们只有5个离散点,但需要知道整条曲线上的任意值。
6.2 线性插值:最朴素的方法
线性插值,顾名思义,就是两点之间画直线。
公式很简单:
给定点 (x0, y0) 和 (x1, y1)
在 x 处的插值结果:
y = y0 + (y1 - y0) * (x - x0) / (x1 - x0)
用Python实现也就几行:
def linear_interp(x, x0, x1, y0, y1):
"""线性插值"""
return y0 + (y1 - y0) * (x - x0) / (x1 - x0)
# 对上面表格的数据做插值
# 行权价97.5,介于95和100之间
vol_97_5 = linear_interp(97.5, 95, 100, 0.28, 0.25)
print(f"行权价97.5的波动率: {vol_97_5:.4f}") # 输出: 0.2650
嗯,就这么简单。但问题来了——线性插值真的够用吗?
我个人习惯把线性插值当作「基准线」。它简单、透明、不会出幺蛾子。但它的缺点也很明显:
- 不光滑:在数据点处有尖角,导数不连续
- 无法捕捉曲率:波动率曲面是有「微笑」形状的,直线拟合不了微笑
- 外推能力差:超出数据范围时,线性外推往往不靠谱
6.3 样条插值:让曲线更自然
样条插值的思路是:不要用一根直线连接两点,而是用一段光滑的曲线。
最常见的三次样条,保证在每个数据点处,曲线的一阶导数和二阶导数都连续。这样整条曲线看起来就很「顺眼」。
Python里用SciPy实现非常方便:
from scipy.interpolate import CubicSpline
import numpy as np
# 原始数据
strikes = np.array([90, 95, 100, 105, 110])
vols = np.array([0.32, 0.28, 0.25, 0.27, 0.31])
# 创建三次样条插值器
cs = CubicSpline(strikes, vols)
# 在任意点求值
test_strikes = np.array([92.5, 97.5, 102.5, 107.5])
interp_vols = cs(test_strikes)
for s, v in zip(test_strikes, interp_vols):
print(f"行权价 {s}: 波动率 {v:.4f}")
输出结果:
行权价 92.5: 波动率 0.3012
行权价 97.5: 波动率 0.2648
行权价 102.5: 波动率 0.2598
行权价 107.5: 波动率 0.2915
你看,样条插值的结果比线性更「圆润」。它捕捉到了波动率微笑的形态——平值附近低,两边高。
6.4 线性 vs 样条:实战对比
为了让大家看得更清楚,我画了一张对比图:
从图上能明显看出来:
- 蓝色虚线(线性):在数据点处有尖角,看起来不自然
- 绿色实线(样条):光滑连续,更符合波动率微笑的形态
但注意,样条也不是完美无缺。你看105到110之间,样条曲线往下弯了一下又往上翘。这在某些情况下会导致「伪波动率微笑」——明明市场没有那个形态,插值硬生生造出来了。
6.5 二维插值:从曲线到曲面
上面我们只讨论了「一个期限、多个行权价」的情况。但波动率曲面是二维的——既有行权价维度,又有期限维度。
二维插值的思路有两种:
- 顺序插值法:先对每个期限做行权价方向的插值,再对每个行权价做期限方向的插值
- 直接二维插值:用二维样条或径向基函数直接拟合
我个人更推荐第一种。为什么?因为可控。你可以对行权价方向用样条,对期限方向用线性——不同维度用不同的方法,灵活度更高。
代码示例:
from scipy.interpolate import interp2d, RectBivariateSpline
import numpy as np
# 假设我们有3个期限,5个行权价的数据
tenors = np.array([0.1, 0.5, 1.0]) # 单位:年
strikes = np.array([90, 95, 100, 105, 110])
# 波动率矩阵:行=期限,列=行权价
vol_matrix = np.array([
[0.33, 0.29, 0.26, 0.28, 0.32], # 0.1年
[0.32, 0.28, 0.25, 0.27, 0.31], # 0.5年
[0.31, 0.27, 0.24, 0.26, 0.30] # 1.0年
])
# 方法1:顺序插值(推荐)
from scipy.interpolate import CubicSpline
def sequential_2d_interp(tenor, strike, tenors, strikes, vol_matrix):
"""顺序二维插值"""
# 先对每个期限做行权价方向的样条插值
interp_vols_at_tenors = []
for i, t in enumerate(tenors):
cs = CubicSpline(strikes, vol_matrix[i])
interp_vols_at_tenors.append(cs(strike))
# 再对期限方向做线性插值
result = np.interp(tenor, tenors, interp_vols_at_tenors)
return result
# 测试
test_tenor = 0.3
test_strike = 97.5
vol = sequential_2d_interp(test_tenor, test_strike, tenors, strikes, vol_matrix)
print(f"期限{test_tenor}年,行权价{test_strike}的波动率: {vol:.4f}")
6.6 插值方法的选择指南
讲了这么多,到底该用哪种?我整理了一个表格:
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 数据点密集(>10个) | 三次样条 | 光滑性好,能捕捉细节 |
| 数据点稀疏(<5个) | 线性插值 | 样条容易过冲,线性更稳健 |
| 需要外推 | 线性外推 | 样条外推可能发散 |
| 实时报价系统 | 线性插值 | 计算速度快,稳定性好 |
| 风险分析/定价 | 样条插值 | 光滑的希腊值计算 |
6.7 本章小结
嗯,这一章的内容就到这里。我们来捋一捋核心要点:
- 线性插值:简单、稳健,适合快速原型和实时系统
- 样条插值:光滑、自然,适合精细定价和风险分析
- 二维插值:推荐顺序插值法,不同维度用不同方法
- 没有银弹:插值方法的选择取决于数据质量和应用场景
最后说一句:插值只是工具,不是目的。我们的目标是构建一个合理、无套利、可交易的波动率曲面。方法选对了,事半功倍;选错了,事倍功半。
下一章,我们会深入讨论如何检验插值结果的合理性,以及如何处理那些「不听话」的数据点。