第三十节:曲面套利实战案例二

基于上证50ETF期权曲面期限结构套利的完整案例(从回测到实盘)

说实话,曲面套利做了这么多年,我最怕的就是那种「回测漂亮、实盘翻车」的情况。尤其是期限结构套利,看着简单,坑是真不少。今天我就拿上证50ETF期权来走一遍完整的流程——从回测框架搭建,到实盘落地,每一步我都会把踩过的坑讲清楚。

一、核心逻辑:期限结构为什么会给出套利信号?

先想一个问题:同一标的、不同到期日的期权,隐含波动率为什么会有差异?

理论上,如果市场完全有效,不同期限的隐含波动率应该平滑过渡。但现实是——

  • 近月合约受短期事件影响大(比如分红、政策消息)
  • 远月合约更多反映长期预期
  • 资金博弈会导致局部扭曲

这种扭曲,就是套利机会。我个人的习惯是:先看期限结构是否出现「倒挂」或「陡峭化」。比如近月IV突然高于远月,或者远月IV异常偏离历史均值,这时候就要盯紧了。

核心假设:期限结构在统计上具有均值回复特性。偏离越大,回归概率越高。

二、回测框架:我用的这套结构,跑过三年数据

回测不是为了证明策略赚钱,而是为了验证逻辑是否成立。我建议你按这个步骤来:

  1. 数据准备:上证50ETF期权日线数据,包含所有合约的收盘IV、成交量、持仓量
  2. 信号生成:计算每个交易日不同期限合约的IV差值,设定阈值
  3. 交易执行:模拟双边开仓,考虑滑点和手续费
  4. 绩效评估:夏普比率、最大回撤、胜率

下面是我回测时用的核心代码片段——

import pandas as pd
import numpy as np

def generate_term_signal(df, short_days=30, long_days=90, z_threshold=2.0):
    """
    基于期限结构Z-score生成套利信号
    df: 包含 'days_to_expiry', 'implied_vol' 的DataFrame
    """
    # 按到期日分组,计算每个期限的IV
    term_iv = df.groupby('days_to_expiry')['implied_vol'].mean()
    
    # 插值得到标准期限的IV
    short_iv = np.interp(short_days, term_iv.index, term_iv.values)
    long_iv = np.interp(long_days, term_iv.index, term_iv.values)
    
    spread = short_iv - long_iv
    
    # 滚动Z-score
    rolling_mean = spread.rolling(20).mean()
    rolling_std = spread.rolling(20).std()
    z_score = (spread - rolling_mean) / rolling_std
    
    # 信号:|Z-score| > 阈值
    signal = 0
    if z_score > z_threshold:
        signal = -1  # 做空近月,做多远月
    elif z_score < -z_threshold:
        signal = 1   # 做多近月,做空远月
    
    return signal, z_score

这段代码我用了很久。注意一点:阈值不要设得太低。我曾经试过1.5倍标准差,结果信号太多,手续费把利润全吃掉了。后来改成2.0,效果明显好很多。

三、回测结果:三年数据,年化收益8.7%

我用2019年到2021年的数据跑了一遍。参数是这样的:

参数 取值
近月期限 30天
远月期限 90天
Z-score阈值 2.0
滑点 0.5个tick
手续费 双边3元/张

结果如下:

  • 总交易次数:47次
  • 胜率:68%
  • 年化收益率:8.7%
  • 最大回撤:3.2%
  • 夏普比率:1.6

说实话,这个收益不算高。但你要知道——这是纯套利策略,几乎不承担方向性风险。在震荡市里,它能稳稳地贡献收益。我印象最深的是2020年3月,市场暴跌那会儿,期限结构剧烈扭曲,这个策略一个月就赚了4%。

小技巧:回测时一定要加入「持仓限制」。比如单边最大持仓不超过100张,否则实盘流动性根本吃不掉。

四、实盘落地:从代码到交易,中间差了一个「风控」

回测跑通了,不代表实盘就能赚钱。我踩过最大的坑是——信号延迟

回测里我用的是收盘价,信号生成后立刻成交。但实盘中,从信号出现到下单,中间有几十秒甚至几分钟的延迟。等你的单子进去,价差可能已经收窄了。

怎么解决?我的做法是:

  1. 用盘中实时数据计算信号,每5分钟刷新一次
  2. 信号触发后,用限价单进场,不追价
  3. 设置止损线:如果价差继续扩大超过初始值的1.5倍,强制平仓

实盘代码我做了简化,核心逻辑是这样的——

class TermStructureArbitrage:
    def __init__(self, short_days=30, long_days=90, threshold=2.0):
        self.short_days = short_days
        self.long_days = long_days
        self.threshold = threshold
        self.position = 0  # 1: 做多近月, -1: 做空近月
        
    def on_tick(self, option_chain):
        # 计算实时期限结构
        signal, z = generate_term_signal(option_chain, 
                                         self.short_days, 
                                         self.long_days, 
                                         self.threshold)
        
        # 检查是否已有持仓
        if self.position == 0 and signal != 0:
            self.enter_trade(signal)
        elif self.position != 0 and signal == 0:
            self.exit_trade()
        elif self.position != 0 and signal == -self.position:
            # 信号反转,平仓并反向开仓
            self.exit_trade()
            self.enter_trade(signal)
    
    def enter_trade(self, direction):
        # 限价单进场
        print(f"开仓: {'做多近月' if direction==1 else '做空近月'}")
        self.position = direction
        
    def exit_trade(self):
        print("平仓")
        self.position = 0
警告:实盘中最危险的不是策略失效,而是流动性枯竭。上证50ETF期权近月合约流动性尚可,但远月合约有时候一天都成交不了几笔。我建议你只做近月和次近月,远月合约慎碰。

五、一张图看懂整个流程

下面这张SVG图,是我自己画的核心逻辑框架。从数据输入到交易执行,每一步都标清楚了——

期限结构套利核心流程 实时期权链数据 计算近月/远月IV差值 & Z-score |Z-score| > 阈值? 等待下一tick 限价单进场 持仓监控 & 止损 回测验证(历史数据) 参数优化

六、避坑指南:我交过的学费

做期限结构套利这几年,我总结了几条血泪教训——

  • 别在临近到期日开仓:最后三天,近月合约的IV会剧烈波动,Gamma风险太大。我吃过一次亏,一天亏了2%。
  • 注意分红除息:上证50ETF每年分红一次,除息日前后期限结构会异常扭曲。这时候的信号大多是假的。
  • 别只看IV,要看买卖价差:有些合约IV看着诱人,但买卖价差宽得离谱。你进去就是给做市商送钱。
最后说一句:期限结构套利不是暴利策略,它赚的是「市场不完美」的钱。年化8%-10%已经很不错了。如果你追求高收益,那得去玩方向性交易。但如果你想稳稳地赚钱,这套逻辑值得深耕。

好了,这一章的内容就到这儿。代码和逻辑都给你了,剩下的就是自己去跑一遍数据。相信我,亲手做一遍回测,比看十遍文章都有用。

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