🚀 AI 一键生成 joinquant 策略代码

JoinQuant 图表定制:如何在回测中实时绘制最大回撤曲线?

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

问题描述

怎么在回测图中画出动态的最大回撤曲线?

解决方案

在聚宽(JoinQuant)回测中画出动态最大回撤曲线,核心思路是在策略运行过程中,实时记录账户权益的历史最高值(High Water Mark),计算当前权益相对于历史最高值的跌幅,并使用 record() 函数将该数值绘制在回测图表中。

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

实现原理

  1. 初始化全局变量:在 initialize 中定义一个全局变量(如 g.high_water_mark)来存储账户的历史最高权益。
  2. 实时更新最高权益:在每一个交易单位时间(如 handle_data 或定时运行的函数中),获取当前账户总资产 context.portfolio.total_value,并与 g.high_water_mark 比较,如果当前资产更高,则更新该变量。
  3. 计算回撤:使用公式 (当前权益 - 历史最高权益) / 历史最高权益 计算当前的回撤比例。
  4. 绘图:调用 record(Key=Value) 函数将计算出的回撤值绘制到图表上。

完整策略代码

你可以将以下代码直接复制到聚宽的策略编辑框中运行。为了演示效果,代码中包含了一个简单的买入逻辑以使账户资金发生波动。

# -*- coding: utf-8 -*-
import jqdata

def initialize(context):
    # 设置基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    
    # 设置手续费
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # 定义一个全局变量,用于存储历史最高权益,初始设为0
    g.high_water_mark = 0
    
    # 要操作的股票
    g.security = '000001.XSHE'
    
    # 每天收盘前运行一次,进行回撤计算和绘图
    # 注意:record函数建议在盘中或收盘前调用,16:00之后调用会算作第二天
    run_daily(record_drawdown_curve, '14:50')

def handle_data(context, data):
    # --- 简单的交易逻辑,仅为了让净值波动 ---
    # 如果没有持仓,就全仓买入
    if context.portfolio.positions[g.security].total_amount == 0:
        order_value(g.security, context.portfolio.available_cash)

def record_drawdown_curve(context):
    """
    计算并绘制最大回撤曲线
    """
    # 1. 获取当前账户总资产 (现金 + 持仓价值)
    current_value = context.portfolio.total_value
    
    # 2. 更新历史最高权益 (High Water Mark)
    # 如果是第一天或者创新高,则更新 g.high_water_mark
    if current_value > g.high_water_mark:
        g.high_water_mark = current_value
        
    # 3. 计算当前的回撤幅度
    # 公式:(当前净值 - 历史最高净值) / 历史最高净值
    # 结果通常为负数或0,例如 -0.10 代表回撤 10%
    if g.high_water_mark > 0:
        current_drawdown = (current_value - g.high_water_mark) / g.high_water_mark
    else:
        current_drawdown = 0
        
    # 4. 使用 record 函数绘图
    # 'Dynamic_MDD' 是图例名称,可以自定义
    record(Dynamic_MDD=current_drawdown)

代码关键点解析

  1. g.high_water_mark:
    这是维护最大回撤计算的核心变量。它像水位线一样,只升不降。只有当账户总资产创出新高时,它才会更新。

  2. current_drawdown 计算:
    回撤通常表示为负数(例如 -0.05 表示回撤 5%)。如果当前资产等于历史最高资产,回撤为 0。

  3. record(Dynamic_MDD=current_drawdown):

    • record 函数会在回测结果页面的收益率曲线下方生成一个新的坐标轴区域(或者在同一区域显示,取决于UI版本),画出你传入的数据。
    • Dynamic_MDD 是你在图表中看到的曲线名称,你可以将其修改为任何你喜欢的名字(如 Drawdown)。

运行效果

运行回测后,在结果页面的图表区域,你会看到一条名为 Dynamic_MDD 的曲线。该曲线大部分时间在 0 以下波动,数值越小(越负),代表当前距离历史最高点的回撤越大。


Q&A

Q: 为什么我的回撤曲线一直是0?
A: 这通常是因为策略没有进行交易,或者一直处于盈利创新高的状态(虽然这种情况很少见)。请检查 context.portfolio.total_value 是否在变化。另外,确保 g.high_water_mark 初始化逻辑正确。

Q: record 函数可以画多条线吗?
A: 可以。你可以同时传入多个参数,例如 record(MDD=current_drawdown, Leverage=context.portfolio.positions_value / context.portfolio.total_value),这样可以在图中同时画出回撤和杠杆率。

Q: 这个动态回撤和回测报告里的“最大回撤”指标有什么区别?
A: 回测报告里的“最大回撤”是一个单一的统计数值,表示整个回测期间发生过的最糟糕的情况。而这里画出的“动态回撤曲线”是一个时间序列,展示了每一天相对于当时历史最高点的回撤情况,能让你看到回撤发生的具体时间段和恢复过程。