🚀 AI 一键生成 ptrade 策略代码

PTrade 策略如何实现动态止损?详解投资组合最大回撤控制

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

问题描述

如何在策略中加入风控逻辑,当账户最大回撤超过10%时自动清仓?

解决方案

在PTrade策略中实现“最大回撤超过10%自动清仓”的风控逻辑,核心在于实时记录账户的历史最高净值,并计算当前净值相对于最高净值的回撤幅度。

以下是实现该逻辑的完整代码示例及详细说明。

核心逻辑说明

  1. 记录最高权益 (g.max_portfolio_value)
    initialize 中初始化该变量。在 handle_data(或 after_trading_end)中,每次运行都比较当前账户总资产 context.portfolio.portfolio_value 与历史最高值,如果当前值更高,则更新最高值。

  2. 计算回撤 (drawdown)
    回撤公式为:$回撤 = 1 - (当前总资产 / 历史最高总资产)$。

  3. 触发风控与清仓
    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)

代码关键点解析

  1. g.max_portfolio_value 的维护
    这个变量必须是全局变量(g.开头),这样它才能在策略运行的每一天或每一分钟保持状态。在回测开始时,它会随着净值增长而不断更新。

  2. g.stop_trading 标志位
    这是一个非常重要的设计。一旦清仓,通常意味着策略失效或市场环境极端恶劣。如果不加这个锁,策略可能会在清仓后的下一个时刻(例如下一分钟)因为满足了原本的买入条件而再次开仓,导致风控失效。

  3. order_target(stock, 0)
    这是PTrade中最方便的平仓函数。无论当前持仓是多少股,该函数都会自动计算需要卖出的数量,将持仓调整为0。

  4. 兼容性
    代码使用了 % 格式化字符串(如 "%.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(随后在第一次运行被更新为当前资产)。这意味着重启后的回撤计算是基于重启时的资产作为起点的。如果需要基于历史最高点,需要结合持久化存储功能。