问题描述
解决方案
在 QMT (Quantitative Trading Platform) 的回测模式中,计算和展示自定义策略表现指标主要通过以下两个步骤实现:
- 计算指标:在
handlebar(ContextInfo)函数中,利用ContextInfo获取账户权益、持仓信息或行情数据,通过 Python 代码计算出你需要的指标(如自定义的动量、策略净值变化率、风险敞口等)。 - 展示指标:使用
ContextInfo.paint()函数将计算出的数值绘制在回测界面的 K 线图(主图或副图)上。
下面我将详细介绍相关 API,并提供一个完整的代码示例。
核心 API 说明
1. ContextInfo.paint()
这是最关键的绘图函数,用于在图表上输出线条或柱状图。
- 用法:
ContextInfo.paint(name, value, index, line_style, color, limit) - 参数:
name(str): 指标名称(显示在图表左上角)。value(number): 当前 K 线对应的指标数值。index(number): 位置索引,通常填-1表示跟随主图 K 线位置。line_style(number): 线型,0为曲线,42为柱状线。color(str): 颜色(如 'red', 'blue', 'yellow')。limit(str): 控制显示位置。'noaxis': 不影响坐标轴缩放(通常用于主图叠加)。- 如果不填或填空字符串,且数值与股价差异较大(如净值、百分比),QMT 会自动将其绘制在副图(Sub-chart)区域。
2. ContextInfo.get_net_value(index)
获取策略在指定 K 线位置的单位净值。这是计算策略收益率、回撤等衍生指标的基础。
完整代码示例
以下代码演示了一个简单的双均线策略。除了基本的交易逻辑外,它还计算并展示了两个自定义指标:
- 策略净值 (Strategy_NV):展示在副图上。
- 自定义乖离率 (My_Bias):计算收盘价与均线的偏离程度,展示在副图上。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
# 1. 设置基本参数
ContextInfo.stock = '600000.SH'
ContextInfo.set_universe([ContextInfo.stock])
ContextInfo.set_account('account_id') # 回测模式下账号ID可任意填写
# 设置回测参数(也可以在界面设置,这里代码设置优先级更高)
ContextInfo.set_commission(0, [0.0001, 0.0001, 0.0003, 0.0003, 0, 5]) # 设置费率
ContextInfo.capital = 1000000 # 初始资金
# 2. 定义策略参数
ContextInfo.short_period = 5
ContextInfo.long_period = 10
def handlebar(ContextInfo):
# 获取当前 K 线索引
index = ContextInfo.barpos
# 获取当前主图品种
stock = ContextInfo.stock
# 1. 获取历史行情数据 (取足够长的数据以计算均线)
# 注意:get_market_data_ex 返回的是 dict {code: dataframe}
data_len = ContextInfo.long_period + 2
market_data = ContextInfo.get_market_data_ex(
['close'],
[stock],
period=ContextInfo.period,
count=data_len,
dividend_type='front'
)
if stock not in market_data or len(market_data[stock]) < data_len:
return
# 获取收盘价序列
close_list = market_data[stock]['close']
current_price = close_list.iloc[-1]
# 2. 计算技术指标 (均线)
ma_short = close_list.rolling(window=ContextInfo.short_period).mean().iloc[-1]
ma_long = close_list.rolling(window=ContextInfo.long_period).mean().iloc[-1]
# 3. 交易逻辑 (简单的金叉死叉)
# 获取当前持仓
positions = ContextInfo.get_trade_detail_data('account_id', 'stock', 'position')
current_vol = 0
for pos in positions:
if pos.m_strInstrumentID == stock:
current_vol = pos.m_nVolume
break
# 金叉买入
if ma_short > ma_long and current_vol == 0:
ContextInfo.paint("Buy_Signal", 1, -1, 0, "red", "noaxis") # 在主图标记买点
order_target_percent(stock, 0.8, "fix", current_price, ContextInfo, 'account_id')
print(f"买入: {stock}, 价格: {current_price}")
# 死叉卖出
elif ma_short < ma_long and current_vol > 0:
ContextInfo.paint("Sell_Signal", 1, -1, 0, "green", "noaxis") # 在主图标记卖点
order_target_percent(stock, 0, "fix", current_price, ContextInfo, 'account_id')
print(f"卖出: {stock}, 价格: {current_price}")
# ============================================================
# 4. 计算并展示自定义指标
# ============================================================
# --- 指标 A: 策略实时净值 ---
# get_net_value 获取的是回测引擎计算出的当前净值
net_val = ContextInfo.get_net_value(index)
# 使用 paint 绘制。
# 因为净值(如 1.05)与股价(如 10.00)坐标系不同,
# 不传 limit 参数,QMT 会自动将其放入副图显示。
ContextInfo.paint("Strategy_NV", net_val, -1, 0, "red")
# --- 指标 B: 自定义乖离率 (Bias) ---
# 计算公式:(收盘价 - 长期均线) / 长期均线 * 100
if ma_long != 0:
my_bias = (current_price - ma_long) / ma_long * 100
else:
my_bias = 0
# 绘制乖离率,使用柱状图 (line_style=42),颜色为黄色
ContextInfo.paint("My_Bias", my_bias, -1, 42, "yellow")
# --- 指标 C: 绘制均线到主图 (叠加) ---
# 传入 'noaxis' 确保它与 K 线共用一个坐标轴
ContextInfo.paint("MA_Short", ma_short, -1, 0, "white", "noaxis")
ContextInfo.paint("MA_Long", ma_long, -1, 0, "cyan", "noaxis")
关键点总结
-
主图叠加 vs 副图显示:
- 如果你希望指标显示在 K 线图上(如均线、布林带),在
paint的最后一个参数传入'noaxis'。 - 如果你希望指标独立显示在下方的副图区域(如 MACD、策略净值、持仓仓位),不要传入
'noaxis'(留空字符串""或不传该参数)。QMT 会检测数值范围,如果与股价差异大,会自动开辟副图。
- 如果你希望指标显示在 K 线图上(如均线、布林带),在
-
数据获取:
ContextInfo.get_net_value(index)是获取回测绩效最直接的方法。- 如果需要更复杂的账户数据(如当前可用资金、持仓市值),可以使用
ContextInfo.get_trade_detail_data获取后自行计算。
-
调试输出:
- 除了画图,你也可以使用
print()函数将指标数值输出到下方的“日志输出”面板,方便进行数值核对。
- 除了画图,你也可以使用
Q&A
Q: 如何在回测结束后导出这些自定义指标的数据?
A: paint 函数仅用于界面展示。如果你需要导出数据,建议在 handlebar 中将计算好的指标存入一个全局的 list 或 pandas.DataFrame(存储在 ContextInfo 对象中),然后在回测结束时(虽然 QMT Python API 没有显式的 on_stop 回测结束回调,但你可以判断 ContextInfo.is_last_bar())将数据保存为 CSV 文件。
Q: 为什么我的 paint 指标没有显示?
A: 请检查以下几点:
- 是否在
handlebar中被return语句提前跳过了(例如数据不足时)。 index参数是否设为了-1。- 如果是主图指标,数值是否和股价在同一个数量级?如果数值是 0.01 而股价是 100,且使用了
'noaxis',线条会压在底部看不见。
Q: 可以自定义副图的名称吗?
A: QMT 的 paint 函数会自动管理副图。通常第一个非主图指标会占据一个副图,后续指标如果数值范围相近会叠加,否则可能开启新副图。无法像通达信那样显式命名“副图1”、“副图2”。