🚀 AI 一键生成 qmt 策略代码

QMT 期权交易:如何实现一个简单的期权做市商策略?

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

问题描述

如何编写一个期权做市商策略,实现自动双边报价?

解决方案

编写一个期权做市商(Market Maker)策略是一个复杂的任务,涉及理论定价、库存管理、订单管理和风险控制。在QMT中,我们可以利用其内置的期权定价函数(BSM模型)和交易接口来实现一个基础的自动双边报价策略

策略逻辑概述

  1. 理论定价:使用 Black-Scholes-Merton (BSM) 模型计算期权的理论合理价格(Fair Price)。
  2. 确定价差:在理论价格的基础上,加减设定的价差(Spread),形成买单价格(Bid)和卖单价格(Ask)。
  3. 撤单机制:在每一轮报价前,撤销之前未成交的挂单,防止订单堆积和价格滞后。
  4. 双边报价:同时发送买入开仓(Bid)和卖出开仓(Ask)的限价委托。
  5. 风控(简化版):检查标的价格有效性,避免在数据异常时下单。

核心 API 说明

  • ContextInfo.bsm_price(...): 计算期权理论价格。
  • ContextInfo.get_market_data_ex(...): 获取标的资产实时行情。
  • ContextInfo.get_trade_detail_data(...): 获取当前挂单状态。
  • cancel(...): 撤单。
  • passorder(...): 下单。

QMT 期权做市商策略代码

# -*- coding: gbk -*-
import time
import datetime
import math

def init(ContextInfo):
    # ================= 策略参数设置 =================
    # 资金账号 (请修改为您的实际期权账号)
    ContextInfo.accID = 'YOUR_OPTION_ACCOUNT_ID'
    
    # 目标期权合约 (例如: 50ETF购x月x.xxx)
    # 请确保该合约在您的行情列表中存在
    ContextInfo.option_code = '1000xxxx.SHO' 
    
    # 标的资产代码 (例如: 50ETF)
    ContextInfo.underlying_code = '510050.SH'
    
    # 期权类型: 'C' (认购) 或 'P' (认沽)
    ContextInfo.option_type = 'C'
    
    # 行权价 (Strike Price)
    ContextInfo.strike_price = 3.000 
    
    # 到期日 (格式: YYYYMMDD)
    ContextInfo.expire_date = '20231227'
    
    # 无风险利率 (例如 2.5%)
    ContextInfo.risk_free_rate = 0.025
    
    # 预设波动率 (IV),实际交易中通常需要根据市场反推或使用波动率曲面
    ContextInfo.sigma = 0.20
    
    # 报价单边价差 (Spread的一半)
    # Bid = FairPrice - half_spread
    # Ask = FairPrice + half_spread
    ContextInfo.half_spread = 0.0010
    
    # 单笔报单数量 (张)
    ContextInfo.volume = 1
    
    # 策略运行间隔 (秒)
    ContextInfo.run_interval = 5
    ContextInfo.last_run_time = 0
    
    # 设置账号
    ContextInfo.set_account(ContextInfo.accID)
    print("期权做市策略初始化完成")

def handlebar(ContextInfo):
    # 仅在实盘的最后一根K线(实时行情)运行
    if not ContextInfo.is_last_bar():
        return

    # 控制运行频率
    current_time = time.time()
    if current_time - ContextInfo.last_run_time < ContextInfo.run_interval:
        return
    ContextInfo.last_run_time = current_time

    # 1. 获取标的资产最新价格
    # 获取标的最近一笔Tick数据
    tick_data = ContextInfo.get_full_tick([ContextInfo.underlying_code])
    if ContextInfo.underlying_code not in tick_data:
        print(f"未获取到标的 {ContextInfo.underlying_code} 行情")
        return
        
    underlying_price = tick_data[ContextInfo.underlying_code]['lastPrice']
    if underlying_price <= 0:
        return

    # 2. 计算剩余天数 (Days to Maturity)
    try:
        today = datetime.datetime.now()
        exp_date = datetime.datetime.strptime(ContextInfo.expire_date, '%Y%m%d')
        delta = exp_date - today
        days_to_maturity = delta.days + (delta.seconds / 86400.0)
        
        if days_to_maturity <= 0:
            print("期权已到期或过期,停止报价")
            return
    except Exception as e:
        print(f"日期计算错误: {e}")
        return

    # 3. 计算 BSM 理论价格 (Fair Price)
    # bsm_price(optionType, objectPrices, strikePrice, riskFree, sigma, days, dividend)
    # 假设分红率为 0
    fair_price = ContextInfo.bsm_price(
        ContextInfo.option_type, 
        underlying_price, 
        ContextInfo.strike_price, 
        ContextInfo.risk_free_rate, 
        ContextInfo.sigma, 
        days_to_maturity, 
        0
    )
    
    if math.isnan(fair_price) or fair_price <= 0:
        print("理论价格计算异常")
        return

    # 4. 计算双边报价价格
    # 价格需要符合最小变动单位 (假设为 0.0001,具体视合约而定)
    bid_price = round(fair_price - ContextInfo.half_spread, 4)
    ask_price = round(fair_price + ContextInfo.half_spread, 4)
    
    print(f"标的价:{underlying_price}, 理论价:{fair_price:.4f}, 拟报买:{bid_price}, 拟报卖:{ask_price}")

    # 5. 撤销旧订单 (Cancel Logic)
    # 获取当前未成交委托
    orders = get_trade_detail_data(ContextInfo.accID, 'STOCK_OPTION', 'ORDER')
    for order in orders:
        # 仅撤销本策略针对该合约的挂单 (通过合约代码判断)
        # 注意:这里会撤销该合约所有未成交单,如果有人工单需谨慎
        if order.m_strInstrumentID == ContextInfo.option_code and order.m_nOrderStatus in [48, 49, 50, 51, 52, 55]: # 未成交或部成状态
            cancel(order.m_strOrderSysID, ContextInfo.accID, 'STOCK_OPTION', ContextInfo)
            print(f"撤单: {order.m_strOrderSysID}")

    # 6. 发送新订单 (Place Orders)
    # 这里的 opType 使用:
    # 50: 买入开仓 (Buy Open)
    # 52: 卖出开仓 (Sell Open)
    # orderType 1101: 单股/单账号/普通/限价
    # prType 11: 指定价 (配合 price 参数)
    
    # 发送买单 (提供流动性 - Bid)
    if bid_price > 0:
        passorder(50, 1101, ContextInfo.accID, ContextInfo.option_code, 11, bid_price, ContextInfo.volume, ContextInfo)
        print(f"报单: 买入开仓 {ContextInfo.option_code} 价格 {bid_price} 数量 {ContextInfo.volume}")

    # 发送卖单 (提供流动性 - Ask)
    if ask_price > 0:
        passorder(52, 1101, ContextInfo.accID, ContextInfo.option_code, 11, ask_price, ContextInfo.volume, ContextInfo)
        print(f"报单: 卖出开仓 {ContextInfo.option_code} 价格 {ask_price} 数量 {ContextInfo.volume}")

