🚀 AI 一键生成 qmt 策略代码

QMT 策略风控进阶:利用 get_net_value 实现动态权益回撤止损

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

问题描述

如何在策略中根据净值回撤来实现自动止损?

解决方案

在 QMT 策略中实现基于净值回撤的自动止损,核心逻辑是实时监控账户的总资产(净值),记录历史最高净值,并计算当前净值相对于最高净值的回撤幅度。一旦回撤超过设定的阈值,立即清空所有持仓并停止开新仓。

以下是具体的实现步骤和代码示例。

实现逻辑

  1. 初始化 (init):

    • 设定止损阈值(例如 10%)。
    • 初始化全局变量用于记录“历史最高净值”和“是否触发止损”的标记。
    • 设置交易账号。
  2. 盘中运行 (handlebar):

    • 获取当前账户的总资产(m_dBalance)。
    • 更新历史最高净值:如果当前净值 > 历史最高,则更新。
    • 计算回撤:$回撤率 = 1 - \frac{当前净值}{历史最高净值}$。
    • 判断止损:
      • 如果 回撤率 >= 阈值未触发过止损
        1. 将止损标记设为 True
        2. 执行清仓操作(遍历持仓并卖出)。
        3. 打印日志告警。
    • 策略控制: 如果止损标记为 True,则跳过正常的开仓逻辑,不再进行交易。

完整策略代码

# -*- coding: gbk -*-
import pandas as pd

def init(ContextInfo):
    # ================= 策略参数设置 =================
    # 设置资金账号 (请替换为您自己的账号)
    ContextInfo.accID = '6000000000' 
    ContextInfo.accType = 'STOCK'  # 账号类型:'STOCK'股票, 'FUTURE'期货
    
    # 设置最大回撤阈值 (例如 0.10 代表 10%)
    ContextInfo.max_drawdown_limit = 0.10 
    
    # ================= 全局变量初始化 =================
    ContextInfo.max_net_value = 0.0       # 记录历史最高净值
    ContextInfo.is_stopped = False        # 止损触发标记
    
    # 绑定账号,用于接收交易回报和查询资产
    ContextInfo.set_account(ContextInfo.accID)
    print("策略初始化完成,回撤止损阈值设置为: {:.2%}".format(ContextInfo.max_drawdown_limit))

def handlebar(ContextInfo):
    # 如果已经触发止损,则不再执行任何逻辑
    if ContextInfo.is_stopped:
        return

    # 1. 获取账户资金信息
    # get_trade_detail_data 返回的是一个对象列表
    account_info_list = get_trade_detail_data(ContextInfo.accID, ContextInfo.accType, 'account')
    
    if not account_info_list:
        print("未获取到账户信息,跳过本次计算")
        return
        
    # 获取当前总资产 (动态权益)
    account_obj = account_info_list[0]
    current_nav = account_obj.m_dBalance
    
    # 2. 更新历史最高净值
    # 如果是第一次运行,将当前净值设为最高
    if ContextInfo.max_net_value == 0:
        ContextInfo.max_net_value = current_nav
    elif current_nav > ContextInfo.max_net_value:
        ContextInfo.max_net_value = current_nav
        
    # 3. 计算当前回撤
    current_drawdown = 0.0
    if ContextInfo.max_net_value > 0:
        current_drawdown = 1 - (current_nav / ContextInfo.max_net_value)
    
    # 打印当前净值状态 (可选,用于调试)
    # print(f"当前净值: {current_nav}, 最高净值: {ContextInfo.max_net_value}, 当前回撤: {current_drawdown:.2%}")

    # 4. 判断是否触发回撤止损
    if current_drawdown >= ContextInfo.max_drawdown_limit:
        print("【警告】触发净值回撤止损!当前回撤: {:.2%}, 阈值: {:.2%}".format(
            current_drawdown, ContextInfo.max_drawdown_limit))
        
        # 标记为已止损,阻止后续开仓
        ContextInfo.is_stopped = True
        
        # 执行清仓操作
        clear_all_positions(ContextInfo)
        return

    # =================================================
    # 下面写正常的策略买卖逻辑
    # =================================================
    
    # 示例:只有在未止损的情况下才执行策略逻辑
    if not ContextInfo.is_stopped:
        # my_strategy_logic(ContextInfo)
        pass

