问题描述
解决方案
这是一个基于 QMT 平台的期权铁鹰式(Iron Condor)策略实现。
策略原理
铁鹰式策略是一种中性策略,适用于预期标的资产价格在特定区间内震荡、波动率下降的市场环境。它由四条腿(Legs)组成,即同时构建一个**卖出宽跨式(Short Strangle)和一个买入宽跨式(Long Strangle)**作为保护。
具体构成(假设标的价格为 S):
- 卖出看涨 (Short Call): 行权价 $K3$ (虚值,高于 S)
- 买入看涨 (Long Call): 行权价 $K4$ (深虚值,高于 K3,用于保护)
- 卖出看跌 (Short Put): 行权价 $K2$ (虚值,低于 S)
- 买入看跌 (Long Put): 行权价 $K1$ (深虚值,低于 K2,用于保护)
行权价关系: $K1 < K2 < S < K3 < K4$
策略代码实现
本代码以 50ETF (510050.SH) 为标的,自动寻找当月合约,根据设定的档位(Gap)构建铁鹰组合。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import datetime
def init(ContextInfo):
# ================= 策略参数设置 =================
# 设定资金账号 (请修改为您的实际账号)
ContextInfo.accID = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.accID)
# 标的资产:50ETF
ContextInfo.underlying = '510050.SH'
# 交易数量(张)
ContextInfo.trade_vol = 1
# 档位设置 (基于ATM平值合约的偏移档位)
# 卖方腿距离平值的档位 (例如 1 代表虚值一档)
ContextInfo.short_gap = 1
# 买方保护腿距离卖方腿的宽度 (例如 2 代表在卖方腿基础上再往外2档)
ContextInfo.wing_width = 2
# 状态标记,防止重复开仓 (示例策略简单处理,实盘需更复杂的持仓管理)
ContextInfo.has_position = False
print("铁鹰策略初始化完成")
def handlebar(ContextInfo):
# 获取当前K线位置
index = ContextInfo.barpos
# 获取当前时间
realtime = ContextInfo.get_bar_timetag(index)
current_date_str = timetag_to_datetime(realtime, '%Y%m%d')
# 如果是回测模式最后几根K线,或者已经有持仓,则不进行开仓逻辑(简化演示)
if ContextInfo.is_last_bar() and not ContextInfo.has_position:
open_iron_condor(ContextInfo, current_date_str)
def open_iron_condor(ContextInfo, current_date_str):
"""
构建铁鹰组合的核心逻辑
"""
# 1. 获取标的最新价格
market_data = ContextInfo.get_market_data_ex(
['close'], [ContextInfo.underlying], period='1d', count=1, subscribe=False
)
if ContextInfo.underlying not in market_data:
print("未获取到标的行情")
return
current_price = market_data[ContextInfo.underlying].iloc[-1]['close']
print(f"当前标的价格: {current_price}")
# 2. 获取期权合约列表 (选取当月合约)
# 获取当前年月,例如 '202310'
# 注意:这里简化处理,直接取当前日期所在的月份。实盘中可能需要判断是否临近到期日而切换到下月
target_month = current_date_str[0:6]
# 获取认购和认沽合约列表
# isavailable=True 表示只获取当前可交易的
calls = ContextInfo.get_option_list(ContextInfo.underlying, target_month, "CALL", True)
puts = ContextInfo.get_option_list(ContextInfo.underlying, target_month, "PUT", True)
if not calls or not puts:
print(f"未找到 {target_month} 到期的期权合约")
return
# 3. 整理合约并按行权价排序
# 辅助函数:获取合约代码和行权价的列表
def get_sorted_options(opt_list):
opts_with_strike = []
for code in opt_list:
detail = ContextInfo.get_instrumentdetail(code)
strike = detail['OptExercisePrice']
opts_with_strike.append({'code': code, 'strike': strike})
# 按行权价从小到大排序
return sorted(opts_with_strike, key=lambda x: x['strike'])
sorted_calls = get_sorted_options(calls)
sorted_puts = get_sorted_options(puts)
# 4. 寻找平值合约 (ATM) 的索引位置
# 寻找行权价最接近当前标的价格的合约索引
atm_call_idx = min(range(len(sorted_calls)), key=lambda i: abs(sorted_calls[i]['strike'] - current_price))
atm_put_idx = min(range(len(sorted_puts)), key=lambda i: abs(sorted_puts[i]['strike'] - current_price))
# 5. 确定四个腿的合约
# 索引保护:确保计算后的索引在列表范围内
try:
# Call侧 (看涨):价格越高,行权价越高,索引越大
# 卖出虚值看涨 (Short Call)
sc_idx = atm_call_idx + ContextInfo.short_gap
# 买入深虚值看涨 (Long Call) - 保护腿
lc_idx = sc_idx + ContextInfo.wing_width
# Put侧 (看跌):价格越低,行权价越低,索引越小
# 卖出虚值看跌 (Short Put)
sp_idx = atm_put_idx - ContextInfo.short_gap
# 买入深虚值看跌 (Long Put) - 保护腿
lp_idx = sp_idx - ContextInfo.wing_width
# 检查索引是否越界
if lp_idx < 0 or lc_idx >= len(sorted_calls):
print("计算出的行权价超出合约列表范围,无法开仓")
return
contract_sc = sorted_calls[sc_idx]['code']
contract_lc = sorted_calls[lc_idx]['code']
contract_sp = sorted_puts[sp_idx]['code']
contract_lp = sorted_puts[lp_idx]['code']
print("="*30)
print(f"构建铁鹰组合 (标的价: {current_price:.4f})")
print(f"1. 买入看跌 (保护): {contract_lp} 行权价: {sorted_puts[lp_idx]['strike']}")
print(f"2. 卖出看跌 (收入): {contract_sp} 行权价: {sorted_puts[sp_idx]['strike']}")
print(f"3. 卖出看涨 (收入): {contract_sc} 行权价: {sorted_calls[sc_idx]['strike']}")
print(f"4. 买入看涨 (保护): {contract_lc} 行权价: {sorted_calls[lc_idx]['strike']}")
print("="*30)
# 6. 下单交易
# 注意:期权组合交易建议使用限价单或对手价,这里演示使用对手价 (PRTP_COMPETE = 14)
# 也可以使用 smart_algo_passorder 进行算法交易拆单
# 腿1: 买入深虚值 Put (Long Put)
buy_open(contract_lp, ContextInfo.trade_vol, "COMPETE", 0, ContextInfo, ContextInfo.accID)
# 腿2: 卖出虚值 Put (Short Put)
sell_open(contract_sp, ContextInfo.trade_vol, "COMPETE", 0, ContextInfo, ContextInfo.accID)
# 腿3: 卖出虚值 Call (Short Call)
sell_open(contract_sc, ContextInfo.trade_vol, "COMPETE", 0, ContextInfo, ContextInfo.accID)
# 腿4: 买入深虚值 Call (Long Call)
buy_open(contract_lc, ContextInfo.trade_vol, "COMPETE", 0, ContextInfo, ContextInfo.accID)
ContextInfo.has_position = True
print("交易指令已发送")
except Exception as e:
print(f"开仓逻辑出错: {e}")
# 辅助函数:时间戳转字符串
def timetag_to_datetime(timetag, format_str):
import time
return time.strftime(format_str, time.localtime(timetag / 1000))
代码逻辑详解
-
初始化 (
init):- 设置标的为 50ETF。
short_gap: 定义卖方合约距离平值合约(ATM)的档位。例如设为1,若ATM行权价为2.5,则卖出Call行权价可能为2.55,卖出Put行权价可能为2.45。wing_width: 定义买方保护合约距离卖方合约的宽度。
-
数据获取与处理:
- 使用
get_market_data_ex获取标的现价。 - 使用
get_option_list获取当月所有 Call 和 Put 合约。 - 使用
get_instrumentdetail获取合约的行权价 (OptExercisePrice)。 - 关键步骤: 将合约按行权价排序,这样我们可以通过列表索引(Index)简单地找到“虚值一档”、“虚值两档”的合约。
- 使用
-
合约选择:
- 首先找到行权价最接近标的现价的合约索引(ATM Index)。
- Call端: 索引越大行权价越高(越虚值)。Short Call = ATM + gap, Long Call = Short Call + width。
- Put端: 索引越小行权价越低(越虚值)。Short Put = ATM - gap, Long Put = Short Put - width。
-
交易执行:
- 使用
buy_open(买入开仓) 和sell_open(卖出开仓) 函数。 - 下单价格类型使用了
COMPETE(对手价),以提高成交概率。
- 使用
注意事项
- 保证金风险: 铁鹰策略虽然风险有限,但包含两个卖方头寸(Short Positions),在开仓时需要缴纳保证金。如果市场剧烈波动,可能面临追加保证金的风险。
- 组合单: 上述代码是分四笔委托发出的。在实盘中,为了避免“单腿裸露”风险(即一边成交一边未成交),建议使用 QMT 的组合交易接口(如果券商支持)或者先买后卖。
- 到期日处理: 代码中简单截取了
current_date_str[0:6]作为月份。在临近行权日(如每月第四个周三)时,通常需要移仓换月到下个月份,需要增加额外的逻辑判断。 - 平仓逻辑: 示例代码仅包含开仓逻辑。完整的策略需要加入止盈止损逻辑(例如组合收益达到最大收益的50%时平仓,或亏损达到一定阈值时平仓)。
Q&A
Q: 如何添加平仓逻辑?
A: 您可以在 handlebar 中监控持仓盈亏。使用 get_trade_detail_data 获取当前持仓,计算四个合约的总盈亏。当达到目标时,使用 buy_close 平掉卖方仓位,使用 sell_close 平掉买方仓位。
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 推荐的新版接口,返回的数据结构更清晰(Pandas DataFrame),且支持订阅模式,效率更高。
Q: 如何确保四条腿同时成交?
A: 在普通账户中很难保证完全同时。通常的做法是先买后卖(先建立保护腿,再建立风险腿)以控制风险,或者使用算法交易中的“篮子交易”功能。如果您的账户有组合保证金权限,可以使用组合下单类型代码(如 passorder 中的特定 opType)。