第二十九章 回测框架:将平滑曲面应用于期权定价与对冲策略回测

好了,终于到了把前面所有平滑技术串起来的时候了。

说实话,很多人在做波动率曲面平滑时,容易陷入一个误区——把曲面做得特别漂亮,各种样条、各种正则化,结果一放到回测里,效果反而不如原始数据。为什么?因为平滑本身不是目的,目的是让定价更准、对冲更稳。

我个人习惯是:先跑一个裸的回测,再逐步加入平滑曲面,对比着看。这样你才能知道,你花了大把时间调的平滑参数,到底值不值。

29.1 回测框架的核心设计思路

回测框架说白了就三件事:

  • 数据输入:历史期权链数据、标的资产价格、无风险利率
  • 策略逻辑:你用什么规则交易?是delta对冲?还是波动率套利?
  • 曲面引擎:每次定价时,从平滑后的曲面上取波动率

我在项目中遇到过最坑的事,就是回测时每次都要重新平滑整个曲面。你想想看,如果数据量一大,回测跑一天都出不来结果。所以,预计算是关键。

核心原则:曲面平滑只做一次,回测时直接查表插值。

29.2 数据预处理与曲面缓存

我们先看数据怎么组织。假设你每天都有期权数据,我建议按以下结构存储:

# 伪代码:曲面缓存结构
class VolSurfaceCache:
    def __init__(self):
        self.surfaces = {}  # key: 日期, value: 平滑后的曲面对象
    
    def build_from_chain(self, date, option_chain):
        # 用前面章节的SVI或样条方法平滑
        surface = smooth_surface(option_chain)
        self.surfaces[date] = surface
    
    def get_vol(self, date, strike, expiry):
        surface = self.surfaces[date]
        return surface.interpolate(strike, expiry)

嗯,这里要注意:不要把原始数据直接丢进回测循环。我见过有人每天在回测里重新拟合SVI参数,结果一个月的回测跑了三天。正确的做法是,先离线把所有日期的曲面都算好,存成pickle或者parquet文件。

29.3 期权定价引擎集成

有了平滑曲面,定价就变成了查表。我们以欧式期权为例:

def price_option(S, K, T, r, surface, date, option_type='call'):
    # 从平滑曲面获取波动率
    sigma = surface.get_vol(date, K, T)
    
    # 标准Black-Scholes定价
    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':
        price = S*norm.cdf(d1) - K*np.exp(-r*T)*norm.cdf(d2)
    else:
        price = K*np.exp(-r*T)*norm.cdf(-d2) - S*norm.cdf(-d1)
    
    return price

你可能会问:直接用原始隐含波动率不行吗?行,但你会遇到两个问题:

  • 原始数据有缺失,某些行权价没报价
  • 原始数据有噪音,导致对冲时delta跳来跳去

平滑曲面解决的就是这两个问题。我曾经在回测中对比过,用平滑曲面后,delta对冲的换手率降低了约15%。

29.4 对冲策略回测示例

我们做一个最简单的delta中性策略。每天收盘前,根据平滑曲面计算期权delta,然后买卖标的资产使组合delta归零。

def run_delta_hedge_backtest(data, surface_cache):
    positions = {'option': 0, 'stock': 0}
    pnl = []
    
    for date in sorted(data.keys()):
        row = data[date]
        S = row['spot_price']
        K = row['strike']
        T = row['time_to_expiry']
        r = row['rate']
        
        # 用平滑曲面算delta
        sigma = surface_cache.get_vol(date, K, T)
        delta = black_scholes_delta(S, K, T, r, sigma)
        
        # 调整标的仓位
        target_stock = -delta * positions['option']
        trade_stock = target_stock - positions['stock']
        positions['stock'] = target_stock
        
        # 记录损益
        pnl.append(compute_pnl(positions, row))
    
    return pnl

小技巧:回测时建议同时跑两个版本——一个用原始波动率,一个用平滑曲面。这样你就能直观看到平滑带来的收益提升。我自己的经验是,平滑曲面在深度虚值期权上的效果最明显。

29.5 回测结果评估指标

光跑回测不行,你得知道怎么评价。我一般看这几个指标:

指标 说明 平滑曲面预期效果
年化收益率 策略总收益年化 略有提升
夏普比率 风险调整后收益 明显提升
最大回撤 最差情况下的亏损 降低
换手率 交易频率 降低(对冲更稳)
对冲误差 实际delta与目标delta的偏差 显著降低

说实话,我最看重的是对冲误差。因为平滑曲面最大的价值,就是让delta更稳定。你想想看,如果delta每天跳来跳去,你就要频繁交易,成本全被手续费吃掉了。

29.6 核心流程图:回测框架整体架构

下面这张图是我自己项目里用的框架结构,你可以参考:

原始期权链数据 波动率曲面平滑 (SVI/样条/核方法) 曲面缓存 回测引擎 每日:查曲面 → 定价 → 计算delta → 调仓 策略逻辑 损益计算与记录 绩效评估:夏普、回撤、对冲误差 注:虚线框表示可选模块,实线为必选流程

避坑指南:我曾经在回测中忘记考虑交易成本,结果平滑曲面带来的换手率降低优势完全没体现出来。记住,交易成本是回测的照妖镜,一定要加进去。

29.7 实战中的几个坑

最后分享几个我踩过的坑:

  • 曲面外推问题:回测中可能会遇到行权价超出历史范围的情况。我的做法是,用最临近的波动率填充,不要外推太远。
  • 时间衰减处理:期权到期日临近时,曲面会变得很不稳定。我建议到期前3天就剔除这些数据。
  • 分红与拆股:这些事件会导致价格跳跃,平滑曲面要重新校准。别偷懒,该重算就重算。

嗯,回测框架这块内容其实挺多的。但核心就一句话:平滑曲面是工具,回测是检验工具的唯一标准。你花再多时间调参数,最后都要回到回测结果上来。

总结:把平滑曲面集成到回测框架中,本质上就是三步——预计算曲面、查表定价、对比评估。别搞复杂了。


公众号:蓝海资料掘金营,微信deep3321