🚀 AI 一键生成 qmt 策略代码

如何在 QMT 中构建和管理一个 Delta 中性期权组合?

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

问题描述

请帮我实现一个期权的 Delta 对冲策略

解决方案

这是一个基于 QMT 平台的期权 Delta 对冲(Delta Neutral)策略实现。

策略逻辑说明

  1. 目标:保持投资组合的 Delta 值接近于 0,以规避标的资产价格小幅变动的风险,主要赚取时间价值(Theta)或波动率(Vega)的收益。
  2. 核心步骤
    • 获取持仓:读取当前账户中持有的期权合约数量和标的资产(如 ETF 或 股票)数量。
    • 计算 Delta:利用 BSM(Black-Scholes-Merton)模型计算期权的实时 Delta 值。
      • 需要参数:标的价格 ($S$)、行权价 ($K$)、无风险利率 ($r$)、剩余期限 ($T$)、隐含波动率 ($\sigma$)。
    • 计算对冲量
      • 期权总 Delta = 期权持仓量 $\times$ 合约乘数 $\times$ 单个期权 Delta。
      • 为了使总 Delta 为 0,标的资产的目标持仓量应为:$-1 \times$ 期权总 Delta。
    • 执行交易:比较标的资产的“目标持仓量”与“当前持仓量”,当偏差超过设定的阈值时,进行买入或卖出操作进行再平衡。

代码实现

# -*- coding: gbk -*-
import numpy as np
import math
from scipy.stats import norm
import datetime

def init(ContextInfo):
    """
    策略初始化函数
    """
    # 1. 设置账号 (请替换为您的实际资金账号)
    # 账号类型:'STOCK'股票, 'STOCK_OPTION'期权
    ContextInfo.account_id = 'YOUR_ACCOUNT_ID' 
    ContextInfo.account_type = 'STOCK_OPTION' # 假设在一个期权账户中同时交易标的和期权,如果是组合账户需分别设置
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 2. 策略参数设置
    # 期权代码 (示例:50ETF购9月3000)
    ContextInfo.option_code = '10005439.SHO' 
    # 标的资产代码 (示例:510050.SH)
    ContextInfo.underlying_code = '510050.SH'
    
    # 无风险利率 (年化,可设为国债收益率)
    ContextInfo.risk_free_rate = 0.025
    
    # 对冲阈值 (Delta偏差超过此数量的标的股数时才交易,避免频繁消耗手续费)
    # 例如:偏差超过 500 股才调仓
    ContextInfo.hedge_threshold = 500
    
    # 3. 设定运行周期
    # 建议在分钟线或Tick级别运行
    print("策略初始化完成,监控标的: {}, 期权: {}".format(ContextInfo.underlying_code, ContextInfo.option_code))

def calculate_bsm_delta(S, K, T, r, sigma, option_type):
    """
    计算 BSM 模型的 Delta 值
    S: 标的现价
    K: 行权价
    T: 剩余期限(年)
    r: 无风险利率
    sigma: 波动率
    option_type: 'CALL' 或 'PUT'
    """
    if T <= 0:
        return 0.0
    
    d1 = (math.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * math.sqrt(T))
    
    if option_type == 'CALL':
        delta = norm.cdf(d1)
    else:
        delta = norm.cdf(d1) - 1
        
    return delta

def get_days_to_expiration(expire_date_str):
    """
    计算距离到期日的剩余年化时间
    expire_date_str: 'YYYYMMDD' 格式
    """
    try:
        exp_date = datetime.datetime.strptime(str(expire_date_str), '%Y%m%d')
        today = datetime.datetime.now()
        days = (exp_date - today).days
        # 如果是当天到期,给予极小的时间值防止除零
        if days <= 0:
            return 0.0001
        return days / 365.0
    except Exception as e:
        print("日期计算错误:", e)
        return 0

