🚀 AI 一键生成 qmt 策略代码

QMT 平台期权交易进阶:如何实现波动率偏斜套利策略?

qmt | 发布时间: 2025/12/2
以下内容由 EasyQuant 生成。

问题描述

如何利用期权的波动率偏斜现象构建一个套利策略?

解决方案

这是一个关于利用期权波动率偏斜(Volatility Skew)构建套利策略的详细指南。

策略概述:波动率偏斜套利

波动率偏斜是指同一标的资产、同一到期日但行权价不同的期权,其隐含波动率(IV)不一致的现象。在股票和指数期权市场中,通常表现为“偏斜”(Skew)或“微笑”(Smirk)形态,即虚值认沽期权(OTM Put)的隐含波动率往往高于平值(ATM)或虚值认购期权(OTM Call)。

策略逻辑(垂直价差策略):
本策略旨在利用过度陡峭的波动率偏斜获利。

  1. 观察: 监控虚值认沽期权(OTM Put)与平值认沽期权(ATM Put)之间的隐含波动率差值(IV Spread)。
  2. 信号: 当 IV Spread 超过设定阈值(例如 5%),认为市场对下行风险过度定价(OTM Put 太贵)。
  3. 执行:
    • 卖出 高 IV 的虚值认沽期权(Short OTM Put)。
    • 买入 较低 IV 的平值认沽期权(Long ATM Put)作为保护。
    • 这构成了一个牛市看跌价差(Bull Put Spread),即贷方价差(Credit Spread)。
  4. 获利: 随着时间流逝或波动率偏斜回归正常,价差缩小或期权归零,策略获利。

QMT 策略代码实现

以下代码展示了如何在 QMT 中计算隐含波动率并执行基于偏斜的套利交易。

# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import time
from datetime import datetime

def init(ContextInfo):
    # 1. 策略参数设置
    ContextInfo.underlying = '510050.SH'  # 标的:上证50ETF
    ContextInfo.account_id = 'YOUR_ACCOUNT_ID' # 请替换为您的资金账号
    ContextInfo.account_type = 'STOCK_OPTION' # 账号类型:期权
    ContextInfo.skew_threshold = 0.05     # 偏斜阈值:5% (OTM IV - ATM IV)
    ContextInfo.trade_qty = 1             # 交易手数
    ContextInfo.holding = False           # 持仓状态标记
    
    # 2. 设置账号
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 3. 费率与无风险利率设置
    ContextInfo.risk_free_rate = 0.03     # 无风险利率 3%
    ContextInfo.dividend_rate = 0.0       # 股息率
    
    print("波动率偏斜策略初始化完成")

