问题描述
解决方案
这是一个基于 QMT 平台的 买入跨式期权(Long Straddle) 策略示例。
策略逻辑说明
- 核心思想:在预期市场会有大幅波动(大涨或大跌),但不确定方向时使用。
- 操作方式:同时买入相同数量、相同行权价、相同到期日的 认购期权(Call) 和 认沽期权(Put)。通常选择 平值(ATM) 期权。
- 获利条件:标的资产价格大幅偏离行权价(无论上涨还是下跌),且幅度超过两份权利金之和。
- 代码实现:
- 获取标的(如 50ETF)的实时价格。
- 筛选出当月到期的期权合约。
- 找到行权价最接近当前标的价格的合约(平值合约)。
- 同时买入 Call 和 Put。
QMT 策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import time
def init(ContextInfo):
"""
策略初始化函数
"""
# ================= 策略参数设置 =================
# 标的资产:这里以 50ETF (510050.SH) 为例
ContextInfo.underlying = '510050.SH'
# 资金账号 (请务必修改为您自己的实盘或模拟账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
# 交易数量 (手)
ContextInfo.trade_volume = 1
# 策略运行状态标记,防止重复开仓
ContextInfo.has_opened = False
# 设置账号
ContextInfo.set_account(ContextInfo.account_id)
print("策略初始化完成,标的:{}, 账号:{}".format(ContextInfo.underlying, ContextInfo.account_id))
def get_atm_options(ContextInfo, underlying_price):
"""
获取平值(ATM)期权合约对 (Call, Put)
逻辑:
1. 获取当前所有可交易期权
2. 筛选出最近到期月份的合约
3. 找到行权价与当前标的价格差值最小的合约
"""
# 获取当前日期,格式 YYYYMMDD
current_date = time.strftime('%Y%m%d', time.localtime(time.time()))
# 获取标的对应的所有可交易期权合约列表
# isavailable=True 表示获取当前可交易的
option_list = ContextInfo.get_option_list(ContextInfo.underlying, current_date, "", True)
if not option_list:
print("未获取到期权合约列表")
return None, None
# 存储期权信息的列表
options_data = []
for opt_code in option_list:
# 获取合约详细信息
detail = ContextInfo.get_instrumentdetail(opt_code)
# 过滤掉非期权品种或数据异常的
if not detail or 'ExpireDate' not in detail or 'OptExercisePrice' not in detail:
continue
# 简单筛选:只看最近到期的(这里简化逻辑,取列表中的第一个到期日作为目标到期日)
# 实际生产中可能需要更复杂的逻辑来锁定特定月份(如当月或次月)
options_data.append({
'code': opt_code,
'expire_date': detail['ExpireDate'],
'strike_price': detail['OptExercisePrice'],
'type': detail['optType'] # 'CALL' or 'PUT'
})
if not options_data:
return None, None
# 转为 DataFrame 方便处理
df = pd.DataFrame(options_data)
# 1. 筛选最近的到期日 (最小的 ExpireDate)
min_expire = df['expire_date'].min()
df_near = df[df['expire_date'] == min_expire]
# 2. 计算行权价与标的价格的差值的绝对值
df_near['diff'] = abs(df_near['strike_price'] - underlying_price)
# 3. 找到差值最小的行权价 (即平值 ATM)
min_diff = df_near['diff'].min()
target_strike = df_near[df_near['diff'] == min_diff]['strike_price'].iloc[0]
# 4. 获取该行权价下的 Call 和 Put
atm_call = df_near[(df_near['strike_price'] == target_strike) & (df_near['type'] == 'CALL')]['code'].values
atm_put = df_near[(df_near['strike_price'] == target_strike) & (df_near['type'] == 'PUT')]['code'].values
call_code = atm_call[0] if len(atm_call) > 0 else None
put_code = atm_put[0] if len(atm_put) > 0 else None
return call_code, put_code
def handlebar(ContextInfo):
"""
K线周期运行函数
"""
# 必须是实盘或回测的最后一根K线才执行交易逻辑
if not ContextInfo.is_last_bar():
return
# 如果已经开仓,则不再开仓 (示例策略仅演示开仓逻辑)
if ContextInfo.has_opened:
return
# 1. 获取标的最新价格
# get_market_data_ex 返回的是 dict {code: dataframe}
market_data = ContextInfo.get_market_data_ex(
['close'],
[ContextInfo.underlying],
period='1d',
count=1,
subscribe=True
)
if ContextInfo.underlying not in market_data:
print("未获取到标的行情")
return
current_price = market_data[ContextInfo.underlying].iloc[-1]['close']
print("当前标的 {} 价格: {}".format(ContextInfo.underlying, current_price))
# 2. 获取平值期权合约
call_code, put_code = get_atm_options(ContextInfo, current_price)
if not call_code or not put_code:
print("未找到合适的平值期权合约")
return
print("选定平值期权对 -> Call: {}, Put: {}".format(call_code, put_code))
# 3. 下单交易 (买入跨式:买 Call + 买 Put)
# 注意:期权买方通常使用 buy_open (买入开仓)
# 买入认购
print("正在买入认购期权: {}".format(call_code))
buy_open(call_code, ContextInfo.trade_volume, "LATEST", 0, ContextInfo, ContextInfo.account_id)
# 买入认沽
print("正在买入认沽期权: {}".format(put_code))
buy_open(put_code, ContextInfo.trade_volume, "LATEST", 0, ContextInfo, ContextInfo.account_id)
# 标记已开仓
ContextInfo.has_opened = True
print("跨式策略开仓指令已发送")
代码关键点解析
-
get_atm_options函数:- 这是策略的核心。它利用
ContextInfo.get_option_list获取标的关联的所有期权。 - 利用
ContextInfo.get_instrumentdetail获取期权的详细信息(行权价、到期日、类型)。 - 通过
abs(strike_price - underlying_price)最小化算法找到最接近当前价格的行权价(即平值期权)。 - 同时筛选出最近到期月份的合约,保证流动性。
- 这是策略的核心。它利用
-
buy_open交易函数:buy_open(stockcode, amount, style, price, ContextInfo, accId)- 这里使用了
"LATEST"(最新价) 进行下单,实盘中为了保证成交,有时会使用对手价"COMPETE"或指定价格。
-
防重复开仓:
- 使用了
ContextInfo.has_opened变量。在实际策略中,您应该通过get_trade_detail_data查询实际持仓来判断是否需要开仓,而不是仅靠一个变量。
- 使用了
使用前注意事项
- 账号设置:请务必将代码中的
'YOUR_ACCOUNT_ID'替换为您实际的 QMT 资金账号(期权账号)。 - 品种权限:确保您的账号开通了期权交易权限,并且在 QMT 客户端中已经登录了该账号。
- 数据下载:在回测模式下,请确保下载了
510050.SH及其对应期权合约的历史数据。 - 风险提示:
- 跨式策略虽然理论上风险有限(最多损失权利金),但如果市场横盘震荡,时间价值(Theta)的损耗会非常快。
- 建议在实盘前先在模拟环境中充分测试。
常见问题 Q&A
Q: 如何修改为卖出跨式(做空波动率)?
A: 将 buy_open 改为 sell_open 即可。但请注意,卖出跨式风险无限(理论上),需要缴纳保证金,风险极高。
Q: 如何指定交易特定月份的期权?
A: 在 get_atm_options 函数中,df_near = df[df['expire_date'] == min_expire] 这一行选取了最近的到期日。你可以修改逻辑,比如判断 expire_date 是否包含特定的月份字符串(如 '202312')。
Q: 为什么获取不到期权数据?
A: 请检查 QMT 客户端右下角的连接状态是否正常,以及是否在“数据管理”中补充了期权的基础数据。