问题描述
解决方案
在PTrade策略中实现“最大回撤超过10%自动清仓”的风控逻辑,核心在于实时记录账户的历史最高净值,并计算当前净值相对于最高净值的回撤幅度。
以下是实现该逻辑的完整代码示例及详细说明。
核心逻辑说明
-
记录最高权益 (
g.max_portfolio_value):
在initialize中初始化该变量。在handle_data(或after_trading_end)中,每次运行都比较当前账户总资产context.portfolio.portfolio_value与历史最高值,如果当前值更高,则更新最高值。 -
计算回撤 (
drawdown):
回撤公式为:$回撤 = 1 - (当前总资产 / 历史最高总资产)$。 -
触发风控与清仓:
当drawdown > 0.1(10%) 时:- 遍历当前持仓
context.portfolio.positions。 - 使用
order_target(stock, 0)将所有持仓调整为0(即卖出)。 - 设置一个标志位(如
g.stop_trading),防止策略在清仓后继续开新仓。
- 遍历当前持仓
策略代码实现
def initialize(context):
"""
初始化函数
"""
# 设置示例股票池(这里仅作演示,实际使用请替换为您需要的标的)
g.security = ['600570.SS', '000001.SZ']
set_universe(g.security)
# --- 风控相关全局变量初始化 ---
# 记录账户历史最高净值,初始化为0
g.max_portfolio_value = 0
# 停止交易标志位,触发风控后置为True
g.stop_trading = False
# 设定最大回撤阈值 (10%)
g.drawdown_limit = 0.10
def handle_data(context, data):
"""
盘中运行函数
"""
# 1. 如果触发了风控停止交易,则不再执行后续策略逻辑
if g.stop_trading:
log.info("风控已触发,策略停止运行,当前为空仓状态。")
return
# 2. 获取当前账户总资产 (现金 + 持仓市值)
current_value = context.portfolio.portfolio_value
# 3. 更新历史最高净值
# 如果是刚开始运行,或者当前净值创新高,则更新
if current_value > g.max_portfolio_value:
g.max_portfolio_value = current_value
# 4. 计算当前回撤
# 避免除以0的异常情况
if g.max_portfolio_value > 0:
drawdown = 1 - (current_value / g.max_portfolio_value)
else:
drawdown = 0
# 打印当前回撤情况(可选,用于调试)
# log.info("当前净值: %.2f, 历史最高: %.2f, 当前回撤: %.2f%%" % (current_value, g.max_portfolio_value, drawdown * 100))
# 5. 检查是否超过最大回撤阈值
if drawdown > g.drawdown_limit:
log.error("【风控触发】当前回撤 %.2f%% 超过设定阈值 %.2f%%,开始执行清仓..." % (drawdown * 100, g.drawdown_limit * 100))
# 执行清仓操作
clear_all_positions(context)
# 标记停止交易
g.stop_trading = True
return
# --- 以下是您的正常交易策略逻辑 ---
# 示例:简单的买入逻辑,仅当未触发风控时执行
if not g.stop_trading:
for stock in g.security:
# 简单的示例:如果没持仓就买入
if get_position(stock).amount == 0:
order_target_value(stock, 10000)
def clear_all_positions(context):
"""
自定义函数:清空所有持仓
"""
# 获取当前所有持仓的标的代码
positions = context.portfolio.positions.keys()
for stock in positions:
# 获取该标的的持仓信息
pos = context.portfolio.positions[stock]
# 如果有持仓(多头或空头),则平仓
if pos.amount != 0:
# order_target 0 表示将仓位调整为0,即全卖
order_target(stock, 0)
log.info("正在清仓卖出: %s" % stock)
代码关键点解析
-
g.max_portfolio_value的维护:
这个变量必须是全局变量(g.开头),这样它才能在策略运行的每一天或每一分钟保持状态。在回测开始时,它会随着净值增长而不断更新。 -
g.stop_trading标志位:
这是一个非常重要的设计。一旦清仓,通常意味着策略失效或市场环境极端恶劣。如果不加这个锁,策略可能会在清仓后的下一个时刻(例如下一分钟)因为满足了原本的买入条件而再次开仓,导致风控失效。 -
order_target(stock, 0):
这是PTrade中最方便的平仓函数。无论当前持仓是多少股,该函数都会自动计算需要卖出的数量,将持仓调整为0。 -
兼容性:
代码使用了%格式化字符串(如"%.2f" % value),这是为了确保在Python 3.5及以上版本中都能稳定运行,符合PTrade环境的要求。
常见问题 (Q&A)
Q: 这个风控逻辑是盘中实时触发还是收盘后触发?
A: 取决于您的策略频率。如果您选择的是分钟级别回测/交易,handle_data 每分钟运行一次,那么风控是盘中实时触发的。如果您选择的是日线级别,则是在每日收盘前的特定时间点(如14:50或15:00)触发。
Q: 清仓后如何恢复交易?
A: 在上述代码中,一旦 g.stop_trading 变为 True,策略将永久停止开仓。如果您希望在回撤恢复(例如净值重新回到一定水平)后恢复交易,需要修改逻辑,在 handle_data 中增加重置 g.stop_trading = False 的条件判断。
Q: 为什么使用 context.portfolio.portfolio_value 而不是 cash?
A: cash 仅代表可用资金。回撤是基于总资产(Total Equity)计算的,总资产 = 可用资金 + 持仓市值。如果只监控现金,无法反映持仓股票下跌带来的亏损。
Q: 实盘中重启策略会发生什么?
A: 在实盘中,如果策略重启,initialize 会重新运行。如果未做持久化处理(使用 pickle 保存 g 变量),g.max_portfolio_value 会重置为0(随后在第一次运行被更新为当前资产)。这意味着重启后的回撤计算是基于重启时的资产作为起点的。如果需要基于历史最高点,需要结合持久化存储功能。