问题描述
解决方案
获取期权隐含波动率及波动率套利策略
在 QMT 平台中,获取期权隐含波动率(Implied Volatility, IV)主要有两种方式:直接获取系统计算好的实时 IV,或者利用 BSM 模型根据市场价格自行反推。
波动率套利策略通常涉及 Delta 中性策略(如跨式组合 Straddle 或 宽跨式组合 Strangle)。
以下将详细介绍如何获取数据,并提供一个完整的 做多波动率(Long Straddle) 策略示例。该策略在隐含波动率处于历史低位时买入跨式组合(同时买入平值认购和认沽),在波动率回归高位或获利时平仓。
1. 获取隐含波动率的方法
方法一:直接获取实时 IV (推荐)
使用 ContextInfo.get_option_iv(optioncode) 接口。这是最简单的方法,直接返回交易所或柜台计算的实时数据。
iv = ContextInfo.get_option_iv('10003280.SHO')
print(f"实时隐含波动率: {iv}")
方法二:使用 BSM 模型反推
使用 ContextInfo.bsm_iv(...) 接口。这适用于回测或需要自定义无风险利率、剩余天数等参数的场景。
# 参数:期权类型, 标的价格, 行权价, 期权现价, 无风险利率, 剩余天数, 分红率
iv = ContextInfo.bsm_iv('C', 3.51, 3.5, 0.0725, 0.03, 15, 0)
2. 波动率套利策略实现 (做多波动率)
策略逻辑:
- 标的:50ETF (510050.SH) 及其对应的期权。
- 合约选择:选择当月到期的 平值 (ATM) 认购 (Call) 和 认沽 (Put) 期权。
- 入场条件:当平值期权的平均隐含波动率低于设定阈值(例如 15%),且当前无持仓时,构建买入跨式组合(Buy Call + Buy Put)。
- 出场条件:当平均隐含波动率高于设定阈值(例如 25%),或期权临近到期(剩余天数 < 3天)时,平掉所有仓位。
- 风控:简单的全平仓逻辑。
完整策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import datetime
import time
def init(ContextInfo):
# 1. 设置账户信息 (请替换为您的真实资金账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.account_type = 'STOCK_OPTION' # 账号类型:股票期权
ContextInfo.set_account(ContextInfo.account_id)
# 2. 策略参数设置
ContextInfo.underlying = '510050.SH' # 标的:50ETF
ContextInfo.low_iv_threshold = 0.15 # 入场阈值:IV < 15%
ContextInfo.high_iv_threshold = 0.25 # 出场阈值:IV > 25%
ContextInfo.days_to_expire_limit = 3 # 临近到期天数限制
ContextInfo.trade_unit = 1 # 每次交易张数
# 3. 全局变量
ContextInfo.holding_call = None # 当前持有的Call代码
ContextInfo.holding_put = None # 当前持有的Put代码
print("波动率套利策略初始化完成")
def handlebar(ContextInfo):
# 仅在最后一根K线(实时行情)或回测的每一根K线运行
# 如果是实时盘中,建议加上 ContextInfo.is_last_bar() 判断
# 获取当前时间
current_time = timetag_to_datetime(ContextInfo.get_bar_timetag(ContextInfo.barpos), '%Y%m%d')
# 1. 获取标的资产(50ETF)的最新价格
underlying_data = ContextInfo.get_market_data_ex(
['close'], [ContextInfo.underlying], period='1d', count=1, subscribe=True
)
if ContextInfo.underlying not in underlying_data:
return
underlying_price = underlying_data[ContextInfo.underlying].iloc[-1]['close']
# 2. 获取当月期权合约列表
# 获取当前年月,格式 YYYYMM
current_month = current_time[0:6]
# 获取认购和认沽合约列表
# 注意:get_option_list 第四个参数 True 表示只获取当前可交易的
calls = ContextInfo.get_option_list(ContextInfo.underlying, current_month, "CALL", True)
puts = ContextInfo.get_option_list(ContextInfo.underlying, current_month, "PUT", True)
if not calls or not puts:
print("未获取到期权合约列表")
return
# 3. 筛选平值合约 (ATM)
# 逻辑:寻找行权价与标的价格差值最小的合约
atm_call = get_atm_contract(ContextInfo, calls, underlying_price)
atm_put = get_atm_contract(ContextInfo, puts, underlying_price)
if not atm_call or not atm_put:
return
# 4. 获取隐含波动率 (IV)
iv_call = ContextInfo.get_option_iv(atm_call)
iv_put = ContextInfo.get_option_iv(atm_put)
# 异常值处理
if iv_call <= 0 or iv_put <= 0:
# 如果实时IV获取失败,可以使用bsm_iv自行计算,这里简单跳过
return
avg_iv = (iv_call + iv_put) / 2
# 5. 检查到期日
# 获取合约详细信息以计算剩余天数
detail = ContextInfo.get_instrumentdetail(atm_call)
expire_date_str = str(detail['ExpireDate']) # 格式 YYYYMMDD
days_left = days_between(current_time, expire_date_str)
print(f"日期:{current_time}, 标的价格:{underlying_price:.3f}, ATM Call:{atm_call}, ATM Put:{atm_put}, 平均IV:{avg_iv:.4f}, 剩余天数:{days_left}")
# 6. 交易逻辑
# 情况A:临近到期,强制平仓
if days_left <= ContextInfo.days_to_expire_limit:
if ContextInfo.holding_call or ContextInfo.holding_put:
print(f"临近到期({days_left}天),强制平仓")
close_positions(ContextInfo)
return
# 情况B:当前无持仓,且 IV 低于阈值 -> 开仓 (做多波动率)
if ContextInfo.holding_call is None and ContextInfo.holding_put is None:
if avg_iv < ContextInfo.low_iv_threshold:
print(f"IV ({avg_iv:.4f}) 低于阈值,开仓买入跨式组合")
# 买入认购
passorder(50, 1101, ContextInfo.account_id, atm_call, 5, -1, ContextInfo.trade_unit, ContextInfo)
# 买入认沽
passorder(50, 1101, ContextInfo.account_id, atm_put, 5, -1, ContextInfo.trade_unit, ContextInfo)
# 更新持仓记录
ContextInfo.holding_call = atm_call
ContextInfo.holding_put = atm_put
# 情况C:当前有持仓,且 IV 高于阈值 -> 平仓 (止盈)
elif ContextInfo.holding_call is not None and ContextInfo.holding_put is not None:
if avg_iv > ContextInfo.high_iv_threshold:
print(f"IV ({avg_iv:.4f}) 高于阈值,平仓获利")
close_positions(ContextInfo)
# 注意:实际交易中,如果标的价格大幅波动导致持仓不再是平值,
# 可能需要进行 Delta 对冲或移仓(Rolling),本示例简化为持有直到IV回归或到期。
def get_atm_contract(ContextInfo, contract_list, underlying_price):
"""
获取平值合约
"""
min_diff = 99999.0
target_contract = None
for code in contract_list:
detail = ContextInfo.get_instrumentdetail(code)
strike_price = detail['OptExercisePrice']
diff = abs(strike_price - underlying_price)
if diff < min_diff:
min_diff = diff
target_contract = code
return target_contract
def days_between(d1, d2):
"""
计算两个日期字符串(YYYYMMDD)之间的天数差
"""
try:
date1 = datetime.datetime.strptime(d1, "%Y%m%d")
date2 = datetime.datetime.strptime(d2, "%Y%m%d")
return (date2 - date1).days
except:
return 0
def close_positions(ContextInfo):
"""
平掉所有记录的持仓
"""
if ContextInfo.holding_call:
# 卖出平仓 Call
passorder(51, 1101, ContextInfo.account_id, ContextInfo.holding_call, 5, -1, ContextInfo.trade_unit, ContextInfo)
ContextInfo.holding_call = None
if ContextInfo.holding_put:
# 卖出平仓 Put
passorder(51, 1101, ContextInfo.account_id, ContextInfo.holding_put, 5, -1, ContextInfo.trade_unit, ContextInfo)
ContextInfo.holding_put = None
代码关键点解析
-
获取期权链 (
get_option_list):- 我们使用
ContextInfo.get_option_list配合当前月份,动态获取当月的所有认购和认沽合约。 - 参数
True确保只获取当前可交易的合约(过滤掉未上市或已退市的)。
- 我们使用
-
计算平值合约 (
get_atm_contract):- 通过遍历合约列表,利用
get_instrumentdetail获取行权价 (OptExercisePrice)。 - 计算行权价与标的现价的差值绝对值,最小者即为平值合约 (ATM)。
- 通过遍历合约列表,利用
-
获取隐含波动率 (
get_option_iv):- 直接调用 QMT 内置接口。这是做波动率策略的核心数据源。
- 策略中取 Call 和 Put 的 IV 平均值作为市场情绪的参考。
-
交易指令 (
passorder):opType=50: 买入开仓。opType=51: 卖出平仓。prType=5: 最新价下单(回测常用,实盘建议用对手价14或限价)。
-
到期日管理:
- 期权策略必须严格管理时间价值衰减(Theta)。代码中设置了
days_to_expire_limit,在临近到期前强制平仓,避免进入交割月或承受过大的 Gamma 风险。
- 期权策略必须严格管理时间价值衰减(Theta)。代码中设置了
注意事项
- 数据权限:获取期权数据(特别是 IV 和 Greeks)通常需要开通 Level-2 或特定的期权数据权限,请确认您的 QMT 账号具备此权限。
- 实盘调整:
- 实盘中下单价格类型建议修改为对手价或挂单价,避免滑点。
- 实盘中需要通过
get_trade_detail_data实时查询真实持仓,而不是仅依赖ContextInfo.holding_call变量,以防止程序重启后状态丢失。
- Delta 对冲:上述策略是纯粹的 IV 投机。更专业的波动率套利需要在持有期间不断调整标的或期权仓位,使组合的 Delta 保持为 0(Delta Neutral),从而剥离方向性风险,纯粹赚取 Vega(波动率)收益。
Q&A
Q1: 为什么 get_option_iv 返回的值有时候是 0 或负数?
A1: 这通常发生在期权流动性极差、深度实值/虚值,或者刚刚开盘数据尚未稳定时。在策略中必须对 IV 进行有效性过滤(如 if iv > 0.01),避免基于错误数据下单。
Q2: 如何实现 Delta 中性对冲(Delta Neutral)?
A2: 您可以使用 ContextInfo.get_option_detail_data(code) 获取期权的 Greeks(如 Delta),或者使用 BSM 公式自行计算。当组合的 Delta 偏离 0 超过一定阈值时,通过买卖标的资产(如 50ETF)来进行对冲。
Q3: 回测时如何设置期权的手续费?
A3: 在 init 函数中使用 ContextInfo.set_commission(...)。期权通常按张收费(例如每张 2 元),不同于股票的按金额比例收费。设置时需注意 commissionType 参数的选择。