9. 一维插值:沿着到期日方向的插值、沿着行权价方向的插值、插值边界处理
好,咱们接着聊。前面我们把波动率曲面的骨架搭起来了——不同到期日、不同行权价上有了几个离散的“锚点”。但实际交易中,你不可能只盯着这几个点看。比如客户要一个45天到期的平值期权,你的数据里只有30天和60天的,怎么办?
这就得靠插值了。说白了,就是用已知的点,去猜未知的点。今天这一章,我就带你手把手搞定一维插值的两个核心方向:沿着到期日(时间轴)和沿着行权价(价格轴)。最后再聊聊那个最容易翻车的边界处理。
核心观点:一维插值是波动率曲面构建的“地基”。地基没打好,后面三维建模全是空中楼阁。我个人习惯,先做时间轴插值,再做行权价轴插值,顺序别搞反。
9.1 沿着到期日方向的插值
先讲时间轴。为什么先做这个?因为不同到期日的期权,它们的隐含波动率在时间维度上是有明确金融含义的——方差随时间线性累积。这一点和行权价方向完全不同。
我个人最常用的方法是方差时间加权插值。说白了,不是直接对波动率做线性插值,而是对总方差做线性插值,再开方转回波动率。为什么?因为波动率本身不是线性的,但方差是。
小技巧:如果你直接用波动率做线性插值,你会发现插出来的结果在短端特别容易失真。我在项目中吃过这个亏,后来改用方差加权,效果立竿见影。
具体公式很简单:
# 已知:T1天的IV是σ1,T2天的IV是σ2
# 要求:T天的IV(T1 < T < T2)
# 总方差 = IV^2 * T
var1 = σ1^2 * T1
var2 = σ2^2 * T2
# 线性插值总方差
var_T = var1 + (var2 - var1) * (T - T1) / (T2 - T1)
# 转回波动率
σ_T = sqrt(var_T / T)
当然,如果你只是做快速原型,线性插值也能用。但记住一点:短端(30天以内)的波动率变化非常剧烈,线性插值很容易把曲线拉平,丢失细节。
还有一种情况——你手头的数据到期日分布不均匀。比如有7天、14天、30天、90天,但中间缺了21天。这时候三次样条插值就派上用场了。它能保证曲线平滑,但要注意:样条在数据稀疏的区域容易“过冲”,产生不合理的负波动率。嗯,这里要小心。
避坑指南:我曾经在构建短端波动率曲面时,直接用三次样条插7天到14天之间的点,结果插出了一个负的波动率。后来加了单调性约束(PCHIP插值),才把这个问题解决。所以,数据点少于4个时,别用三次样条。
9.2 沿着行权价方向的插值
行权价方向的插值,比时间轴要复杂得多。为什么?因为波动率微笑(vol smile)的形状不是单调的——它通常是两边翘、中间凹的U形。你想想看,深度虚值和深度实值的波动率都比平值高,这个形状用简单的线性插值根本搞不定。
我一般分两步走:
- 先判断数据密度——如果行权价间隔小于2%(比如每0.5%一个行权价),直接用三次样条就够。
- 如果数据稀疏(比如只有5个行权价),我会先用SVI模型拟合参数,再用拟合后的曲线做插值。
这里给一个三次样条插值的代码示例:
import numpy as np
from scipy.interpolate import CubicSpline
# 已知行权价和对应的隐含波动率
strikes = np.array([0.90, 0.95, 1.00, 1.05, 1.10]) # 行权价/现货价格
ivs = np.array([0.25, 0.22, 0.20, 0.22, 0.26]) # 隐含波动率
# 创建三次样条插值器
cs = CubicSpline(strikes, ivs, bc_type='natural')
# 在更细的网格上求值
fine_strikes = np.linspace(0.88, 1.12, 50)
fine_ivs = cs(fine_strikes)
注意这里我用了 bc_type='natural',意思是边界处二阶导数为零。这是最常用的边界条件,能保证曲线在两端自然收敛。
关键点:行权价方向的插值,一定要保证插值后的曲线仍然是“微笑”形状的。如果插出来是平的,或者出现了多个波峰波谷,那说明你的插值方法选错了,或者数据有问题。
9.3 插值边界处理
好,终于到了最容易翻车的部分——边界处理。什么叫边界?就是你的插值范围之外的那些点。比如你的数据行权价范围是0.90到1.10,但客户要一个1.15的期权,怎么办?
边界处理有两种主流思路:
| 方法 | 做法 | 适用场景 | 风险 |
|---|---|---|---|
| 外推(Extrapolation) | 用边界处的斜率继续延伸 | 数据密集、趋势明确 | 可能产生极端值 |
| 截断(Clamping) | 超出范围的值取边界值 | 数据稀疏、保守场景 | 可能低估风险 |
| 常数外推 | 边界外波动率保持不变 | 深度虚值/实值 | 与市场实际不符 |
我个人习惯是:到期日方向用截断,行权价方向用常数外推。为什么?
- 到期日方向:你不可能外推出一个比最短到期日还短的波动率,那没有金融意义。截断到最近的数据点,反而更安全。
- 行权价方向:深度虚值的波动率变化已经很小了,常数外推虽然不完美,但至少不会产生离谱的值。
避坑指南:我曾经在行权价方向用了线性外推,结果在1.20的位置外推出了一个0.35的波动率,而实际市场只有0.28。那次交易差点因为定价偏高而错失机会。从那以后,我对边界外推都加了“硬限制”——波动率不能低于历史最低,也不能高于历史最高。
最后,给一个边界处理的实用代码:
def safe_interpolate(cs, x_new, x_min, x_max, fill_value='clamp'):
"""
安全的插值函数,带边界处理
cs: CubicSpline对象
x_new: 需要求值的点
x_min, x_max: 数据范围
fill_value: 'clamp' 截断, 'extrapolate' 外推, 'constant' 常数
"""
if fill_value == 'clamp':
x_new = np.clip(x_new, x_min, x_max)
return cs(x_new)
elif fill_value == 'extrapolate':
return cs(x_new, extrapolate=True)
elif fill_value == 'constant':
result = cs(x_new, extrapolate=True)
result[x_new < x_min] = cs(x_min)
result[x_new > x_max] = cs(x_max)
return result
好了,一维插值这块就聊到这儿。记住三个核心:时间轴用方差加权,行权价轴用三次样条,边界处理宁保守勿激进。把这些基础打牢了,下一章我们就能上二维插值了——那才是真正构建完整曲面的开始。
最后一句:插值不是越复杂越好。我见过有人用神经网络做一维插值,结果过拟合得一塌糊涂。简单方法+合理边界,往往比花里胡哨的模型更可靠。
公众号:蓝海资料掘金营,微信deep3321