🚀 AI 一键生成 qmt 策略代码

QMT 期权教程:牛市价差 (Bull Call Spread) 策略实现

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

问题描述

如何编写牛市价差期权策略?

解决方案

编写牛市价差(Bull Call Spread)期权策略需要同时操作两个方向的期权合约:买入一个较低行权价的认购期权(Call),同时卖出一个较高行权价的认购期权(Call)。两者标的资产相同,到期日相同。

这种策略通常用于预期标的资产价格会温和上涨,通过卖出高行权价期权来降低权利金成本。

以下是基于 QMT Python API 实现的策略代码。

策略逻辑说明

  1. 标的选择:以 50ETF(510050.SH)为例。
  2. 合约选择
    • 获取当月或次月到期的认购期权列表。
    • 买入腿(Long Leg):选择平值(ATM)或轻微实值期权。
    • 卖出腿(Short Leg):选择虚值(OTM)期权(行权价高于买入腿)。
  3. 交易信号
    • 当标的资产价格上涨趋势确立(例如价格高于20日均线)且当前无持仓时,构建组合。
    • 当临近到期日或标的价格跌破止损线时,平仓。
  4. 风控:设置简单的到期平仓逻辑。

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))

代码关键点解析

  1. 期权合约获取 (get_option_list):

    • 使用 ContextInfo.get_option_list(标的代码, 月份, 类型) 获取期权列表。
    • target_month 需要根据当前回测或实盘的时间动态生成(格式为 YYYYMM)。
  2. 行权价筛选:

    • 通过 ContextInfo.get_instrumentdetail(合约代码) 获取合约详情,其中 OptExercisePrice 是行权价。
    • 策略核心在于选取两个行权价:
      • $K_L$ (Low Strike): 通常选平值(ATM)附近,代码中通过计算与标的现价差值绝对值最小来定位。
      • $K_H$ (High Strike): 选比 $K_L$ 高一档或多档的行权价。
  3. 交易指令:

    • 买入腿: 使用 buy_open (买入开仓)。
    • 卖出腿: 使用 sell_open (卖出开仓)。
    • 平仓: 对应使用 buy_close_tdayfirst (买入平仓,优先平今) 和 sell_close_tdayfirst (卖出平仓,优先平今)。
  4. 到期日管理:

    • 期权策略必须关注到期日。代码中通过 get_instrumentdetail 获取 ExpireDate,并在临近到期前强制平仓,避免进入行权交割流程(大部分投机策略不希望进行实物交割)。

注意事项

  • 保证金: 卖出期权(Short Call)需要占用保证金,牛市价差策略虽然风险有限,但作为组合策略,如果账户不支持组合保证金优惠,可能会占用两笔资金(买入权利金 + 卖出保证金)。
  • 滑点与流动性: 期权流动性不如标的资产,建议在实盘中使用限价单(FIX)而非市价单,或者使用算法交易接口。
  • 回测数据: 运行此策略需要下载完整的期权历史数据和标的(50ETF)历史数据。

Q&A

Q: 如何修改策略为熊市价差(Bear Put Spread)?
A: 逻辑类似,但方向相反:

  1. get_option_list 的类型从 "CALL" 改为 "PUT"
  2. 买入一个较高行权价的认沽期权(Put)。
  3. 卖出一个较低行权价的认沽期权(Put)。

Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 推荐的新版接口,返回的数据结构更统一(DataFrame),且支持订阅模式,处理效率更高。

Q: 实盘中如何保证两腿同时成交?
A: 在普通账户中很难保证完全同时。通常建议先成交买入腿(权利方),再成交卖出腿(义务方),以防止裸卖空的风险暴露。如果拥有组合交易权限,可以使用 passorder 的组合下单类型(如 opType=40 期货组合开多等,具体需参考券商提供的组合策略类型ID)。