第21章 曲面一致性检验:无套利条件、日历价差与蝶式价差检验

波动率曲面这东西,看着挺漂亮,对吧?但漂亮不代表能用。

我见过不少交易员,拿着一个看似完美的曲面去定价,结果一算套利空间,发现全是漏洞。说白了,曲面如果不满足一致性条件,你用它做出来的策略,可能一开始就输在起跑线上。

这一章,我们就来聊聊怎么给波动率曲面做「体检」。重点看三个指标:无套利条件、日历价差检验、蝶式价差检验。

21.1 为什么曲面需要一致性检验?

先问个问题:你凭什么相信一个波动率曲面是合理的?

凭它拟合得好?凭它平滑?不够。真正的检验标准是——它能不能被用来构造无风险套利。

如果存在套利机会,说明曲面内部有矛盾。要么是数据错了,要么是模型错了。无论哪种,你都不能用它做交易决策。

我个人习惯,拿到一个新曲面,第一件事不是看形状,而是跑一遍一致性检验。这就像开车前检查刹车,虽然麻烦,但能救命。

核心原则:一个有效的波动率曲面,必须同时满足日历价差无套利和蝶式价差无套利。否则,它就是一张废纸。

21.2 无套利条件的基本框架

无套利条件,说白了就是「不能白捡钱」。在期权市场里,这体现为两个维度:

  • 时间维度(日历价差):同一行权价、不同到期日的期权,价格不能出现倒挂。
  • 截面维度(蝶式价差):同一到期日、不同行权价的期权,价格不能出现凸性违反。

这两个条件,一个管纵向,一个管横向。交叉起来,就构成了曲面的「骨架」。

我曾经遇到过一个团队,他们用机器学习拟合曲面,结果R²高达0.99。但一跑日历价差检验,发现超过30%的节点存在套利机会。为什么?因为模型只关注拟合精度,没考虑金融约束。这就是典型的「过拟合但无效」。

21.3 日历价差检验

日历价差,也叫时间价差。它检验的是:对于同一个行权价,远月期权的价格是否应该高于近月?

答案是:应该。因为远月期权有更多的时间价值。如果远月比近月还便宜,那就存在套利机会——买入远月、卖出近月,锁定利润。

具体检验方法如下:

def check_calendar_arbitrage(surface, strike):
    """
    检验给定行权价的日历价差无套利条件
    surface: 波动率曲面对象,包含不同到期日的隐含波动率
    strike: 行权价
    返回: True 表示无套利,False 表示存在套利
    """
    # 获取该行权价下所有到期日的隐含波动率
    ivs = surface.get_iv_by_strike(strike)
    
    # 按到期日排序
    maturities = sorted(ivs.keys())
    
    for i in range(len(maturities) - 1):
        t1 = maturities[i]
        t2 = maturities[i + 1]
        iv1 = ivs[t1]
        iv2 = ivs[t2]
        
        # 核心条件:远月隐含波动率 >= 近月隐含波动率
        # 注意:这里用方差形式更严谨
        var1 = iv1 ** 2 * t1
        var2 = iv2 ** 2 * t2
        
        if var2 < var1 - 1e-6:  # 容忍小误差
            print(f"发现日历套利: 行权价={strike}, 到期日={t1}->{t2}")
            return False
    
    return True

这里有个细节要注意:直接用隐含波动率比较是不对的。因为不同到期日的波动率水平天然不同,应该比较的是「总方差」,也就是 IV² × T。

为什么?你想想看,波动率是年化概念。一个30天到期的期权,年化波动率20%,和一个60天到期的期权,年化波动率19%,哪个更贵?算一下总方差就知道了。

实战技巧:我在做日历价差检验时,通常会给一个容忍度(比如1e-6)。因为市场微观结构会导致微小偏差,完全零容忍反而会误报。但容忍度不能太大,否则会漏掉真正的套利机会。

21.4 蝶式价差检验

蝶式价差检验的是凸性条件。它要求:对于同一到期日,隐含波动率关于行权价的二阶导数不能为负。

换句话说,波动率微笑必须是凸的。如果出现凹形区域,就可以构造蝶式套利。

