🚀 AI 一键生成 qmt 策略代码

如何使用 QMT Python API 构建蝶式价差期权组合策略?

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

问题描述

请帮我用Python实现一个蝶式价差期权策略,包括如何选择三条腿的合约并下单。

解决方案

这是一个关于如何在QMT平台上实现**蝶式价差(Butterfly Spread)**期权策略的详细指南。

蝶式价差是一种中性策略,通常用于预期标的资产价格在到期日将停留在某个特定价格附近(即波动率下降)。

策略逻辑(以买入看涨蝶式为例)

  1. 观点:预期标的资产价格变化不大,波动率降低。
  2. 构建
    • 买入 1张低行权价($K_1$)的看涨期权(实值)。
    • 卖出 2张中间行权价($K_2$)的看涨期权(平值)。
    • 买入 1张高行权价($K_3$)的看涨期权(虚值)。
    • 条件:$K_2 - K_1 = K_3 - K_2$ (行权价间距相等)。

QMT Python 策略代码实现

以下代码实现了一个自动化的买入看涨蝶式策略。它会自动获取标的当前价格,寻找平值合约(ATM),并选取左右相邻档位的合约作为翅膀。

# -*- coding: gbk -*-
import pandas as pd
import time

def init(ContextInfo):
    # 1. 设置资金账号 (请修改为您的实际账号)
    ContextInfo.accID = 'YOUR_ACCOUNT_ID' 
    ContextInfo.set_account(ContextInfo.accID)
    
    # 2. 策略参数设置
    ContextInfo.underlying = '510050.SH'  # 标的资产:上证50ETF
    ContextInfo.gap_level = 1             # 档位间隔:1表示选取相邻的行权价
    ContextInfo.trade_executed = False    # 交易标志位,防止重复下单
    ContextInfo.expiry_month = '202312'   # 目标到期月份,格式YYYYMM (需根据实际情况修改)
    
    print("策略初始化完成,标的:{}, 到期月:{}".format(ContextInfo.underlying, ContextInfo.expiry_month))

def handlebar(ContextInfo):
    # 仅在最后一根K线(实时行情)运行,且只交易一次
    if not ContextInfo.is_last_bar() or ContextInfo.trade_executed:
        return

    # 1. 获取标的最新价格
    market_data = ContextInfo.get_market_data_ex(
        ['close'], [ContextInfo.underlying], period='1d', count=1, subscribe=True
    )
    
    if ContextInfo.underlying not in market_data:
        print("未获取到标的行情")
        return
        
    current_price = market_data[ContextInfo.underlying].iloc[-1]['close']
    print("标的当前价格: {:.4f}".format(current_price))

    # 2. 获取期权链 (指定标的、到期月、看涨Call)
    # get_option_list(标的代码, 到期月YYYYMM, 类型CALL/PUT)
    option_list = ContextInfo.get_option_list(ContextInfo.underlying, ContextInfo.expiry_month, "CALL")
    
    if not option_list:
        print("未获取到期权合约列表,请检查到期月份设置")
        return

    # 3. 筛选并排序合约
    # 我们需要获取每个合约的行权价
    contracts_info = []
    for code in option_list:
        detail = ContextInfo.get_instrumentdetail(code)
        if detail:
            contracts_info.append({
                'code': code,
                'strike_price': detail['OptExercisePrice'],
                'name': detail['InstrumentName']
            })
    
    # 按行权价从小到大排序
    sorted_contracts = sorted(contracts_info, key=lambda x: x['strike_price'])
    
    if len(sorted_contracts) < 3:
        print("合约数量不足,无法构建蝶式策略")
        return

    # 4. 寻找平值合约 (K2) - 行权价最接近标的价格的合约
    # 使用 min 函数寻找绝对差值最小的合约索引
    atm_index = min(range(len(sorted_contracts)), key=lambda i: abs(sorted_contracts[i]['strike_price'] - current_price))
    
    # 5. 确定 K1 (低行权价) 和 K3 (高行权价)
    gap = ContextInfo.gap_level
    k1_index = atm_index - gap
    k3_index = atm_index + gap

    # 检查索引是否越界
    if k1_index < 0 or k3_index >= len(sorted_contracts):
        print("无法构建策略:平值合约过于接近边界,无法选取 K1 或 K3")
        return

    leg1 = sorted_contracts[k1_index] # K1: 买入实值
    leg2 = sorted_contracts[atm_index] # K2: 卖出平值
    leg3 = sorted_contracts[k3_index] # K3: 买入虚值

    print("构建蝶式组合:")
    print("Leg 1 (买入): {} 行权价: {}".format(leg1['name'], leg1['strike_price']))
    print("Leg 2 (卖出): {} 行权价: {}".format(leg2['name'], leg2['strike_price']))
    print("Leg 3 (买入): {} 行权价: {}".format(leg3['name'], leg3['strike_price']))

    # 6. 执行下单 (使用 passorder)
    # opType说明: 50:买入开仓, 52:卖出开仓
    # orderType: 1101 (单股、单账号、普通、股/手方式)
    # prType: 5 (最新价), 14 (对手价) - 这里演示使用对手价以保证成交
    
    # 下单 Leg 1: 买入 1 张 K1
    passorder(50, 1101, ContextInfo.accID, leg1['code'], 14, -1, 1, ContextInfo)
    print("下单: 买入开仓 1张 {}".format(leg1['code']))
    
    # 下单 Leg 2: 卖出 2 张 K2
    passorder(52, 1101, ContextInfo.accID, leg2['code'], 14, -1, 2, ContextInfo)
    print("下单: 卖出开仓 2张 {}".format(leg2['code']))
    
    # 下单 Leg 3: 买入 1 张 K3
    passorder(50, 1101, ContextInfo.accID, leg3['code'], 14, -1, 1, ContextInfo)
    print("下单: 买入开仓 1张 {}".format(leg3['code']))

    ContextInfo.trade_executed = True

