3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个基于 QMT 平台的期权铁鹰式(Iron Condor)策略实现。
铁鹰式策略是一种中性策略,适用于预期标的资产价格在特定区间内震荡、波动率下降的市场环境。它由四条腿(Legs)组成,即同时构建一个**卖出宽跨式(Short Strangle)和一个买入宽跨式(Long Strangle)**作为保护。
具体构成(假设标的价格为 S):
行权价关系: $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):
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)。合约选择:
交易执行:
buy_open (买入开仓) 和 sell_open (卖出开仓) 函数。COMPETE (对手价),以提高成交概率。current_date_str[0:6] 作为月份。在临近行权日(如每月第四个周三)时,通常需要移仓换月到下个月份,需要增加额外的逻辑判断。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)。