代码关键点解析

  1. BSM 定价 (ContextInfo.bsm_price):

    • 这是做市商的核心。我们根据标的资产(如50ETF)的实时价格,结合行权价、剩余时间、利率和波动率算出理论价格。
    • 注意: 代码中的 ContextInfo.sigma (波动率) 是固定值。在进阶策略中,你需要根据市场隐含波动率(IV)实时更新这个值,或者维护一个波动率曲面。
  2. 撤单逻辑:

    • 做市商必须紧跟市场。如果标的价格变动,旧的挂单价格可能已经偏离合理区间(被“打穿”),导致亏损。
    • 因此,代码逻辑是:先撤旧单,再报新单
    • 使用 get_trade_detail_data 获取 ORDER 对象,检查 m_nOrderStatus 状态码(如 50=已报, 55=部成)来决定是否撤单。
  3. 下单接口 (passorder):

    • 期权交易的 opType 与股票不同。
    • 50: 买入开仓 (做多波动率/方向)。
    • 52: 卖出开仓 (做空波动率/方向,即作为卖方)。
    • 做市商通常双边开仓(既买又卖),赚取 Bid-Ask Spread。
    • 注:实际做市中,如果手里有持仓,可能需要判断是“平仓”还是“开仓”。本示例为了简化,统一使用“开仓”指令。
  4. 时间与频率控制:

    • 使用 ContextInfo.run_interval 控制报价频率(例如每5秒一次),避免过于频繁的撤单报单被交易所限制或产生过多的废单。

风险提示与改进建议

这是一个基础的演示策略,切勿直接用于实盘大资金交易,因为它缺少以下关键模块:

  1. Delta 对冲 (Delta Hedging):
    • 做市商通常希望保持 Delta 中性。当成交后,持仓会有 Delta 敞口,需要买卖标的资产(如50ETF)来对冲风险。
  2. 库存管理:
    • 如果买单成交多,卖单成交少,库存会积压。策略应根据当前持仓调整报价中心(例如库存多时,降低卖价以促进卖出)。
  3. 波动率拟合:
    • 固定波动率会导致定价偏离市场。应使用 ContextInfo.get_option_iv 获取市场隐含波动率,或自行计算实时 IV。
  4. 异常处理:
    • 需要处理行情中断、涨跌停板、资金不足等异常情况。

Q&A

Q: 如何获取实时的隐含波动率而不是使用固定值?
A: 可以使用 ContextInfo.get_option_iv(optioncode) 接口获取交易所发布的或系统计算的实时隐含波动率,将其赋值给 ContextInfo.sigma

Q: 为什么我的 passorder 下单没有反应?
A: 1. 检查 ContextInfo.accID 是否填写正确且已登录。2. 检查 opType 是否正确(期权是50/52)。3. 检查价格是否在涨跌停范围内。4. 检查是否有足够的保证金。

Q: 如何实现 Delta 对冲?
A: 你需要计算持仓的 Delta 总和。QMT 没有直接返回持仓 Delta 的接口,你需要遍历持仓,对每个合约调用 BSM 公式计算 Delta(BSM公式的一阶导数),加总后,如果 Delta 偏离 0 太多,就交易 50ETF 现货进行对冲。