问题描述
解决方案
编写牛市价差(Bull Call Spread)期权策略需要同时操作两个方向的期权合约:买入一个较低行权价的认购期权(Call),同时卖出一个较高行权价的认购期权(Call)。两者标的资产相同,到期日相同。
这种策略通常用于预期标的资产价格会温和上涨,通过卖出高行权价期权来降低权利金成本。
以下是基于 QMT Python API 实现的策略代码。
策略逻辑说明
- 标的选择:以 50ETF(510050.SH)为例。
- 合约选择:
- 获取当月或次月到期的认购期权列表。
- 买入腿(Long Leg):选择平值(ATM)或轻微实值期权。
- 卖出腿(Short Leg):选择虚值(OTM)期权(行权价高于买入腿)。
- 交易信号:
- 当标的资产价格上涨趋势确立(例如价格高于20日均线)且当前无持仓时,构建组合。
- 当临近到期日或标的价格跌破止损线时,平仓。
- 风控:设置简单的到期平仓逻辑。
QMT 策略代码
# -*- coding: gbk -*-
import pandas as pd
import time
import datetime
def init(ContextInfo):
# 1. 设置账户信息 (请替换为您的真实资金账号)
ContextInfo.accID = '6000000000'
ContextInfo.set_account(ContextInfo.accID)
# 2. 策略参数设置
ContextInfo.underlying = '510050.SH' # 标的资产:50ETF
ContextInfo.trade_period = '1d' # 运行周期:日线
ContextInfo.holding = False # 持仓状态标记
ContextInfo.long_leg = '' # 买入持仓合约代码
ContextInfo.short_leg = '' # 卖出持仓合约代码
# 3. 资金分配
ContextInfo.lots = 1 # 每次交易手数
print("牛市价差策略初始化完成")
def handlebar(ContextInfo):
# 获取当前K线的时间
bar_index = ContextInfo.barpos
current_time = ContextInfo.get_bar_timetag(bar_index)
current_date_str = timetag_to_datetime(current_time, '%Y%m%d')
# 1. 获取标的资产行情 (50ETF)
market_data = ContextInfo.get_market_data_ex(
['close'],
[ContextInfo.underlying],
period=ContextInfo.trade_period,
count=21, # 取过去21根K线计算均线
subscribe=True
)
if ContextInfo.underlying not in market_data:
return
close_prices = market_data[ContextInfo.underlying]['close']
current_price = close_prices.iloc[-1]
# 计算20日均线作为趋势判断
if len(close_prices) < 20:
return
ma20 = close_prices.iloc[-20:].mean()
# 2. 交易逻辑
# --- 平仓逻辑 (如果持有持仓) ---
if ContextInfo.holding:
# 检查是否临近到期 (这里简化逻辑:如果是合约到期月份的最后几天则平仓)
# 获取持仓合约的详细信息
detail = ContextInfo.get_instrumentdetail(ContextInfo.long_leg)
expire_date = str(detail['ExpireDate']) # 格式如 20231227
# 如果当前日期 >= 到期日前2天,或者 价格跌破均线,则平仓
is_near_expire = int(current_date_str) >= int(expire_date) - 2
is_trend_broken = current_price < ma20
if is_near_expire or is_trend_broken:
print(f"触发平仓: 临近到期={is_near_expire}, 趋势破坏={is_trend_broken}")
# 平掉买入腿 (卖出平仓)
buy_close_tdayfirst(ContextInfo.long_leg, ContextInfo.lots, 'LATEST', 0, ContextInfo, ContextInfo.accID)
# 平掉卖出腿 (买入平仓)
sell_close_tdayfirst(ContextInfo.short_leg, ContextInfo.lots, 'LATEST', 0, ContextInfo, ContextInfo.accID)
ContextInfo.holding = False
ContextInfo.long_leg = ''
ContextInfo.short_leg = ''
# --- 开仓逻辑 (如果没有持仓) ---
else:
# 如果价格站上20日均线,看多,构建牛市价差
if current_price > ma20:
print(f"趋势看多 (现价 {current_price:.3f} > MA20 {ma20:.3f}),构建牛市价差组合")
# A. 确定期权到期月份 (选择当月或次月)
# 获取当前年月,例如 202312
target_month = current_date_str[:6]
# B. 获取期权链
# 获取指定标的、指定月份的认购期权(CALL)列表
# 注意:get_option_list 第二个参数如果是YYYYMM格式,返回该月到期的合约
option_list = ContextInfo.get_option_list(ContextInfo.underlying, target_month, "CALL")
if not option_list:
print(f"{target_month} 无可用期权合约")
return
# C. 筛选行权价
# 我们需要找到行权价及对应的合约代码
strikes = {} # {行权价: 合约代码}
for opt_code in option_list:
detail = ContextInfo.get_instrumentdetail(opt_code)
strike_price = detail['OptExercisePrice']
strikes[strike_price] = opt_code
# 对行权价进行排序
sorted_strikes = sorted(strikes.keys())
# D. 寻找平值合约 (ATM) 和 虚值合约 (OTM)
# 找到离当前标的价格最近的行权价作为 ATM
atm_strike = min(sorted_strikes, key=lambda x: abs(x - current_price))
atm_index = sorted_strikes.index(atm_strike)
# 确保有更高行权价的合约存在
if atm_index + 1 < len(sorted_strikes):
low_strike = atm_strike # 较低行权价 (买入)
high_strike = sorted_strikes[atm_index + 1] # 较高行权价 (卖出),这里选择高一档
long_code = strikes[low_strike]
short_code = strikes[high_strike]
print(f"构建组合: 买入 {long_code}(行权价{low_strike}),卖出 {short_code}(行权价{high_strike})")
# E. 下单执行
# 1. 买入低行权价认购期权 (买入开仓)
buy_open(long_code, ContextInfo.lots, 'LATEST', 0, ContextInfo, ContextInfo.accID)
# 2. 卖出高行权价认购期权 (卖出开仓)
sell_open(short_code, ContextInfo.lots, 'LATEST', 0, ContextInfo, ContextInfo.accID)
# 更新状态
ContextInfo.holding = True
ContextInfo.long_leg = long_code
ContextInfo.short_leg = short_code
else:
print("无法找到合适的虚值合约构建价差")
# 辅助函数:时间戳转字符串
def timetag_to_datetime(timetag, format_str):
import time
return time.strftime(format_str, time.localtime(timetag / 1000))
代码关键点解析
-
期权合约获取 (
get_option_list):- 使用
ContextInfo.get_option_list(标的代码, 月份, 类型)获取期权列表。 target_month需要根据当前回测或实盘的时间动态生成(格式为YYYYMM)。
- 使用
-
行权价筛选:
- 通过
ContextInfo.get_instrumentdetail(合约代码)获取合约详情,其中OptExercisePrice是行权价。 - 策略核心在于选取两个行权价:
- $K_L$ (Low Strike): 通常选平值(ATM)附近,代码中通过计算与标的现价差值绝对值最小来定位。
- $K_H$ (High Strike): 选比 $K_L$ 高一档或多档的行权价。
- 通过
-
交易指令:
- 买入腿: 使用
buy_open(买入开仓)。 - 卖出腿: 使用
sell_open(卖出开仓)。 - 平仓: 对应使用
buy_close_tdayfirst(买入平仓,优先平今) 和sell_close_tdayfirst(卖出平仓,优先平今)。
- 买入腿: 使用
-
到期日管理:
- 期权策略必须关注到期日。代码中通过
get_instrumentdetail获取ExpireDate,并在临近到期前强制平仓,避免进入行权交割流程(大部分投机策略不希望进行实物交割)。
- 期权策略必须关注到期日。代码中通过
注意事项
- 保证金: 卖出期权(Short Call)需要占用保证金,牛市价差策略虽然风险有限,但作为组合策略,如果账户不支持组合保证金优惠,可能会占用两笔资金(买入权利金 + 卖出保证金)。
- 滑点与流动性: 期权流动性不如标的资产,建议在实盘中使用限价单(
FIX)而非市价单,或者使用算法交易接口。 - 回测数据: 运行此策略需要下载完整的期权历史数据和标的(50ETF)历史数据。
Q&A
Q: 如何修改策略为熊市价差(Bear Put Spread)?
A: 逻辑类似,但方向相反:
- 将
get_option_list的类型从"CALL"改为"PUT"。 - 买入一个较高行权价的认沽期权(Put)。
- 卖出一个较低行权价的认沽期权(Put)。
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 推荐的新版接口,返回的数据结构更统一(DataFrame),且支持订阅模式,处理效率更高。
Q: 实盘中如何保证两腿同时成交?
A: 在普通账户中很难保证完全同时。通常建议先成交买入腿(权利方),再成交卖出腿(义务方),以防止裸卖空的风险暴露。如果拥有组合交易权限,可以使用 passorder 的组合下单类型(如 opType=40 期货组合开多等,具体需参考券商提供的组合策略类型ID)。