问题描述
解决方案
这是一个非常实用的量化分析需求。在 QMT 中,我们可以通过以下步骤实现:
- 获取隐含波动率 (IV):直接调用 QMT 提供的
ContextInfo.get_option_iv()接口获取期权的实时隐含波动率。 - 获取标的资产:通过
ContextInfo.get_option_detail_data()查询期权对应的标的资产代码(如 ETF 或 股票)。 - 获取历史行情:使用
ContextInfo.get_market_data_ex()获取标的资产的历史收盘价。 - 计算历史波动率 (HV):计算标的资产的对数收益率的标准差,并进行年化处理(通常乘以 $\sqrt{252}$)。
策略代码实现
以下是完整的 Python 策略代码。你可以将其复制到 QMT 的策略编辑器中运行。
# -*- coding: gbk -*-
import numpy as np
import pandas as pd
import math
def init(ContextInfo):
# 在这里设置你想分析的期权代码
# 例如:'10004366.SHO' (假设这是某个上证50ETF购X月X合约,请根据实际情况修改)
# 注意:请确保该期权合约在当前回测或实盘时间段内是上市交易的
ContextInfo.option_code = '10004366.SHO'
# 设置计算历史波动率的周期列表 (天)
ContextInfo.periods = [30, 60, 90]
# 设置运行周期,建议在日线级别运行
ContextInfo.period = '1d'
def handlebar(ContextInfo):
# 只在最后一根K线(最新时刻)进行计算和输出,避免历史回测时重复打印
if not ContextInfo.is_last_bar():
return
option_code = ContextInfo.option_code
# 1. 获取期权的隐含波动率 (IV)
# get_option_iv 返回的是小数,例如 0.20 代表 20%
iv = ContextInfo.get_option_iv(option_code)
# 2. 获取期权详细信息,找到标的资产代码
detail = ContextInfo.get_option_detail_data(option_code)
if not detail:
print(f"错误:无法获取期权 {option_code} 的详细信息")
return
# 拼接标的代码和市场,例如 '510050' 和 'SH' -> '510050.SH'
underlying_code = detail['OptUndlCode'] + '.' + detail['OptUndlMarket']
print("=" * 50)
print(f"期权代码: {option_code}")
print(f"标的代码: {underlying_code}")
print(f"当前隐含波动率 (IV): {iv:.2%}")
print("-" * 50)
# 3. 获取标的资产的历史行情数据
# 我们需要获取足够的数据来计算最长周期(90天)的波动率
# 多取一些数据以防停牌等情况,这里取 120 个数据点
count = max(ContextInfo.periods) + 20
# 获取收盘价
market_data = ContextInfo.get_market_data_ex(
fields=['close'],
stock_code=[underlying_code],
period='1d',
count=count,
dividend_type='front' # 使用前复权数据计算收益率更准确
)
if underlying_code not in market_data or market_data[underlying_code].empty:
print(f"错误:无法获取标的 {underlying_code} 的行情数据")
return
close_prices = market_data[underlying_code]['close']
# 4. 计算历史波动率 (HV) 并比较
# 计算对数收益率: ln(Pt / Pt-1)
# np.log 是自然对数
log_returns = np.log(close_prices / close_prices.shift(1)).dropna()
print(f"{'周期(天)':<10} | {'历史波动率(HV)':<15} | {'IV - HV 差值':<15}")
print("-" * 50)
for days in ContextInfo.periods:
if len(log_returns) < days:
print(f"{days:<10} | 数据不足")
continue
# 取最近 N 天的收益率
recent_returns = log_returns.tail(days)
# 计算标准差 (std)
std_dev = np.std(recent_returns)
# 年化波动率 = 日波动率 * sqrt(252)
hv = std_dev * math.sqrt(252)
diff = iv - hv
print(f"{days:<12} | {hv:.2%} | {diff:.2%}")
print("=" * 50)
# 简单的分析逻辑
hv_30 = np.std(log_returns.tail(30)) * math.sqrt(252)
if iv > hv_30:
print(f"结论: 当前IV ({iv:.2%}) 高于过去30天HV ({hv_30:.2%}),期权可能相对昂贵。")
else:
print(f"结论: 当前IV ({iv:.2%}) 低于过去30天HV ({hv_30:.2%}),期权可能相对便宜。")
代码核心逻辑解析
-
ContextInfo.get_option_iv(option_code):- 这是 QMT 特有的接口,直接返回交易所或数据商计算好的隐含波动率。如果返回值为 0 或异常,可能是该合约流动性不足或数据缺失。
-
ContextInfo.get_option_detail_data(option_code):- 期权分析必须基于标的资产。这个函数返回一个字典,其中
OptUndlCode是标的代码(如510050),OptUndlMarket是市场代码(如SH)。我们需要将它们拼接成510050.SH格式供后续行情接口使用。
- 期权分析必须基于标的资产。这个函数返回一个字典,其中
-
ContextInfo.get_market_data_ex(...):- 这是获取历史数据的核心函数。
dividend_type='front':非常重要。计算波动率时,必须使用复权价格(通常是前复权),否则分红除权造成的股价缺口会被误判为剧烈波动,导致 HV 计算虚高。
-
波动率计算公式:
- 首先计算对数收益率:$R_t = \ln(\frac{P_t}{P_{t-1}})$。
- 计算收益率的标准差 $\sigma$。
- 年化处理:$HV = \sigma \times \sqrt{252}$。金融市场通常假设一年有 252 个交易日。
如何运行
- 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
- 新建一个 Python 策略。
- 将上述代码粘贴进去。
- 修改
init函数中的ContextInfo.option_code为你想要观察的实际期权合约代码(例如当前活跃的 ETF 期权合约)。 - 点击【运行】。
- 在下方的【日志】窗口查看输出结果。
常见问题 Q&A
Q: 为什么计算出来的 HV 和某些看盘软件上显示的不一样?
A: 历史波动率的计算有多种口径,差异通常来自:
- 收益率算法:使用的是对数收益率还是简单收益率(差异通常很小)。
- 样本数量:比如计算30天波动率,是取过去30个交易日,还是过去30个自然日对应的交易日。
- 年化系数:有的软件使用 $\sqrt{250}$ 而不是 $\sqrt{252}$。
- 复权方式:是否使用了前复权数据。
Q: 如果 get_option_iv 返回 0 怎么办?
A: 这通常发生在期权合约流动性极差、刚刚上市或即将到期时。如果需要更精确的控制,可以使用 ContextInfo.bsm_iv 函数,自己传入标的价格、行权价、无风险利率等参数反推 IV。
Q: 这个脚本可以用于回测吗?
A: 可以。代码中使用了 ContextInfo.is_last_bar() 判断。如果在回测模式下,去掉这个判断,它就会在每一天的 K 线上计算当时的 IV 和当时的过去 N 天 HV,从而形成一条历史对比曲线。