问题描述
解决方案
在 QMT 平台中,将策略回测的净值数据进行可视化绘图,最直接且推荐的方式是使用平台内置的 ContextInfo.paint() 函数。该函数可以将计算出的数值直接绘制在回测界面的 K 线图或副图上。
以下是具体的实现步骤和代码示例。
核心思路
- 获取净值:在
handlebar函数中,使用ContextInfo.get_net_value(index)获取当前 K 线位置的策略净值。 - 绘制曲线:使用
ContextInfo.paint()将获取到的净值画在图表上。
完整策略代码示例
你可以将以下代码复制到 QMT 的策略编辑器中进行回测。为了演示效果,该策略包含了一个简单的买入逻辑,以便净值发生变化。
# -*- coding: gbk -*-
def init(ContextInfo):
"""
初始化函数
"""
# 设置相关标的,这里以平安银行为例
ContextInfo.set_universe(['000001.SZ'])
# 设置回测初始资金
ContextInfo.capital = 1000000
# 设置手续费(可选,为了回测更真实)
# 佣金万三
ContextInfo.set_commission(0, [0.001, 0.001, 0.0003, 0.0003, 0, 5])
# 定义一个全局变量用于控制只买一次
ContextInfo.has_bought = False
def handlebar(ContextInfo):
"""
K线逐根运行函数
"""
# 获取当前K线索引
index = ContextInfo.barpos
# --- 简单的交易逻辑 (为了让净值产生波动) ---
# 获取当前主图代码
stock_code = ContextInfo.stockcode
# 如果没买过,且是第一根K线之后,全仓买入
if not ContextInfo.has_bought and index > 0:
# 获取最新价
last_price = ContextInfo.get_market_data(['close'], stock_code=[stock_code], count=1, period='1d')
if last_price is not None:
# 全仓买入
passorder(23, 1101, ContextInfo.accountid, stock_code, 5, -1, 1, ContextInfo)
ContextInfo.has_bought = True
print(f"在索引 {index} 处买入 {stock_code}")
# --- 核心:净值可视化绘图 ---
# 1. 获取当前回测节点的策略净值
# 注意:get_net_value 仅在回测模式下有效
net_value = ContextInfo.get_net_value(index)
# 2. 使用 paint 函数绘图
# 参数说明:
# name: 指标名称,显示在左上角
# value: 要绘制的数值
# index: 位置索引,-1 表示当前位置
# line_style: 线型,0 为曲线
# color: 颜色 (可选)
# limit: 坐标轴控制 (可选)
ContextInfo.paint('策略净值', net_value, -1, 0, 'red')
# 3. (可选) 绘制基准净值进行对比
# 这里的基准通常是 1.0 开始,具体取决于回测设置的基准
# ContextInfo.paint('基准线', 1.0, -1, 0, 'blue')
关键函数详解
1. ContextInfo.get_net_value(index)
- 作用:获取策略在指定 K 线索引位置的单位净值(初始值为 1.0)。
- 参数:
index,通常传入ContextInfo.barpos。 - 注意:此函数仅在回测模式下返回有效数据。
2. ContextInfo.paint(name, value, index, line_style, color, limit)
- 作用:在图表上绘制指标线。
- 参数:
name(string): 指标名称,例如'策略净值'。value(number): 当前要画的数值。index(number): 填-1即可,表示跟随主图 K 线索引。line_style(number):0代表曲线,42代表柱状线。color(string): 颜色,如'red','blue','yellow'等。limit(string):- 如果不填,QMT 会尝试将线条和 K 线画在同一个坐标系中。由于净值通常是 1.0 附近,而股价可能是几十几百,这会导致图形压缩。
- 建议操作:在运行策略时,在 QMT 界面上选择**“副图”**显示,或者在代码中不加
limit参数,但在界面上右键点击主图 -> 选择“叠加指标”或“副图指标”来查看。
如何在 QMT 界面查看结果
- 编译并运行:点击策略编辑器的“回测”按钮。
- 查看位置:
- 如果策略设置中“位置”选的是**“副图”**,净值曲线会直接显示在 K 线图下方的副图区域。
- 如果选的是**“主图”,由于数值差异巨大(净值约 1.0 vs 股价),建议在图表上右键** -> 叠加指标 -> 选择你的策略,或者将策略属性改为副图显示。
进阶:使用 Matplotlib 绘图(非实时)
如果你希望在回测结束后弹出一个独立的 Python 图表,可以使用 matplotlib 库。
# -*- coding: gbk -*-
import matplotlib.pyplot as plt
def init(ContextInfo):
ContextInfo.net_values = [] # 用于存储净值序列
ContextInfo.dates = [] # 用于存储日期序列
ContextInfo.set_universe(['000001.SZ'])
ContextInfo.capital = 1000000
def handlebar(ContextInfo):
index = ContextInfo.barpos
# 记录数据
nv = ContextInfo.get_net_value(index)
current_time = ContextInfo.get_bar_timetag(index)
ContextInfo.net_values.append(nv)
ContextInfo.dates.append(current_time)
# 简单的买入逻辑...
if index == 0:
passorder(23, 1101, ContextInfo.accountid, ContextInfo.stockcode, 5, -1, 1, ContextInfo)
# 判断是否是最后一根K线,如果是,则画图
if ContextInfo.is_last_bar():
plt.figure(figsize=(10, 6))
plt.plot(ContextInfo.net_values, label='Net Value', color='red')
plt.title('Strategy Backtest Net Value')
plt.xlabel('Bar Index')
plt.ylabel('Net Value')
plt.legend()
plt.grid(True)
plt.show()
Q&A
Q: 为什么我的 paint 画出来的线是一条直线?
A: 可能是因为策略没有进行任何交易,或者交易没有成交,导致账户资金没有变化,净值一直维持在 1.0。请检查 passorder 是否被调用以及是否成交。
Q: paint 函数能画在独立的窗口吗?
A: paint 函数是绑定在 QMT 行情图表上的。如果需要独立窗口,请使用上面提到的 matplotlib 方法。
Q: 回测时 get_net_value 返回的净值包含浮动盈亏吗?
A: 是的,get_net_value 计算的是包含持仓浮动盈亏和现金在内的总资产净值。