如何在Python中用样条插值波动率偏斜并解决负值问题
波动率偏斜曲线插值负值问题分析与解决
我有两组数据用于描述波动率曲面偏斜的y(x)函数:
- x_points(行权价):
[22.56, 27.07, 31.58, 36.10, 40.61, 45.12, 49.63, 54.14, 58.66, 63.17, 67.68] - y_points(波动率相对值):
[97.44, 87.32, 79.73, 75.47, 73.58, 74.53, 78.61, 83.64, 88.03, 92.26, 96.44]
尝试用三次样条插值生成0到200区间的曲线时,出现不符合预期的负值,代码如下:
def f(x): x = np.linspace(0, 200, 399) tck = interpolate.splrep(x_points, y_points) return interpolate.splev(x, tck)
问题原因
三次样条插值的核心是在给定数据点间生成二阶连续导数的曲线,但它没有针对外插区间的约束逻辑:
- 原始数据的x范围仅为22.5667.68,022.56和67.68~200属于外插区域
- 三次样条外插时会沿着边界点的导数趋势无限延伸,而原始数据两端的导数趋势未做约束时,极易出现向下震荡,导致本应非负的波动率出现负值
解决方案
1. 给三次样条添加边界约束
通过scipy.interpolate.splrep的bc_type参数设置边界条件,强制外插时曲线保持合理趋势。比如设置两端的一阶导数为0(平坦延伸),或根据原始数据趋势设置导数:
import numpy as np from scipy import interpolate x_points = [22.56, 27.07, 31.58, 36.10, 40.61, 45.12, 49.63, 54.14, 58.66, 63.17, 67.68] y_points = [97.44, 87.32, 79.73, 75.47, 73.58, 74.53, 78.61, 83.64, 88.03, 92.26, 96.44] def constrained_spline(x): # 设置边界一阶导数为0,让曲线在两端平坦延伸 tck = interpolate.splrep(x_points, y_points, bc_type=((1, 0.0), (1, 0.0))) return interpolate.splev(x, tck) # 生成0~200的采样点 x_new = np.linspace(0, 200, 399) y_new = constrained_spline(x_new)
2. 使用单调插值(Pchip)
原始数据呈现先递减后递增的单调趋势(从97.44降至73.58再升至96.44),PchipInterpolator可保证插值曲线严格遵循数据的单调性,彻底避免震荡和负值:
from scipy.interpolate import PchipInterpolator def pchip_interp(x): pchip = PchipInterpolator(x_points, y_points) return pchip(x) y_new_pchip = pchip_interp(x_new)
3. 分段处理插值与外插
将0~200区间分成三段,针对性处理:
- 0~22.56:按左端趋势做常数/线性延伸
- 22.56~67.68:用三次样条或Pchip插值
- 67.68~200:按右端趋势做常数/线性延伸
def piecewise_interp(x): y = np.zeros_like(x) # 左外插区间:常数延伸 left_mask = x < x_points[0] y[left_mask] = y_points[0] # 插值区间:三次样条 interp_mask = (x >= x_points[0]) & (x <= x_points[-1]) tck = interpolate.splrep(x_points, y_points) y[interp_mask] = interpolate.splev(x[interp_mask], tck) # 右外插区间:常数延伸 right_mask = x > x_points[-1] y[right_mask] = y_points[-1] return y y_new_piecewise = piecewise_interp(x_new)
4. 带非负约束的曲线拟合
如果需要更严格的非负约束,可通过优化方法拟合带约束的曲线(以5次多项式为例):
from scipy.optimize import minimize # 定义5次多项式拟合函数 def poly_func(params, x): return params[0] + params[1]*x + params[2]*x**2 + params[3]*x**3 + params[4]*x**4 + params[5]*x**5 # 定义损失函数 def loss(params, x, y): return np.sum((poly_func(params, x) - y)**2) # 初始参数与非负约束 init_params = np.zeros(6) cons = ({'type': 'ineq', 'fun': lambda params: poly_func(params, np.linspace(0,200,100))}) # 优化求解 result = minimize(loss, init_params, args=(x_points, y_points), constraints=cons) # 生成拟合结果 def constrained_poly(x): return poly_func(result.x, x) y_new_poly = constrained_poly(x_new)
内容的提问来源于stack exchange,提问作者Giovanni Venticinque
相关产品推荐
相关产品推荐