def handlebar(ContextInfo):
    # 获取当前K线位置
    index = ContextInfo.barpos
    realtime = ContextInfo.get_bar_timetag(index)
    current_date_str = timetag_to_datetime(realtime, '%Y%m%d')
    
    # 1. 获取标的最新价格
    underlying_data = ContextInfo.get_market_data_ex(
        ['close'], [ContextInfo.underlying], period='1d', count=1, subscribe=True
    )
    if ContextInfo.underlying not in underlying_data:
        return
    S = underlying_data[ContextInfo.underlying].iloc[-1]['close']
    
    # 2. 获取期权链
    # 获取当月或次月合约(这里简化为获取当前交易日可交易的期权)
    # 注意:实际生产中应筛选特定到期月份
    option_list = ContextInfo.get_option_list(ContextInfo.underlying, current_date_str, "PUT", True)
    
    if not option_list:
        print("未获取到期权合约列表")
        return

    # 3. 筛选 ATM 和 OTM 合约
    # 获取合约详细信息以得到行权价和到期日
    options_meta = []
    for opt_code in option_list:
        detail = ContextInfo.get_option_detail_data(opt_code)
        if detail:
            options_meta.append({
                'code': opt_code,
                'strike': detail['OptExercisePrice'],
                'expire': detail['ExpireDate']
            })
    
    if not options_meta:
        return

    # 过滤出最近到期月的合约(简化逻辑:取列表中第一个到期日)
    target_expire = min([x['expire'] for x in options_meta])
    target_options = [x for x in options_meta if x['expire'] == target_expire]
    
    # 计算剩余天数 (Days to Maturity)
    expire_date = datetime.strptime(str(target_expire), '%Y%m%d')
    curr_date = datetime.strptime(current_date_str, '%Y%m%d')
    days_to_maturity = (expire_date - curr_date).days
    
    if days_to_maturity <= 0:
        return # 到期日不交易
        
    days_year = days_to_maturity / 365.0

    # 寻找 ATM (平值) 和 OTM (虚值)
    # ATM: 行权价最接近标的价格
    # OTM Put: 行权价 < 标的价格 (这里取比ATM低两档的合约作为示例)
    
    sorted_options = sorted(target_options, key=lambda x: x['strike'])
    
    # 找到 ATM 索引
    atm_index = -1
    min_diff = float('inf')
    for i, opt in enumerate(sorted_options):
        diff = abs(opt['strike'] - S)
        if diff < min_diff:
            min_diff = diff
            atm_index = i
            
    if atm_index == -1 or atm_index < 2:
        return # 找不到合适的合约

    atm_opt = sorted_options[atm_index]
    otm_opt = sorted_options[atm_index - 2] # 选取行权价更低的虚值期权
    
    # 4. 获取期权市场价格
    opt_codes = [atm_opt['code'], otm_opt['code']]
    opt_market_data = ContextInfo.get_market_data_ex(
        ['close'], opt_codes, period='1d', count=1, subscribe=True
    )
    
    if atm_opt['code'] not in opt_market_data or otm_opt['code'] not in opt_market_data:
        return
        
    price_atm = opt_market_data[atm_opt['code']].iloc[-1]['close']
    price_otm = opt_market_data[otm_opt['code']].iloc[-1]['close']
    
    # 5. 计算隐含波动率 (IV)
    # bsm_iv(optionType, objectPrices, strikePrice, optionPrice, riskFree, days, dividend)
    # 注意:days 参数在 bsm_iv 中通常指年化时间或具体天数,QMT API文档中days为剩余天数
    
    iv_atm = ContextInfo.bsm_iv(
        'P', S, atm_opt['strike'], price_atm, 
        ContextInfo.risk_free_rate, days_year * 365, ContextInfo.dividend_rate
    )
    
    iv_otm = ContextInfo.bsm_iv(
        'P', S, otm_opt['strike'], price_otm, 
        ContextInfo.risk_free_rate, days_year * 365, ContextInfo.dividend_rate
    )
    
    # 过滤无效IV
    if np.isnan(iv_atm) or np.isnan(iv_otm) or iv_atm <= 0.001:
        return

    skew = iv_otm - iv_atm
    
    print(f"标的: {S:.3f}, ATM({atm_opt['strike']}) IV: {iv_atm:.2%}, OTM({otm_opt['strike']}) IV: {iv_otm:.2%}, Skew: {skew:.2%}")

    # 6. 交易逻辑
    # 如果未持仓且偏斜过大 (OTM IV 远高于 ATM IV),卖出 OTM,买入 ATM
    if not ContextInfo.holding and skew > ContextInfo.skew_threshold:
        print(f"触发偏斜套利信号: Skew {skew:.2%} > {ContextInfo.skew_threshold:.2%}")
        
        # 卖出 OTM Put (Short High IV)
        sell_open(otm_opt['code'], ContextInfo.trade_qty, 'LATEST', 0, ContextInfo, ContextInfo.account_id)
        
        # 买入 ATM Put (Long Low IV) - 保护腿
        buy_open(atm_opt['code'], ContextInfo.trade_qty, 'LATEST', 0, ContextInfo, ContextInfo.account_id)
        
        ContextInfo.holding = True
        ContextInfo.holding_codes = [otm_opt['code'], atm_opt['code']]

    # 7. 平仓逻辑 (简化:当偏斜回归或到期前平仓)
    # 这里演示:如果 Skew 回归到 1% 以内,平仓获利
    elif ContextInfo.holding:
        # 重新计算当前持仓合约的 Skew (逻辑同上,略)
        # 简单起见,这里假设如果 Skew < 0.01 则平仓
        if skew < 0.01:
            print(f"偏斜回归,平仓: Skew {skew:.2%}")
            # 平 OTM 空头 (买入平仓)
            buy_close_tdayfirst(ContextInfo.holding_codes[0], ContextInfo.trade_qty, 'LATEST', 0, ContextInfo, ContextInfo.account_id)
            # 平 ATM 多头 (卖出平仓)
            sell_close_tdayfirst(ContextInfo.holding_codes[1], ContextInfo.trade_qty, 'LATEST', 0, ContextInfo, ContextInfo.account_id)
            
            ContextInfo.holding = False

