问题描述
解决方案
在聚宽(JoinQuant)回测中画出动态最大回撤曲线,核心思路是在策略运行过程中,实时记录账户权益的历史最高值(High Water Mark),计算当前权益相对于历史最高值的跌幅,并使用 record() 函数将该数值绘制在回测图表中。
以下是具体的实现步骤和完整的代码示例。
实现原理
- 初始化全局变量:在
initialize中定义一个全局变量(如g.high_water_mark)来存储账户的历史最高权益。 - 实时更新最高权益:在每一个交易单位时间(如
handle_data或定时运行的函数中),获取当前账户总资产context.portfolio.total_value,并与g.high_water_mark比较,如果当前资产更高,则更新该变量。 - 计算回撤:使用公式
(当前权益 - 历史最高权益) / 历史最高权益计算当前的回撤比例。 - 绘图:调用
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)
代码关键点解析
-
g.high_water_mark:
这是维护最大回撤计算的核心变量。它像水位线一样,只升不降。只有当账户总资产创出新高时,它才会更新。 -
current_drawdown计算:
回撤通常表示为负数(例如 -0.05 表示回撤 5%)。如果当前资产等于历史最高资产,回撤为 0。 -
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: 回测报告里的“最大回撤”是一个单一的统计数值,表示整个回测期间发生过的最糟糕的情况。而这里画出的“动态回撤曲线”是一个时间序列,展示了每一天相对于当时历史最高点的回撤情况,能让你看到回撤发生的具体时间段和恢复过程。