🚀 AI 一键生成 qmt 策略代码

QMT 期权策略实战:如何构建买入跨式套利策略(Long Straddle)?

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

问题描述

请帮我编写一个买入跨式期权套利策略,在市场可能出现大波动时同时买入认购和认沽期权。

解决方案

这是一个基于 QMT 平台的 买入跨式期权(Long Straddle) 策略示例。

策略逻辑说明

  1. 核心思想:在预期市场会有大幅波动(大涨或大跌),但不确定方向时使用。
  2. 操作方式:同时买入相同数量、相同行权价、相同到期日的 认购期权(Call)认沽期权(Put)。通常选择 平值(ATM) 期权。
  3. 获利条件:标的资产价格大幅偏离行权价(无论上涨还是下跌),且幅度超过两份权利金之和。
  4. 代码实现
    • 获取标的(如 50ETF)的实时价格。
    • 筛选出当月到期的期权合约。
    • 找到行权价最接近当前标的价格的合约(平值合约)。
    • 同时买入 Call 和 Put。

QMT 策略代码

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

def init(ContextInfo):
    """
    策略初始化函数
    """
    # ================= 策略参数设置 =================
    # 标的资产:这里以 50ETF (510050.SH) 为例
    ContextInfo.underlying = '510050.SH'
    
    # 资金账号 (请务必修改为您自己的实盘或模拟账号)
    ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
    
    # 交易数量 (手)
    ContextInfo.trade_volume = 1
    
    # 策略运行状态标记,防止重复开仓
    ContextInfo.has_opened = False
    
    # 设置账号
    ContextInfo.set_account(ContextInfo.account_id)
    
    print("策略初始化完成,标的:{}, 账号:{}".format(ContextInfo.underlying, ContextInfo.account_id))

def get_atm_options(ContextInfo, underlying_price):
    """
    获取平值(ATM)期权合约对 (Call, Put)
    逻辑:
    1. 获取当前所有可交易期权
    2. 筛选出最近到期月份的合约
    3. 找到行权价与当前标的价格差值最小的合约
    """
    # 获取当前日期,格式 YYYYMMDD
    current_date = time.strftime('%Y%m%d', time.localtime(time.time()))
    
    # 获取标的对应的所有可交易期权合约列表
    # isavailable=True 表示获取当前可交易的
    option_list = ContextInfo.get_option_list(ContextInfo.underlying, current_date, "", True)
    
    if not option_list:
        print("未获取到期权合约列表")
        return None, None

    # 存储期权信息的列表
    options_data = []
    
    for opt_code in option_list:
        # 获取合约详细信息
        detail = ContextInfo.get_instrumentdetail(opt_code)
        
        # 过滤掉非期权品种或数据异常的
        if not detail or 'ExpireDate' not in detail or 'OptExercisePrice' not in detail:
            continue
            
        # 简单筛选:只看最近到期的(这里简化逻辑,取列表中的第一个到期日作为目标到期日)
        # 实际生产中可能需要更复杂的逻辑来锁定特定月份(如当月或次月)
        options_data.append({
            'code': opt_code,
            'expire_date': detail['ExpireDate'],
            'strike_price': detail['OptExercisePrice'],
            'type': detail['optType'] # 'CALL' or 'PUT'
        })
    
    if not options_data:
        return None, None

    # 转为 DataFrame 方便处理
    df = pd.DataFrame(options_data)
    
    # 1. 筛选最近的到期日 (最小的 ExpireDate)
    min_expire = df['expire_date'].min()
    df_near = df[df['expire_date'] == min_expire]
    
    # 2. 计算行权价与标的价格的差值的绝对值
    df_near['diff'] = abs(df_near['strike_price'] - underlying_price)
    
    # 3. 找到差值最小的行权价 (即平值 ATM)
    min_diff = df_near['diff'].min()
    target_strike = df_near[df_near['diff'] == min_diff]['strike_price'].iloc[0]
    
    # 4. 获取该行权价下的 Call 和 Put
    atm_call = df_near[(df_near['strike_price'] == target_strike) & (df_near['type'] == 'CALL')]['code'].values
    atm_put = df_near[(df_near['strike_price'] == target_strike) & (df_near['type'] == 'PUT')]['code'].values
    
    call_code = atm_call[0] if len(atm_call) > 0 else None
    put_code = atm_put[0] if len(atm_put) > 0 else None
    
    return call_code, put_code

