🚀 AI 一键生成 qmt 策略代码

如何在 QMT 策略中实现基于资金曲线的最大回撤止损?

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

问题描述

如何监控我的资金曲线,当回撤达到20%的时候就自动清仓止损?

解决方案

这是一个非常实用的风控需求。在 QMT 中实现基于资金曲线回撤的自动清仓(止损),核心逻辑如下:

  1. 获取当前总资产:通过 get_trade_detail_data 获取账户的动态权益(总资产)。
  2. 记录权益高点(High Water Mark):在策略运行过程中,不断更新并记录历史最高资产净值。
  3. 计算回撤:$回撤幅度 = (最高资产 - 当前资产) / 最高资产$。
  4. 触发清仓:当回撤幅度 $\ge 20%$ 时,遍历当前持仓并发出市价全平指令,同时停止策略的开仓逻辑。

以下是完整的策略代码实现。

策略代码

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

def init(ContextInfo):
    # ================= 配置区域 =================
    # 请在此处填写您的资金账号
    ContextInfo.accID = '6000000000' 
    # 账号类型:'STOCK'股票, 'FUTURE'期货
    ContextInfo.accountType = 'STOCK'
    # 止损阈值 (20%)
    ContextInfo.stop_loss_threshold = 0.20
    # ===========================================

    # 绑定账号,用于接收回报和下单
    ContextInfo.set_account(ContextInfo.accID)
    
    # 初始化全局变量
    ContextInfo.max_capital = 0.0      # 记录历史最高权益
    ContextInfo.is_triggered = False   # 标记是否已经触发了清仓
    
    print("策略初始化完成,开始监控资金曲线...")

def handlebar(ContextInfo):
    # 如果已经触发过清仓,则不再执行任何逻辑(防止清仓后又开仓)
    if ContextInfo.is_triggered:
        return

    # 1. 获取账户资金信息
    # get_trade_detail_data 返回的是一个列表,通常取第一个对象
    account_obj_list = get_trade_detail_data(ContextInfo.accID, ContextInfo.accountType, 'ACCOUNT')
    
    if not account_obj_list:
        print("未获取到账户信息,请检查账号配置或连接状态")
        return

    account_info = account_obj_list[0]
    
    # m_dBalance 代表总资产(动态权益)
    current_capital = account_info.m_dBalance
    
    # 2. 更新历史最高权益 (High Water Mark)
    if ContextInfo.max_capital == 0:
        ContextInfo.max_capital = current_capital
    else:
        if current_capital > ContextInfo.max_capital:
            ContextInfo.max_capital = current_capital

    # 3. 计算当前回撤
    if ContextInfo.max_capital > 0:
        drawdown = (ContextInfo.max_capital - current_capital) / ContextInfo.max_capital
    else:
        drawdown = 0.0

    # 打印资金监控日志 (可选,避免日志过多可注释掉)
    # print(f"当前资产: {current_capital:.2f}, 历史最高: {ContextInfo.max_capital:.2f}, 当前回撤: {drawdown*100:.2f}%")

    # 4. 判断是否达到止损阈值
    if drawdown >= ContextInfo.stop_loss_threshold:
        msg = f"【警告】当前回撤 {drawdown*100:.2f}% 已达到阈值 {ContextInfo.stop_loss_threshold*100}%,触发清仓止损!"
        print(msg)
        ContextInfo.paint('回撤报警', 1, -1, 0, 'red', 'noaxis') # 在界面上画图提示
        
        # 执行清仓操作
        clear_all_positions(ContextInfo)
        
        # 标记为已触发,停止后续逻辑
        ContextInfo.is_triggered = True

def clear_all_positions(ContextInfo):
    """
    清仓函数:遍历所有持仓并市价卖出
    """
    # 获取当前持仓列表
    positions = get_trade_detail_data(ContextInfo.accID, ContextInfo.accountType, 'POSITION')
    
    if not positions:
        print("当前无持仓,无需操作。")
        return

    print(f"开始执行清仓,共 {len(positions)} 只标的...")

    for pos in positions:
        stock_code = pos.m_strInstrumentID + "." + pos.m_strExchangeID # 拼接代码,如 000001.SZ
        volume = pos.m_nVolume # 持仓数量
        
        # 过滤掉数量为0的持仓(有时会有残余记录)
        if volume <= 0:
            continue

        print(f"正在平仓: {stock_code}, 数量: {volume}")

        # 下单参数:
        # 24: 卖出
        # 1101: 单股单账号普通交易
        # ContextInfo.accID: 账号
        # stock_code: 代码
        # 14: 对手价 (为了快速成交,也可以用 5:最新价 或 44:对手方最优)
        # -1: 价格 (市价/对手价模式下无效,填-1即可)
        # volume: 数量
        passorder(24, 1101, ContextInfo.accID, stock_code, 14, -1, volume, ContextInfo)
    
    print("清仓指令发送完毕。")

代码逻辑详解

  1. init 函数

    • 设置了 ContextInfo.accID,请务必修改为您实际的资金账号。
    • 初始化了 max_capital(最高权益)和 is_triggered(触发标记)。is_triggered 的作用是防止清仓后,策略的其他逻辑(如果有)又去买入股票,或者重复发送清仓指令。
  2. handlebar 函数

    • 这是 QMT 的核心驱动函数,行情更新时会调用。
    • 获取资金:使用 get_trade_detail_data(..., 'ACCOUNT') 获取账户对象,其中 m_dBalance 属性即为当前的总资产(现金+持仓市值)。
    • 计算回撤:逻辑是 (历史最高 - 当前) / 历史最高
    • 触发风控:一旦回撤比例 >= 0.20,调用 clear_all_positions 函数。
  3. clear_all_positions 函数

    • 使用 get_trade_detail_data(..., 'POSITION') 获取当前所有持仓。
    • 遍历持仓列表,使用 passorder 函数发送卖出指令。
    • 下单价格类型:代码中使用了 14 (对手价),这是为了在止损时能尽快成交。如果是在极速变动的行情中,也可以考虑使用市价委托(需根据具体券商支持情况调整 prType 参数)。

注意事项

  • 实盘/模拟盘模式:此代码依赖 get_trade_detail_data,因此必须在实盘模拟交易模式下运行,不能在纯回测模式下直接使用此逻辑(回测模式下资金获取方式不同)。
  • 数据频率handlebar 的运行频率取决于您订阅行情的频率。建议在主图上运行,或者订阅全推行情,以确保资金监控的实时性。
  • 手动干预:代码触发清仓后会设置 is_triggered = True 停止运行。如果您后续入金了或者想重新开启策略,需要手动停止并重新运行策略以重置该变量。

Q&A

Q: 如何在回测中实现这个功能?
A: 回测中不需要使用 get_trade_detail_data。在回测模式下,您可以使用 ContextInfo.capital 获取初始资金,结合 ContextInfo.get_net_value(ContextInfo.barpos) 获取当前净值来计算总资产和回撤。

Q: 为什么使用 passorder 而不是 order_target_value
A: passorder 是 QMT 最底层的下单接口,对于清仓这种操作,它能更精确地控制下单类型(如使用对手价或市价)和指定具体的资金账号,在实盘风控场景下更可靠。

Q: 清仓后如何自动停止策略?
A: 代码中通过 if ContextInfo.is_triggered: return 实现了逻辑上的停止。如果您希望彻底停止 Python 引擎,可以在清仓后调用 ContextInfo.stop()(注意:这可能会导致整个策略线程停止)。