🚀 AI 一键生成 qmt 策略代码

如何对 QMT 策略的回测结果进行自定义的深度分析?

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

问题描述

怎样才能把回测的每一笔交易都导出来,然后自己计算一些比如盈亏比、最大连亏次数之类的指标?

解决方案

在 QMT 中,要导出回测的每一笔交易明细,最标准的方法是利用策略生命周期中的 stop(ContextInfo) 函数。

stop() 函数会在回测结束时自动运行一次。此时,我们可以调用 get_trade_detail_data 获取整个回测期间产生的所有成交记录,将其转换为 DataFrame 并导出为 CSV 文件。

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

核心思路

  1. 策略运行:在 handlebar 中正常进行买卖操作。
  2. 回测结束:在 stop 函数中,使用 get_trade_detail_data(..., 'DEAL') 获取所有成交单对象。
  3. 数据解析:遍历成交对象列表,提取日期、时间、代码、方向、价格、数量、手续费等关键字段。
  4. 数据导出:使用 pandas 库将数据保存为 CSV 文件,方便后续在 Excel 或 Python 中计算盈亏比、最大连亏等指标。

完整代码示例

你可以将以下代码复制到 QMT 的 Python 编辑器中进行回测。代码包含了一个简单的均线策略用于生成交易数据,以及核心的导出逻辑。

# -*- coding: gbk -*-
import pandas as pd
import os

def init(ContextInfo):
    # 1. 设置账号信息 (回测模式下账号ID通常可以随意填,或者使用默认)
    ContextInfo.accID = '6000000000' 
    ContextInfo.accountType = 'STOCK'
    ContextInfo.set_account(ContextInfo.accID)
    
    # 2. 策略参数设置 (示例:简单的均线策略)
    ContextInfo.stock = '600000.SH'
    ContextInfo.set_universe([ContextInfo.stock])
    ContextInfo.period = 5  # 5日均线
    
    # 3. 设置导出路径 (请确保D盘存在,或者修改为你电脑上存在的路径)
    ContextInfo.export_path = 'D:/qmt_backtest_records.csv'

def handlebar(ContextInfo):
    # --- 示例策略逻辑:为了生成交易记录 ---
    # 获取历史收盘价
    closes = ContextInfo.get_history_data(ContextInfo.period + 1, '1d', 'close')
    if ContextInfo.stock not in closes:
        return
    close_list = closes[ContextInfo.stock]
    
    if len(close_list) < ContextInfo.period:
        return
        
    ma5 = sum(close_list[-5:]) / 5
    current_price = close_list[-1]
    
    # 获取当前持仓
    positions = get_trade_detail_data(ContextInfo.accID, ContextInfo.accountType, 'POSITION')
    curr_vol = 0
    for pos in positions:
        if pos.m_strInstrumentID == ContextInfo.stock:
            curr_vol = pos.m_nVolume
            break
            
    # 简单的交易逻辑
    if current_price > ma5 and curr_vol == 0:
        # 均线上方买入
        order_shares(ContextInfo.stock, 1000, 'fix', current_price, ContextInfo, ContextInfo.accID)
    elif current_price < ma5 and curr_vol > 0:
        # 均线下方卖出
        order_shares(ContextInfo.stock, -curr_vol, 'fix', current_price, ContextInfo, ContextInfo.accID)

def stop(ContextInfo):
    """
    回测结束时触发,用于导出交易记录
    """
    print("回测结束,开始导出交易记录...")
    
    # 1. 获取所有成交记录 ('DEAL')
    # 注意:这里获取的是回测期间产生的所有成交明细
    deal_list = get_trade_detail_data(ContextInfo.accID, ContextInfo.accountType, 'DEAL')
    
    if not deal_list:
        print("本次回测无成交记录。")
        return

    # 2. 解析成交对象属性
    data = []
    for deal in deal_list:
        # 解析买卖方向:48代表买入,49代表卖出 (参考API文档附录)
        direction = "买入" if deal.m_nDirection == 48 else "卖出"
        
        record = {
            "日期": deal.m_strTradeDate,
            "时间": deal.m_strTradeTime,
            "代码": deal.m_strInstrumentID,
            "名称": deal.m_strInstrumentName,
            "方向": direction,
            "成交均价": deal.m_dPrice,
            "成交数量": deal.m_nVolume,
            "成交金额": deal.m_dTradeAmount,
            "手续费": deal.m_dComssion,
            "平仓盈亏": deal.m_dCloseProfit, # 注意:股票回测中此字段可能不准确,通常需自己计算
            "备注": deal.m_strRemark
        }
        data.append(record)
    
    # 3. 转换为DataFrame并导出
    df = pd.DataFrame(data)
    
    # 按日期和时间排序
    df = df.sort_values(by=['日期', '时间'])
    
    try:
        # 导出为CSV,使用gbk编码防止中文乱码
        df.to_csv(ContextInfo.export_path, index=False, encoding='gbk')
        print(f"交易记录已成功导出至: {ContextInfo.export_path}")
        print(f"共导出 {len(df)} 条成交记录")
    except Exception as e:
        print(f"导出失败: {e}")
        print("请检查路径是否存在或文件是否被占用。")

关键点说明

  1. get_trade_detail_data(..., 'DEAL'):

    • 这是核心函数。参数 'DEAL' 表示获取成交明细。
    • 返回的是一个对象列表,每个对象包含该笔成交的详细信息。
  2. 对象属性解析:

    • m_strTradeDate / m_strTradeTime: 交易时间。
    • m_nDirection: 买卖方向。QMT底层使用枚举值,48 代表买入 (ENTRUST_BUY)49 代表卖出 (ENTRUST_SELL)
    • m_dPrice: 成交价格。
    • m_nVolume: 成交数量。
    • m_dComssion: 手续费。
  3. 关于盈亏计算:

    • 虽然成交对象中有 m_dCloseProfit (平仓盈亏) 属性,但在股票回测模式下,该字段有时可能不会自动计算或计算逻辑与预期不同(它在期货回测中更准确)。
    • 建议:导出上述 CSV 后,在 Excel 或 Python 中通过“先进先出”或“平均成本法”自行匹配买单和卖单来计算每一笔完整交易(Round-trip)的盈亏。

如何在外部计算指标(思路)

导出 CSV 后,你可以使用 Python (Pandas) 进行如下分析:

  1. 计算单笔盈亏:将“卖出金额 - 对应买入成本 - 手续费”得到净盈亏。
  2. 盈亏比总盈利金额 / abs(总亏损金额)平均盈利金额 / abs(平均亏损金额)
  3. 最大连亏次数
    • 按时间排序交易。
    • 标记每笔交易是盈是亏(1为盈,-1为亏)。
    • 计算连续出现的 -1 的最大长度。

Q&A

Q: 为什么我导出的 CSV 是乱码?
A: 代码中使用了 encoding='gbk',这是为了适配 Excel 打开中文 CSV 的默认编码。如果你使用其他软件(如 Mac 上的 Numbers 或某些文本编辑器),可能需要将其改为 encoding='utf-8-sig'

Q: stop 函数没有执行怎么办?
A: 确保你的回测正常跑完了全程。如果中途手动停止或报错中断,stop 函数可能不会被触发。

Q: 能否导出委托记录(包括未成交的)?
A: 可以。将 get_trade_detail_data 的第三个参数改为 'ORDER' 即可获取所有委托单,包括已撤单和废单。