代码关键点解析

  1. 合约选择逻辑 (handlebar 第2-5步)

    • 获取期权链:使用 ContextInfo.get_option_list 获取指定月份的所有Call合约。
    • 获取行权价:通过 ContextInfo.get_instrumentdetail 获取每个合约的详细信息,提取 OptExercisePrice(行权价)。
    • 定位ATM:计算所有行权价与标的现价的差值绝对值,最小的即为平值合约(K2)。
    • 确定翅膀:根据 gap_level(间隔档位),向左找K1,向右找K3。
  2. 下单逻辑 (handlebar 第6步)

    • passorder 函数:QMT的核心下单函数。
    • opType 参数
      • 50:买入开仓(用于K1和K3)。
      • 52:卖出开仓(用于K2)。
    • prType 参数:这里使用了 14 (对手价),在实盘中为了保证三条腿同时成交,通常建议使用对手价或更激进的价格,或者使用算法交易接口。
    • 数量:严格遵循 1:2:1 的比例(买1张K1,卖2张K2,买1张K3)。
  3. 风险控制

    • 代码中加入了 ContextInfo.trade_executed 标志位,确保策略在演示时只运行一次,防止重复开仓。
    • 加入了边界检查,防止ATM合约是列表中的第一个或最后一个,导致无法选取K1或K3。

注意事项

  • 保证金:卖出开仓(Leg 2)需要占用保证金,请确保账户资金充足。
  • 滑点风险:蝶式策略涉及三个合约,如果分别下单(Leging in),可能会面临价格变动的风险。在QMT中,如果资金量大,建议使用组合交易功能或算法交易功能来尽量同步成交。
  • 到期日管理:代码中硬编码了 expiry_month = '202312',实际使用时请务必修改为当前的有效月份。
  • 编码格式:QMT Python脚本必须在第一行加上 # -*- coding: gbk -*-,否则中文注释会报错。

Q&A

Q: 如何将此策略改为卖出蝶式(做多波动率)?
A: 只需反转下单方向:

  • Leg 1 (K1): 卖出开仓 (opType=52)
  • Leg 2 (K2): 买入开仓 (opType=50),数量为2
  • Leg 3 (K3): 卖出开仓 (opType=52)

Q: 如何获取当前最近的到期月份而不是手动输入?
A: 可以使用 ContextInfo.get_option_list 获取所有合约后,解析其到期日,或者通过标的ETF的详细信息查找关联的期权月份。简单的做法是获取当前日期,格式化为 YYYYMM

Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是QMT较新的接口,返回的数据结构(DataFrame)更标准,且支持订阅模式,处理实时数据更稳定。