问题描述
解决方案
如何利用期权的波动率微笑现象进行套利
波动率微笑(Volatility Smile)是指具有相同到期日但行权价不同的期权,其隐含波动率(Implied Volatility, IV)并不相同,而是呈现出一种中间低、两边高的微笑形状(或偏斜形状,称为Volatility Skew)。
利用波动率微笑进行套利(或更准确地说是相对价值交易),核心思想是做空高波动率的合约,做多低波动率的合约,同时保持Delta中性或构建特定的价差组合,等待波动率回归或通过时间价值获利。
在 QMT 平台中,我们可以利用内置的 BSM 模型函数计算隐含波动率,并构建相应的策略。
1. 常见的波动率微笑套利策略
A. 垂直价差套利 (Vertical Spread Arbitrage)
这是最直接利用波动率偏斜(Skew)的策略。
- 原理:如果虚值(OTM)期权的隐含波动率显著高于平值(ATM)期权(即微笑曲线陡峭),可以卖出高 IV 的 OTM 期权,买入低 IV 的 ATM 期权。
- 操作:例如 牛市看涨价差 (Bull Call Spread) 或 熊市看跌价差 (Bear Put Spread)。
B. 日历价差套利 (Calendar Spread)
利用不同到期日的波动率结构(期限结构)。
- 原理:通常近期期权的波动率变化比远期更剧烈。如果近期 IV 远高于远期 IV,可以卖出近期、买入远期。
C. 蝶式套利 (Butterfly Spread)
利用微笑曲线的曲率。
- 原理:如果两端的 IV 极高,中间的 IV 较低,可以卖出两端(高IV),买入中间(低IV)。
2. QMT 策略实现思路
在 QMT 中实现该策略的步骤如下:
- 获取期权链:使用
get_option_list获取标的对应的所有期权合约。 - 获取数据:使用
get_market_data_ex获取标的价格和期权价格。 - 计算隐含波动率 (IV):使用
ContextInfo.bsm_iv计算每个合约的 IV。 - 发现交易机会:比较不同行权价的 IV,寻找价差过大的机会。
- 执行交易:使用
passorder构建价差组合。
3. QMT 代码示例:基于波动率观测的垂直价差策略
以下代码演示了如何在 QMT 中计算 510300 (沪深300 ETF) 期权的隐含波动率,并构建一个简单的牛市看涨价差 (Bull Call Spread)。该策略假设虚值期权 IV 过高,因此卖出虚值 Call,买入平值 Call。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import time
from datetime import datetime
def init(ContextInfo):
# 设置账号
ContextInfo.accid = '您的资金账号'
ContextInfo.set_account(ContextInfo.accid)
# 策略参数
ContextInfo.underlying = '510300.SH' # 标的:300ETF
ContextInfo.trade_unit = 1 # 交易手数
ContextInfo.rf = 0.025 # 无风险利率 (假设2.5%)
ContextInfo.dividend = 0.0 # 分红率
# 设定定时运行,例如每天14:30运行一次
ContextInfo.run_time("my_strategy_logic", "1d", "2023-01-01 14:30:00", "SH")
def my_strategy_logic(ContextInfo):
"""
核心策略逻辑:计算IV,构建牛市看涨价差
"""
# 1. 获取标的当前价格
last_tick = ContextInfo.get_full_tick([ContextInfo.underlying])
if not last_tick:
print("无法获取标的行情")
return
S = last_tick[ContextInfo.underlying]['lastPrice'] # 标的现价
current_date = datetime.now().strftime('%Y%m%d')
# 2. 获取当月到期的认购期权列表
# 获取期权链,这里简化处理,直接获取当前可交易的期权
# 实际生产中应根据到期日筛选"当月"合约
option_list = ContextInfo.get_option_list(ContextInfo.underlying, current_date, "CALL", True)
if not option_list:
print("未获取到期权合约列表")
return
# 3. 获取期权详细信息(行权价、到期日)和行情
iv_data = []
# 批量获取期权行情
opt_ticks = ContextInfo.get_full_tick(option_list)
for opt_code in option_list:
detail = ContextInfo.get_instrumentdetail(opt_code)
tick = opt_ticks.get(opt_code)
if not detail or not tick:
continue
K = detail['OptExercisePrice'] # 行权价
price = tick['lastPrice'] # 期权现价
expire_date = detail['ExpireDate'] # 到期日 (int, e.g., 20231227)
# 计算剩余天数
days = days_between(current_date, str(expire_date))
if days <= 0 or price <= 0.0001:
continue
# 4. 计算隐含波动率 (IV)
# bsm_iv(optionType, objectPrices, strikePrice, optionPrice, riskFree, days, dividend)
iv = ContextInfo.bsm_iv('C', S, K, price, ContextInfo.rf, days, ContextInfo.dividend)
if np.isnan(iv) or iv == 0:
continue
iv_data.append({
'code': opt_code,
'strike': K,
'price': price,
'iv': iv,
'days': days
})
# 转换为DataFrame方便处理
df = pd.DataFrame(iv_data)
if df.empty:
return
# 按行权价排序
df = df.sort_values('strike')
# 打印波动率微笑曲线数据
print("="*30)
print(f"标的价格: {S}")
print(df[['code', 'strike', 'price', 'iv']])
print("="*30)
# 5. 策略逻辑:构建牛市看涨价差 (Bull Call Spread)
# 假设我们观察到深度虚值(OTM)的IV异常高,我们想卖出它赚取波动率溢价
# 同时买入平值(ATM)或轻度实值(ITM)期权作为保护和方向性赌注
# 找到平值合约 (行权价最接近标的价)
df['diff'] = abs(df['strike'] - S)
atm_opt = df.loc[df['diff'].idxmin()]
# 找到虚值合约 (行权价 > 标的价,且IV较高)
# 这里简单选取行权价高于标的价2档的合约作为卖出对象
otm_candidates = df[df['strike'] > S].sort_values('strike')
if len(otm_candidates) >= 2:
otm_opt = otm_candidates.iloc[1] # 选取第二档虚值
# 简单的交易信号:如果OTM的IV比ATM的IV高出一定阈值(例如2%),则开仓
# 注意:实际市场中Call通常呈现偏斜(低行权价IV高),这里仅为演示代码逻辑
iv_spread = otm_opt['iv'] - atm_opt['iv']
print(f"ATM IV: {atm_opt['iv']:.4f}, OTM IV: {otm_opt['iv']:.4f}, Spread: {iv_spread:.4f}")
# 执行交易 (示例:买入ATM Call,卖出OTM Call)
# 实际交易需检查持仓、资金等
print(f"交易信号: 买入 {atm_opt['code']} (K={atm_opt['strike']}), 卖出 {otm_opt['code']} (K={otm_opt['strike']})")
# 买入平值认购
passorder(23, 1101, ContextInfo.accid, atm_opt['code'], 5, -1, ContextInfo.trade_unit, ContextInfo)
# 卖出虚值认购
passorder(24, 1101, ContextInfo.accid, otm_opt['code'], 5, -1, ContextInfo.trade_unit, ContextInfo)
def days_between(d1, d2):
"""计算两个日期字符串(YYYYMMDD)之间的天数"""
d1 = datetime.strptime(d1, "%Y%m%d")
d2 = datetime.strptime(d2, "%Y%m%d")
return (d2 - d1).days
def handlebar(ContextInfo):
# 实时行情驱动,此处留空,逻辑在 run_time 中定时触发
pass
4. 代码关键点解析
-
ContextInfo.bsm_iv: 这是 QMT 提供的核心函数,用于反推隐含波动率。- 参数:
optionType('C'/'P'),objectPrices(标的价),strikePrice(行权价),optionPrice(期权价),riskFree(无风险利率),days(剩余天数),dividend(分红率)。 - 注意:
days参数通常需要是剩余天数(整数或浮点数),QMT 内部会将其转换为年化时间(days/365)。
- 参数:
-
数据获取:
get_option_list: 能够根据标的和月份快速筛选合约。get_full_tick: 获取最新的盘口价格,用于计算实时的 IV。
-
策略逻辑:
- 代码首先计算了所有 Call 的 IV 并打印,这让你能直观地在日志中看到“微笑”或“偏斜”的形状。
- 构建了 Bull Call Spread:买入低行权价(ATM),卖出高行权价(OTM)。如果 OTM 的 IV 显著高于 ATM(即市场过度定价了尾部风险),卖出 OTM 可以获得理论上的波动率溢价。
5. 风险提示
- 模型风险:BSM 模型假设波动率是常数,而我们正是利用波动率不为常数(微笑)来套利,这本身存在理论悖论。实际操作中 IV 只是一个报价单位。
- 流动性风险:深度虚值期权流动性可能很差,买卖价差(Bid-Ask Spread)可能很大,这会吞噬掉套利利润。建议在代码中加入对
bidPrice和askPrice的检查。 - 保证金风险:作为期权卖方(卖出 OTM Call),需要缴纳保证金。垂直价差策略通常可以减少保证金占用,但需确保账户资金充足。
- Delta 风险:上述策略并非 Delta 中性。如果标的价格大幅下跌,Bull Call Spread 会亏损。纯粹的波动率套利通常需要用期货进行 Delta 对冲。
Q&A
Q: 如何在 QMT 中进行 Delta 对冲以实现纯波动率套利?
A: 您可以使用 ContextInfo.bsm_price 或相关的希腊字母计算公式计算持仓期权的 Delta 值,然后计算整个组合的 Net Delta。如果 Net Delta 不为 0,则通过买卖标的(如 300ETF 或 股指期货 IF)来将 Delta 归零。这需要在 handlebar 中高频监控并动态调整。
Q: 为什么计算出的 IV 有时是 NaN?
A: 如果期权价格偏离理论价格太远(例如深度实值期权价格低于内涵价值,或者价格为0),BSM 公式可能无法收敛求出解。代码中应加入 np.isnan 判断进行过滤。
Q: get_market_data_ex 和 get_full_tick 哪个更适合期权?
A: 对于实时监控和 IV 计算,推荐使用 get_full_tick,因为它提供最新的买一卖一价和最新成交价。get_market_data_ex 更适合获取历史序列数据进行回测分析。