蝶式价差的构造很简单:买入一个低行权价期权,买入一个高行权价期权,卖出两个中间行权价期权。如果这个组合的价格为负,就存在套利。

def check_butterfly_arbitrage(surface, maturity):
    """
    检验给定到期日的蝶式价差无套利条件
    surface: 波动率曲面对象
    maturity: 到期日
    返回: True 表示无套利,False 表示存在套利
    """
    # 获取该到期日下所有行权价的隐含波动率
    ivs = surface.get_iv_by_maturity(maturity)
    
    # 按行权价排序
    strikes = sorted(ivs.keys())
    
    for i in range(1, len(strikes) - 1):
        k1 = strikes[i - 1]
        k2 = strikes[i]
        k3 = strikes[i + 1]
        
        iv1 = ivs[k1]
        iv2 = ivs[k2]
        iv3 = ivs[k3]
        
        # 计算蝶式价差价格(用Black-Scholes近似)
        # 核心条件:中间行权价的隐含波动率不能高于两端插值
        # 即:波动率曲线必须是凸的
        
        # 线性插值得到中间点的理论波动率
        weight = (k3 - k2) / (k3 - k1)
        iv_interp = weight * iv1 + (1 - weight) * iv3
        
        if iv2 > iv_interp + 1e-6:
            print(f"发现蝶式套利: 到期日={maturity}, 行权价={k1},{k2},{k3}")
            return False
    
    return True

这段代码的逻辑是:如果中间行权价的隐含波动率高于两端插值,说明曲线是凹的,存在套利。

嗯,这里要注意一个坑。蝶式价差检验对数据质量非常敏感。如果输入数据有噪音,很容易误报。我建议先对曲面做平滑处理,再做检验。

警告:蝶式价差检验不能只看隐含波动率,还要考虑行权价间距。如果行权价间距不均匀,直接比较波动率会出错。正确的做法是计算期权价格,再构造蝶式组合。

21.5 完整的曲面一致性检验流程

在实际项目中,我不会只做单项检验。我会把日历价差和蝶式价差结合起来,形成一个完整的检验流水线。

下面是我常用的流程:

  1. 数据清洗:剔除异常报价,处理缺失值
  2. 曲面拟合:用SVI或样条方法构建初始曲面
  3. 日历价差检验:遍历所有行权价,检查时间维度一致性
  4. 蝶式价差检验:遍历所有到期日,检查截面维度一致性
  5. 套利修正:对违反条件的区域进行局部调整
  6. 重新检验:迭代直到所有条件满足

这个流程看起来简单,但做起来很考验耐心。我记得有一次,为了修正一个外汇期权曲面的套利问题,我迭代了7轮才通过所有检验。

21.6 知识体系结构图

下面这张图,把曲面一致性检验的核心逻辑串起来了。你可以把它当作一个检查清单。

曲面一致性检验知识体系 波动率曲面 日历价差检验 蝶式价差检验 同一行权价 不同到期日 总方差比较 同一到期日 不同行权价 凸性条件 无套利曲面 两个条件同时满足,曲面才可用于交易决策

21.7 实战中的常见问题

做一致性检验这么多年,我踩过不少坑。挑几个典型的说说:

  • 数据频率问题:用日频数据做检验,结果全是套利。换成分钟频数据,套利就消失了。为什么?因为日频数据是收盘价,包含了隔夜信息,而分钟频数据更能反映实时市场。
  • 行权价稀疏问题:有些品种行权价间距很大,蝶式价差检验会漏掉中间区域的套利。我建议用插值方法补全中间行权价,再做检验。
  • 到期日不匹配问题:不同到期日的交易日数不同,直接用日历天数会出错。应该用实际交易日数或更精确的时间度量。

我的经验:一致性检验不是一次性工作。每次更新曲面后,都应该重新跑一遍。我习惯把它写成自动化脚本,每天开盘前自动运行一次。如果发现套利,就暂停使用该曲面,直到问题解决。

好了,关于曲面一致性检验,核心就是这些。记住一句话:一个不能通过检验的曲面,再漂亮也是摆设。做交易,首先要保证你的工具是可靠的。


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