def handlebar(ContextInfo):
    """
    K线周期运行函数
    """
    # 必须是实盘或回测的最后一根K线才执行交易逻辑
    if not ContextInfo.is_last_bar():
        return

    # 如果已经开仓,则不再开仓 (示例策略仅演示开仓逻辑)
    if ContextInfo.has_opened:
        return

    # 1. 获取标的最新价格
    # get_market_data_ex 返回的是 dict {code: dataframe}
    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("当前标的 {} 价格: {}".format(ContextInfo.underlying, current_price))

    # 2. 获取平值期权合约
    call_code, put_code = get_atm_options(ContextInfo, current_price)
    
    if not call_code or not put_code:
        print("未找到合适的平值期权合约")
        return
        
    print("选定平值期权对 -> Call: {}, Put: {}".format(call_code, put_code))

    # 3. 下单交易 (买入跨式:买 Call + 买 Put)
    # 注意:期权买方通常使用 buy_open (买入开仓)
    
    # 买入认购
    print("正在买入认购期权: {}".format(call_code))
    buy_open(call_code, ContextInfo.trade_volume, "LATEST", 0, ContextInfo, ContextInfo.account_id)
    
    # 买入认沽
    print("正在买入认沽期权: {}".format(put_code))
    buy_open(put_code, ContextInfo.trade_volume, "LATEST", 0, ContextInfo, ContextInfo.account_id)
    
    # 标记已开仓
    ContextInfo.has_opened = True
    print("跨式策略开仓指令已发送")

代码关键点解析

  1. get_atm_options 函数

    • 这是策略的核心。它利用 ContextInfo.get_option_list 获取标的关联的所有期权。
    • 利用 ContextInfo.get_instrumentdetail 获取期权的详细信息(行权价、到期日、类型)。
    • 通过 abs(strike_price - underlying_price) 最小化算法找到最接近当前价格的行权价(即平值期权)。
    • 同时筛选出最近到期月份的合约,保证流动性。
  2. buy_open 交易函数

    • buy_open(stockcode, amount, style, price, ContextInfo, accId)
    • 这里使用了 "LATEST" (最新价) 进行下单,实盘中为了保证成交,有时会使用对手价 "COMPETE" 或指定价格。
  3. 防重复开仓

    • 使用了 ContextInfo.has_opened 变量。在实际策略中,您应该通过 get_trade_detail_data 查询实际持仓来判断是否需要开仓,而不是仅靠一个变量。

使用前注意事项

  1. 账号设置:请务必将代码中的 'YOUR_ACCOUNT_ID' 替换为您实际的 QMT 资金账号(期权账号)。
  2. 品种权限:确保您的账号开通了期权交易权限,并且在 QMT 客户端中已经登录了该账号。
  3. 数据下载:在回测模式下,请确保下载了 510050.SH 及其对应期权合约的历史数据。
  4. 风险提示
    • 跨式策略虽然理论上风险有限(最多损失权利金),但如果市场横盘震荡,时间价值(Theta)的损耗会非常快。
    • 建议在实盘前先在模拟环境中充分测试。

常见问题 Q&A

Q: 如何修改为卖出跨式(做空波动率)?
A: 将 buy_open 改为 sell_open 即可。但请注意,卖出跨式风险无限(理论上),需要缴纳保证金,风险极高。

Q: 如何指定交易特定月份的期权?
A: 在 get_atm_options 函数中,df_near = df[df['expire_date'] == min_expire] 这一行选取了最近的到期日。你可以修改逻辑,比如判断 expire_date 是否包含特定的月份字符串(如 '202312')。

Q: 为什么获取不到期权数据?
A: 请检查 QMT 客户端右下角的连接状态是否正常,以及是否在“数据管理”中补充了期权的基础数据。