🚀 AI 一键生成 qmt 策略代码

QMT 回测教程:如何计算并展示自定义的绩效指标?

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

问题描述

怎样在回测中计算和展示自定义的策略表现指标?

解决方案

在 QMT (Quantitative Trading Platform) 的回测模式中,计算和展示自定义策略表现指标主要通过以下两个步骤实现:

  1. 计算指标:在 handlebar(ContextInfo) 函数中,利用 ContextInfo 获取账户权益、持仓信息或行情数据,通过 Python 代码计算出你需要的指标(如自定义的动量、策略净值变化率、风险敞口等)。
  2. 展示指标:使用 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 线位置的单位净值。这是计算策略收益率、回撤等衍生指标的基础。

完整代码示例

以下代码演示了一个简单的双均线策略。除了基本的交易逻辑外,它还计算并展示了两个自定义指标:

  1. 策略净值 (Strategy_NV):展示在副图上。
  2. 自定义乖离率 (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")

关键点总结

  1. 主图叠加 vs 副图显示

    • 如果你希望指标显示在 K 线图上(如均线、布林带),在 paint 的最后一个参数传入 'noaxis'
    • 如果你希望指标独立显示在下方的副图区域(如 MACD、策略净值、持仓仓位),不要传入 'noaxis'(留空字符串 "" 或不传该参数)。QMT 会检测数值范围,如果与股价差异大,会自动开辟副图。
  2. 数据获取

    • ContextInfo.get_net_value(index) 是获取回测绩效最直接的方法。
    • 如果需要更复杂的账户数据(如当前可用资金、持仓市值),可以使用 ContextInfo.get_trade_detail_data 获取后自行计算。
  3. 调试输出

    • 除了画图,你也可以使用 print() 函数将指标数值输出到下方的“日志输出”面板,方便进行数值核对。

Q&A

Q: 如何在回测结束后导出这些自定义指标的数据?
A: paint 函数仅用于界面展示。如果你需要导出数据,建议在 handlebar 中将计算好的指标存入一个全局的 listpandas.DataFrame(存储在 ContextInfo 对象中),然后在回测结束时(虽然 QMT Python API 没有显式的 on_stop 回测结束回调,但你可以判断 ContextInfo.is_last_bar())将数据保存为 CSV 文件。

Q: 为什么我的 paint 指标没有显示?
A: 请检查以下几点:

  1. 是否在 handlebar 中被 return 语句提前跳过了(例如数据不足时)。
  2. index 参数是否设为了 -1
  3. 如果是主图指标,数值是否和股价在同一个数量级?如果数值是 0.01 而股价是 100,且使用了 'noaxis',线条会压在底部看不见。

Q: 可以自定义副图的名称吗?
A: QMT 的 paint 函数会自动管理副图。通常第一个非主图指标会占据一个副图,后续指标如果数值范围相近会叠加,否则可能开启新副图。无法像通达信那样显式命名“副图1”、“副图2”。