问题描述
解决方案
这是一个关于量化交易策略实现的问题。
策略逻辑分析
日历价差(Calendar Spread) 是一种利用不同到期月份期权的时间价值衰减差异来获利的策略。
具体到你的需求:卖出近月平值看涨期权 + 买入远月平值看涨期权。
- 标的:假设为 50ETF(510050.SH)或 300ETF(510300.SH)。
- 腿1(空头):卖出开仓(Sell Open),下个月到期(近月),行权价最接近当前标的价格(平值),类型为看涨(Call)。
- 腿2(多头):买入开仓(Buy Open),下下个月到期(远月),行权价最接近当前标的价格(平值),类型为看涨(Call)。
- 核心难点:如何在代码中自动识别“下个月”和“下下个月”的到期日,并筛选出平值合约。
QMT 代码实现
以下是完整的 Python 策略代码。代码包含了合约筛选逻辑、平值识别逻辑以及下单交易逻辑。
# -*- coding: gbk -*-
import pandas as pd
import time
import datetime
def init(ContextInfo):
# 1. 设置账户 (请替换为真实的资金账号)
# 格式:'账号ID'
ContextInfo.accID = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.accID)
# 2. 设置标的物,这里以 50ETF 为例
ContextInfo.underlying = '510050.SH'
# 3. 设置交易数量 (张)
ContextInfo.trade_vol = 1
# 4. 策略运行控制变量,防止重复下单
ContextInfo.has_traded = False
print("策略初始化完成,标的:{}".format(ContextInfo.underlying))
def handlebar(ContextInfo):
# 仅在最后一根K线(实时行情)运行,或者是回测模式
if not ContextInfo.is_last_bar():
return
# 每日只交易一次的简单控制逻辑
if ContextInfo.has_traded:
return
# 1. 获取标的当前价格
# 获取最新的一条tick数据
tick_data = ContextInfo.get_full_tick([ContextInfo.underlying])
if not tick_data:
print("未获取到标的行情")
return
current_price = tick_data[ContextInfo.underlying]['lastPrice']
print("当前标的价格: {}".format(current_price))
# 2. 获取所有看涨期权合约列表
# get_option_list(标的代码, 到期月, 类型),到期月为空表示取所有,类型为空表示取所有
# 这里我们先取所有,然后自己筛选日期
option_list = ContextInfo.get_option_list(ContextInfo.underlying, '', 'CALL', True)
if not option_list:
print("未获取到期权合约列表")
return
# 3. 筛选到期日
# 我们需要找到“最近的两个到期日”来代表“下个月”和“下下个月”
# 注意:实际交易中“下个月”通常指代最近的两个主力合约月份
# 获取当前日期 YYYYMMDD
current_date_str = datetime.datetime.now().strftime('%Y%m%d')
# 存储结构:{到期日: [合约代码列表]}
expiry_map = {}
for opt_code in option_list:
# 获取合约详细信息
detail = ContextInfo.get_instrumentdetail(opt_code)
expire_date = str(detail['ExpireDate'])
# 过滤掉已经过期的合约
if expire_date > current_date_str:
if expire_date not in expiry_map:
expiry_map[expire_date] = []
expiry_map[expire_date].append(opt_code)
# 对到期日进行排序
sorted_expiry_dates = sorted(expiry_map.keys())
if len(sorted_expiry_dates) < 2:
print("可交易的期权到期月份不足两个,无法构建日历价差")
return
# 确定近月和远月到期日
near_term_date = sorted_expiry_dates[0] # 近月(下个月)
far_term_date = sorted_expiry_dates[1] # 远月(下下个月)
print("近月到期日: {}, 远月到期日: {}".format(near_term_date, far_term_date))
# 4. 筛选平值合约 (ATM)
# 分别在近月和远月合约列表中,寻找行权价最接近 current_price 的合约
near_atm_contract = get_atm_contract(ContextInfo, expiry_map[near_term_date], current_price)
far_atm_contract = get_atm_contract(ContextInfo, expiry_map[far_term_date], current_price)
if not near_atm_contract or not far_atm_contract:
print("未找到合适的平值合约")
return
print("选定近月合约(卖出): {}, 行权价: {}".format(near_atm_contract['code'], near_atm_contract['strike']))
print("选定远月合约(买入): {}, 行权价: {}".format(far_atm_contract['code'], far_atm_contract['strike']))
# 5. 执行交易
# 腿1:卖出近月平值看涨期权 (卖出开仓)
# opType: 52 (卖出开仓), orderType: 1101 (限价单), prType: 14 (对手价)
passorder(52, 1101, ContextInfo.accID, near_atm_contract['code'], 14, -1, ContextInfo.trade_vol, ContextInfo)
print("交易发出:卖出开仓 {}".format(near_atm_contract['code']))
# 腿2:买入远月平值看涨期权 (买入开仓)
# opType: 50 (买入开仓)
passorder(50, 1101, ContextInfo.accID, far_atm_contract['code'], 14, -1, ContextInfo.trade_vol, ContextInfo)
print("交易发出:买入开仓 {}".format(far_atm_contract['code']))
# 标记已交易
ContextInfo.has_traded = True
def get_atm_contract(ContextInfo, contract_list, current_price):
"""
辅助函数:从合约列表中寻找行权价最接近标的价格的合约
"""
best_contract = None
min_diff = 999999.0
for code in contract_list:
detail = ContextInfo.get_instrumentdetail(code)
strike_price = detail['OptExercisePrice']
diff = abs(strike_price - current_price)
if diff < min_diff:
min_diff = diff
best_contract = {
'code': code,
'strike': strike_price
}
return best_contract
代码关键点解析
-
合约筛选逻辑 (
get_option_list&get_instrumentdetail):- 首先通过
get_option_list获取标的下所有的 CALL(认购)合约。 - 然后遍历合约,通过
get_instrumentdetail获取ExpireDate(到期日)。 - 将合约按到期日分组,并按时间排序。
- 取
sorted_expiry_dates[0]作为近月(通常是当月或下月,取决于当前是否临近行权日),取sorted_expiry_dates[1]作为远月。
- 首先通过
-
平值识别 (
get_atm_contract):- 平值(ATM)定义为行权价(Strike Price)与标的现价(Underlying Price)差值绝对值最小的合约。
- 代码中遍历特定月份的所有合约,计算
abs(strike - current_price),取最小值对应的合约。
-
交易下单 (
passorder):- 卖出近月:使用
opType=52(卖出开仓)。这是日历价差的获利腿,利用近月时间价值衰减快。 - 买入远月:使用
opType=50(买入开仓)。这是保护腿,利用远月时间价值衰减慢。 - 价格类型:示例中使用了
prType=14(对手价) 以提高成交概率,实盘中也可以考虑使用5(最新价) 或11(指定价)。
- 卖出近月:使用
-
注意事项:
- 账号设置:请务必在
init函数中将'YOUR_ACCOUNT_ID'替换为您实际的 QMT 资金账号。 - 风控:期权卖方风险较大(虽然日历价差是风险有限策略,但仍需保证金),请确保账户有足够资金。
- 运行频率:示例代码加了
ContextInfo.has_traded锁,确保策略启动后只运行一次。如果是实盘持续运行,需要根据您的逻辑修改触发条件(例如:每天定时触发、价差达到特定阈值触发等)。
- 账号设置:请务必在
Q&A
Q: 如何修改代码以交易看跌期权(Put)的日历价差?
A: 在 handlebar 函数中,将 ContextInfo.get_option_list 的第三个参数从 'CALL' 改为 'PUT' 即可。
Q: 为什么使用 get_instrumentdetail 而不是直接解析合约代码?
A: 虽然合约代码(如 1000xxxx.SHO)有时包含信息,但直接调用 API 获取 ExpireDate 和 OptExercisePrice(行权价)是最准确且兼容性最好的方法,不受交易所编码规则变更的影响。
Q: 如何在回测中查看成交情况?
A: 可以在 QMT 界面点击“回测”,系统会自动撮合 passorder 发出的委托。您也可以在代码中添加 get_trade_detail_data 函数来实时查询持仓和成交状态。