3. 隐含波动率曲面拟合与做市报价

任务目标

本案例基于市场期权价格数据,构建隐含波动率曲面(Implied Volatility Surface),并利用该曲面为任意行权价与到期日的期权生成一致性的做市报价。核心解决两个问题:一是从离散的期权报价中提取隐含波动率;二是对提取的波动率进行曲面拟合,消除单一合约报价的噪声与套利不一致性。

实现思路

采用两步法:第一步,利用Black-Scholes模型对市场期权价格反解出各合约的隐含波动率(IV);第二步,以行权价与到期时间为自变量,对IV进行二维插值拟合,形成光滑曲面。做市报价时,输入任意行权价与剩余期限,从曲面插值得到IV,再代回BS公式计算理论价格,并叠加买卖价差输出双边报价。

关键代码

import numpy as np
from scipy.optimize import brentq
from scipy.interpolate import griddata
from scipy.stats import norm

# ---------- 1. Black-Scholes定价与IV反解 ----------
def bs_price(S, K, T, r, sigma, option_type='call'):
    if T <= 0 or sigma <= 0:
        return np.nan
    d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
    d2 = d1 - sigma*np.sqrt(T)
    if option_type == 'call':
        return S*norm.cdf(d1) - K*np.exp(-r*T)*norm.cdf(d2)
    else:
        return K*np.exp(-r*T)*norm.cdf(-d2) - S*norm.cdf(-d1)

def implied_vol(market_price, S, K, T, r, option_type='call'):
    """反解隐含波动率,使用二分法"""
    if T <= 0 or market_price <= 0:
        return np.nan
    try:
        return brentq(lambda sig: bs_price(S, K, T, r, sig, option_type) - market_price,
                      1e-4, 5.0)
    except ValueError:
        return np.nan

# ---------- 2. 曲面拟合 ----------
def fit_iv_surface(quotes, S, r):
    """
    quotes: list of dict, 每个元素含 K, T, market_price, option_type
    返回插值函数 f(K, T) -> sigma
    """
    points = []
    values = []
    for q in quotes:
        iv = implied_vol(q['market_price'], S, q['K'], q['T'], r, q['option_type'])
        if not np.isnan(iv):
            points.append([q['K'], q['T']])
            values.append(iv)
    points = np.array(points)
    values = np.array(values)
    # 使用三次立方插值,fill_value设为边界值外推
    def surface(K_new, T_new):
        return griddata(points, values, (K_new, T_new), method='cubic',
                        fill_value=np.nan)
    return surface

# ---------- 3. 做市报价 ----------
def market_make_quote(surface, S, K, T, r, spread_bps=50, option_type='call'):
    """
    基于曲面给出双边报价
    spread_bps: 买卖价差,单位基点(1bp=0.0001)
    """
    sigma = surface(K, T)
    if np.isnan(sigma):
        return None  # 曲面外无法报价
    mid_price = bs_price(S, K, T, r, sigma, option_type)
    half_spread = mid_price * spread_bps / 10000
    bid = mid_price - half_spread
    ask = mid_price + half_spread
    return {'mid': mid_price, 'bid': bid, 'ask': ask, 'iv': sigma}

# ---------- 4. 演示:构造模拟市场数据并报价 ----------
if __name__ == '__main__':
    S = 100.0      # 标的现价
    r = 0.02       # 无风险利率
    # 模拟市场报价:不同K和T的看涨期权
    market_quotes = []
    for K in [90, 95, 100, 105, 110]:
        for T in [0.1, 0.2, 0.5, 1.0]:
            true_iv = 0.2 + 0.05*(K-100)/100 + 0.03*T  # 真实曲面
            price = bs_price(S, K, T, r, true_iv, 'call')
            market_quotes.append({'K': K, 'T': T, 'market_price': price,
                                  'option_type': 'call'})

    # 拟合曲面
    surf = fit_iv_surface(market_quotes, S, r)

    # 对曲面内任意点报价
    test_points = [(98, 0.3), (103, 0.7), (105, 0.15)]
    for K, T in test_points:
        quote = market_make_quote(surf, S, K, T, r)
        if quote:
            print(f"K={K}, T={T}: mid={quote['mid']:.3f}, "
                  f"bid={quote['bid']:.3f}, ask={quote['ask']:.3f}, IV={quote['iv']:.4f}")
        else:
            print(f"K={K}, T={T}: 曲面外,无法报价")

运行与验证

运行方式:直接执行脚本,需安装 numpy、scipy。代码末尾的演示部分会输出三个测试点的双边报价。

验证方法:

  1. IV反解正确性:将输出的IV与构造数据时使用的真实IV(true_iv)对比,误差应小于0.001(因插值平滑略有偏差属正常)。
  2. 曲面一致性:对任意测试点,输出的bid < mid < ask,且价差约为mid的50bp。
  3. 无套利检查:对同一到期日,不同行权价的报价应满足凸性关系(可自行验证相邻K的价差合理性)。

预期输出示例(数值随插值略有浮动):

K=98, T=0.3: mid=4.123, bid=4.102, ask=4.144, IV=0.2187
K=103, T=0.7: mid=5.876, bid=5.846, ask=5.906, IV=0.2265
K=105, T=0.15: mid=1.234, bid=1.228, ask=1.240, IV=0.2078

常见问题

  • 插值出现NaN:当请求的点超出已知数据范围时,griddata返回NaN,此时做市商应拒绝报价或使用边界IV近似,本实现选择拒绝。
  • 实盘数据噪声:本案例使用模拟无噪声数据演示核心逻辑。实盘需先对异常报价(如买卖价差倒挂、深度虚值流动性差)进行过滤,否则反解IV可能失败。
  • 曲面外推风险:对超出样本范围的期限或行权价,插值结果不可靠。建议实盘中限制报价范围在曲面覆盖区域内。