代码关键点解析

  1. 数据获取 (get_market_data_ex & get_option_list):

    • 首先获取标的资产(如 510050.SH)的价格。
    • 使用 get_option_list 获取当前可交易的认沽(PUT)期权列表。
    • 使用 get_option_detail_data 获取行权价和到期日,以便筛选出 ATM 和 OTM 合约。
  2. 隐含波动率计算 (bsm_iv):

    • 这是策略的核心。QMT 内置了 ContextInfo.bsm_iv 函数,基于 Black-Scholes 模型反推波动率。
    • 参数注意:需要传入标的价格、行权价、期权现价、无风险利率、剩余天数等。
    • Skew 计算Skew = IV_OTM - IV_ATM
  3. 合约选择逻辑:

    • ATM (平值):行权价最接近标的现价的合约。
    • OTM (虚值):对于认沽期权,行权价低于标的现价。代码中简单选取了比 ATM 低两档(index - 2)的合约。
  4. 交易执行:

    • 开仓:当 Skew > 阈值 时,卖出高 IV 的 OTM Put (sell_open),买入低 IV 的 ATM Put (buy_open)。
    • 平仓:当 Skew 回归正常(例如小于 1%)时,反向操作平仓。

注意事项

  1. 数据频率:上述代码在 handlebar 中运行,如果是日线周期,每天计算一次。对于波动率套利,通常建议使用分钟线(如 15m1h)以捕捉盘中机会。
  2. 保证金风险:卖出期权(Short Put)涉及保证金交易。虽然买入了 ATM Put 构成了价差组合(Spread),限制了最大亏损,但实盘中需确保账户有足够的保证金。
  3. 流动性风险:深度虚值期权的流动性可能较差,买卖价差(Bid-Ask Spread)较大,可能会吞噬套利利润。建议在下单时使用限价单(FIX)而非市价单。
  4. 模型假设:BSM 模型假设波动率是常数,而我们正是利用波动率不为常数(偏斜)来获利,计算出的 IV 仅作为相对参考指标。

Q&A

Q: 为什么通常使用认沽期权(Put)来做波动率偏斜套利?
A: 在股票市场中,投资者通常更担心市场崩盘而非暴涨,因此对虚值认沽期权(OTM Put)有强烈的避险需求,导致其价格被推高,隐含波动率显著高于平值期权,形成了典型的“波动率微笑”左侧高翘的形态。

Q: ContextInfo.bsm_iv 计算失败返回什么?
A: 如果输入参数不合理(例如期权价格低于内在价值,或者计算不收敛),该函数可能返回 nan (Not a Number)。代码中必须包含 np.isnan() 的检查逻辑,否则会导致程序报错或产生错误信号。

Q: 如何优化合约的选择?
A: 代码中简单使用了“低两档”的合约。更严谨的做法是根据 Delta 值来选择,例如选择 Delta = -0.5 的作为 ATM,Delta = -0.2 的作为 OTM,这样能更标准化地衡量偏斜程度。