def clear_all_positions(ContextInfo):
    """
    清空所有持仓的函数
    """
    print("开始执行清仓操作...")
    
    # 获取当前持仓列表
    positions_list = get_trade_detail_data(ContextInfo.accID, ContextInfo.accType, 'position')
    
    for pos in positions_list:
        stock_code = pos.m_strInstrumentID + "." + pos.m_strExchangeID
        volume = pos.m_nVolume # 当前持仓数量
        can_use_volume = pos.m_nCanUseVolume # 可用持仓数量
        
        # 如果是股票,只能卖出可用持仓 (T+1)
        # 如果是期货,通常可以直接平仓,这里以股票为例
        if can_use_volume > 0:
            print(f"正在清仓: {stock_code}, 数量: {can_use_volume}")
            
            # 使用 order_target_value 将持仓市值调整为 0,即清仓
            # 注意:order_target_value 在实盘中通常以市价或最新价下单,具体取决于设置
            # 这里为了保险,也可以使用 passorder 指定价格卖出
            
            # 方式一:使用 order_target_value (简单)
            order_target_value(stock_code, 0, ContextInfo, ContextInfo.accID)
            
            # 方式二:使用 passorder (更可控,例如用对手价卖出)
            # opType: 24 (股票卖出), orderType: 1101 (单股单账号)
            # passorder(24, 1101, ContextInfo.accID, stock_code, 14, -1, can_use_volume, ContextInfo)
            
    print("清仓指令发送完毕。")

关键点说明

  1. get_trade_detail_data:

    • 这是实盘和模拟交易中获取资金和持仓的核心函数。
    • 参数 'account' 用于获取资金对象,其中 m_dBalance 代表总资产(现金+持仓市值)。
    • 参数 'position' 用于获取持仓对象列表,用于清仓时遍历。
  2. ContextInfo.is_stopped 标记:

    • 这是一个非常重要的风控锁。一旦触发止损,将其置为 True
    • 在策略的开仓逻辑前,必须检查 if not ContextInfo.is_stopped:,防止清仓后策略逻辑又自动买入。
  3. 清仓逻辑 (clear_all_positions):

    • 代码中使用 order_target_value(code, 0, ...) 是最简便的清仓方式,它会自动计算需要卖出的股数。
    • 注意股票交易的 T+1 规则,代码中使用了 m_nCanUseVolume(可用持仓)来判断可卖数量。如果是当日买入的股票,当日无法卖出(除非是回转交易品种如ETF或债券)。
  4. 回测与实盘的区别:

    • 上述代码主要针对实盘/模拟盘(Run模式)。
    • 如果是回测(Backtest模式),get_trade_detail_data 获取的是回测撮合系统的虚拟账户信息,逻辑是通用的。但在回测中,你也可以使用 ContextInfo.get_net_value(ContextInfo.barpos) 来获取回测引擎计算的净值,但在实盘中必须用 get_trade_detail_data。为了代码通用性,建议使用上述写法。

Q&A

Q: 如何在止损后恢复交易?
A: 在代码中,一旦 is_stopped 变为 True,策略就会停止交易。如果需要恢复,通常需要人工干预(重启策略)或者在代码中添加“恢复条件”(例如:当大盘指数站上20日均线后,将 is_stopped 重置为 False)。

Q: 这个逻辑是针对单只股票还是全账户?
A: 上述代码是针对全账户净值的。如果账户总资产从最高点回撤超过 10%,则清空账户内所有股票。如果是针对单只股票的止损,逻辑需要修改为监控单只股票的成本价和现价。

Q: m_dBalance 包含浮动盈亏吗?
A: 是的,m_dBalance 是动态权益,等于 可用资金 + 持仓市值,包含了持仓的浮动盈亏。