🚀 AI 一键生成 qmt 策略代码

QMT 期权进阶:铁鹰式 (Iron Condor) 策略 Python 源码

qmt | 发布时间: 2025/12/22
以下内容由 EasyQuant 生成。

问题描述

请帮我写一个期权铁鹰式策略

解决方案

这是一个基于 QMT 平台的期权铁鹰式(Iron Condor)策略实现。

策略原理

铁鹰式策略是一种中性策略,适用于预期标的资产价格在特定区间内震荡、波动率下降的市场环境。它由四条腿(Legs)组成,即同时构建一个**卖出宽跨式(Short Strangle)和一个买入宽跨式(Long Strangle)**作为保护。

具体构成(假设标的价格为 S):

  1. 卖出看涨 (Short Call): 行权价 $K3$ (虚值,高于 S)
  2. 买入看涨 (Long Call): 行权价 $K4$ (深虚值,高于 K3,用于保护)
  3. 卖出看跌 (Short Put): 行权价 $K2$ (虚值,低于 S)
  4. 买入看跌 (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))

代码逻辑详解

  1. 初始化 (init):

    • 设置标的为 50ETF。
    • short_gap: 定义卖方合约距离平值合约(ATM)的档位。例如设为1,若ATM行权价为2.5,则卖出Call行权价可能为2.55,卖出Put行权价可能为2.45。
    • wing_width: 定义买方保护合约距离卖方合约的宽度。
  2. 数据获取与处理:

    • 使用 get_market_data_ex 获取标的现价。
    • 使用 get_option_list 获取当月所有 Call 和 Put 合约。
    • 使用 get_instrumentdetail 获取合约的行权价 (OptExercisePrice)。
    • 关键步骤: 将合约按行权价排序,这样我们可以通过列表索引(Index)简单地找到“虚值一档”、“虚值两档”的合约。
  3. 合约选择:

    • 首先找到行权价最接近标的现价的合约索引(ATM Index)。
    • Call端: 索引越大行权价越高(越虚值)。Short Call = ATM + gap, Long Call = Short Call + width。
    • Put端: 索引越小行权价越低(越虚值)。Short Put = ATM - gap, Long Put = Short Put - width。
  4. 交易执行:

    • 使用 buy_open (买入开仓) 和 sell_open (卖出开仓) 函数。
    • 下单价格类型使用了 COMPETE (对手价),以提高成交概率。

注意事项

  1. 保证金风险: 铁鹰策略虽然风险有限,但包含两个卖方头寸(Short Positions),在开仓时需要缴纳保证金。如果市场剧烈波动,可能面临追加保证金的风险。
  2. 组合单: 上述代码是分四笔委托发出的。在实盘中,为了避免“单腿裸露”风险(即一边成交一边未成交),建议使用 QMT 的组合交易接口(如果券商支持)或者先买后卖。
  3. 到期日处理: 代码中简单截取了 current_date_str[0:6] 作为月份。在临近行权日(如每月第四个周三)时,通常需要移仓换月到下个月份,需要增加额外的逻辑判断。
  4. 平仓逻辑: 示例代码仅包含开仓逻辑。完整的策略需要加入止盈止损逻辑(例如组合收益达到最大收益的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)。