def handlebar(ContextInfo):
    """
    K线/行情驱动函数
    """
    # 1. 获取当前持仓信息
    positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
    
    option_pos = 0      # 期权持仓数量 (张)
    underlying_pos = 0  # 标的持仓数量 (股)
    
    for pos in positions:
        if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == ContextInfo.option_code:
            option_pos = pos.m_nVolume
            # 如果是卖方持仓,数量为负 (QMT部分接口返回可能是正数配合方向,这里简化处理,假设净持仓)
            # 实际需根据 m_nDirection 判断,这里假设 m_nVolume 已经处理好或者是净持仓
            # 如果是义务仓(卖方),通常需要乘以 -1,具体视QMT版本返回对象属性而定
            # 这里假设 m_nVolume 为净持仓,多头为正
            
        elif pos.m_strInstrumentID + '.' + pos.m_strExchangeID == ContextInfo.underlying_code:
            underlying_pos = pos.m_nVolume

    # 如果没有期权持仓,则不需要对冲 (或者策略逻辑是先开期权)
    if option_pos == 0:
        # print("无期权持仓,无需对冲")
        return

    # 2. 获取期权详细信息 (行权价、到期日、类型、合约乘数)
    opt_detail = ContextInfo.get_option_detail_data(ContextInfo.option_code)
    if not opt_detail:
        print("获取期权详情失败")
        return
        
    K = opt_detail['OptExercisePrice']       # 行权价
    expire_date = opt_detail['ExpireDate']   # 到期日 YYYYMMDD
    opt_type = opt_detail['optType']         # 'CALL' or 'PUT'
    multiplier = opt_detail['VolumeMultiple'] # 合约乘数 (通常ETF期权为10000)
    
    # 3. 获取市场数据
    # 获取标的最新价 S
    last_tick = ContextInfo.get_full_tick([ContextInfo.underlying_code])
    if not last_tick:
        return
    S = last_tick[ContextInfo.underlying_code]['lastPrice']
    
    # 获取期权隐含波动率 Sigma
    # QMT 提供了直接获取 IV 的接口,如果获取不到可以使用历史波动率替代
    iv = ContextInfo.get_option_iv(ContextInfo.option_code)
    if iv <= 0.0001:
        # 如果获取失败,使用默认值或历史波动率,这里暂用 20% 代替演示
        sigma = 0.2 
    else:
        sigma = iv

    # 计算剩余期限 T
    T = get_days_to_expiration(expire_date)
    
    # 4. 计算 Delta
    unit_delta = calculate_bsm_delta(S, K, T, ContextInfo.risk_free_rate, sigma, opt_type)
    
    # 5. 计算对冲所需的标的头寸
    # 投资组合总 Delta = (期权持仓 * 合约乘数 * 单个Delta) + (标的持仓 * 1)
    # 目标是 总 Delta = 0
    # 所以:目标标的持仓 = -1 * (期权持仓 * 合约乘数 * 单个Delta)
    
    total_option_delta = option_pos * multiplier * unit_delta
    target_underlying_pos = -1 * total_option_delta
    
    # 6. 计算偏差并交易
    diff = target_underlying_pos - underlying_pos
    
    # 输出状态日志
    print(f"时间:{ContextInfo.get_bar_timetag(ContextInfo.barpos)} | 标的价:{S:.3f} | IV:{sigma:.3f} | Delta:{unit_delta:.4f}")
    print(f"期权持仓:{option_pos} | 当前标的:{underlying_pos} | 目标标的:{int(target_underlying_pos)} | 需调整:{int(diff)}")
    
    # 检查是否超过阈值
    if abs(diff) > ContextInfo.hedge_threshold:
        trade_vol = int(abs(diff))
        # 调整为100的倍数 (A股/ETF通常按手交易)
        trade_vol = (trade_vol // 100) * 100
        
        if trade_vol == 0:
            return

        if diff > 0:
            # 需要买入标的
            print(f"执行对冲:买入 {trade_vol} 股 {ContextInfo.underlying_code}")
            passorder(23, 1101, ContextInfo.account_id, ContextInfo.underlying_code, 5, -1, trade_vol, ContextInfo)
        else:
            # 需要卖出标的
            print(f"执行对冲:卖出 {trade_vol} 股 {ContextInfo.underlying_code}")
            passorder(24, 1101, ContextInfo.account_id, ContextInfo.underlying_code, 5, -1, trade_vol, ContextInfo)

关键点解析

  1. BSM 模型计算 Delta

    • 代码中 calculate_bsm_delta 函数实现了标准的 Black-Scholes Delta 计算公式。
    • norm.cdf 用于计算标准正态分布的累积分布函数。
    • Call Option Delta $\in (0, 1)$,Put Option Delta $\in (-1, 0)$。
  2. 数据获取

    • get_option_detail_data:这是 QMT 特有的接口,非常方便地获取期权的静态数据(行权价、合约乘数、到期日等)。
    • get_option_iv:直接获取交易所或券商计算好的隐含波动率,这比自己计算要准确且高效。
    • get_full_tick:获取标的资产最新的实时价格。
  3. 对冲逻辑

    • 公式:$Target_Underlying = -(Option_Pos \times Multiplier \times Delta)$。
    • 例如:持有 1 张认购期权(Call),Delta 为 0.6,合约乘数 10000。
    • 期权端 Delta 敞口 = $1 \times 10000 \times 0.6 = 6000$ 股。
    • 为了中性化,我们需要持有 -6000 股标的(即卖空 6000 股)。
    • 注意:如果账户不能融券(卖空标的),则该策略只能用于对冲义务仓(卖出期权,Delta为负,需要买入标的)或者在已有标的持仓基础上进行减持。
  4. 交易执行

    • 使用了 passorder 函数。
    • opType 23 代表买入,24 代表卖出。
    • prType 5 代表最新价下单。
    • 加入了 hedge_threshold(阈值)控制,防止因为 Delta 的微小跳动导致频繁交易,消耗手续费。

使用前注意事项

  1. 账号设置:请务必将 ContextInfo.account_id 替换为您真实的资金账号。
  2. 合约代码:期权合约代码会随时间过期,请替换为当前存续的主力合约代码。
  3. Python 库:确保 QMT 的 Python 环境中安装了 scipy 库(QMT 内置 Python 通常已包含)。
  4. 实盘风险:Delta 是动态变化的(Gamma 风险),且 IV 也会变化。此策略仅为 Delta 中性,未考虑 Gamma 和 Vega 风险,实盘请务必先在模拟盘测试。

Q&A

Q: 如何获取期权的隐含波动率 (IV)?
A: 在 QMT 中,可以使用 ContextInfo.get_option_iv(option_code) 接口直接获取。如果返回值为 0 或异常,建议自行计算或使用历史波动率作为替代。

Q: 为什么需要设置对冲阈值 (hedge_threshold)?
A: 市场价格是时刻波动的,Delta 值也会随之微小跳动。如果不设置阈值,策略可能会在每一根 K 线都进行微量的买卖(例如买 100 股,下一分钟卖 100 股),这会导致巨大的交易成本(手续费和滑点)。

Q: 这个策略支持 Put 期权吗?
A: 支持。代码中的 calculate_bsm_delta 函数已经根据 option_type ('CALL' 或 'PUT') 进行了区分处理。Put 的 Delta 为负数,计算出的目标标的持仓将为正数(买入标的来对冲)。