3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在 QMT 平台中,获取期权隐含波动率(Implied Volatility, IV)主要有两种方式:直接获取系统计算好的实时 IV,或者利用 BSM 模型根据市场价格自行反推。
波动率套利策略通常涉及 Delta 中性策略(如跨式组合 Straddle 或 宽跨式组合 Strangle)。
以下将详细介绍如何获取数据,并提供一个完整的 做多波动率(Long Straddle) 策略示例。该策略在隐含波动率处于历史低位时买入跨式组合(同时买入平值认购和认沽),在波动率回归高位或获利时平仓。
使用 ContextInfo.get_option_iv(optioncode) 接口。这是最简单的方法,直接返回交易所或柜台计算的实时数据。
iv = ContextInfo.get_option_iv('10003280.SHO')
print(f"实时隐含波动率: {iv}")
使用 ContextInfo.bsm_iv(...) 接口。这适用于回测或需要自定义无风险利率、剩余天数等参数的场景。
# 参数:期权类型, 标的价格, 行权价, 期权现价, 无风险利率, 剩余天数, 分红率
iv = ContextInfo.bsm_iv('C', 3.51, 3.5, 0.0725, 0.03, 15, 0)
策略逻辑:
# -*- 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)。获取隐含波动率 (get_option_iv):
交易指令 (passorder):
opType=50: 买入开仓。opType=51: 卖出平仓。prType=5: 最新价下单(回测常用,实盘建议用对手价 14 或限价)。到期日管理:
days_to_expire_limit,在临近到期前强制平仓,避免进入交割月或承受过大的 Gamma 风险。get_trade_detail_data 实时查询真实持仓,而不是仅依赖 ContextInfo.holding_call 变量,以防止程序重启后状态丢失。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 参数的选择。