问题描述
解决方案
在 QMT 策略中实现基于净值回撤的自动止损,核心逻辑是实时监控账户的总资产(净值),记录历史最高净值,并计算当前净值相对于最高净值的回撤幅度。一旦回撤超过设定的阈值,立即清空所有持仓并停止开新仓。
以下是具体的实现步骤和代码示例。
实现逻辑
-
初始化 (
init):- 设定止损阈值(例如 10%)。
- 初始化全局变量用于记录“历史最高净值”和“是否触发止损”的标记。
- 设置交易账号。
-
盘中运行 (
handlebar):- 获取当前账户的总资产(
m_dBalance)。 - 更新历史最高净值:如果当前净值 > 历史最高,则更新。
- 计算回撤:$回撤率 = 1 - \frac{当前净值}{历史最高净值}$。
- 判断止损:
- 如果
回撤率 >= 阈值且未触发过止损:- 将止损标记设为
True。 - 执行清仓操作(遍历持仓并卖出)。
- 打印日志告警。
- 将止损标记设为
- 如果
- 策略控制: 如果止损标记为
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("清仓指令发送完毕。")
关键点说明
-
get_trade_detail_data:- 这是实盘和模拟交易中获取资金和持仓的核心函数。
- 参数
'account'用于获取资金对象,其中m_dBalance代表总资产(现金+持仓市值)。 - 参数
'position'用于获取持仓对象列表,用于清仓时遍历。
-
ContextInfo.is_stopped标记:- 这是一个非常重要的风控锁。一旦触发止损,将其置为
True。 - 在策略的开仓逻辑前,必须检查
if not ContextInfo.is_stopped:,防止清仓后策略逻辑又自动买入。
- 这是一个非常重要的风控锁。一旦触发止损,将其置为
-
清仓逻辑 (
clear_all_positions):- 代码中使用
order_target_value(code, 0, ...)是最简便的清仓方式,它会自动计算需要卖出的股数。 - 注意股票交易的 T+1 规则,代码中使用了
m_nCanUseVolume(可用持仓)来判断可卖数量。如果是当日买入的股票,当日无法卖出(除非是回转交易品种如ETF或债券)。
- 代码中使用
-
回测与实盘的区别:
- 上述代码主要针对实盘/模拟盘(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 是动态权益,等于 可用资金 + 持仓市值,包含了持仓的浮动盈亏。