🚀 AI 一键生成 qmt 策略代码

如何使用 QMT API 将历史行情和回测数据导出到 Excel 文件?

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

问题描述

怎样把程序化交易的历史行情数据或者回测报告里的持仓明细导出成一个Excel文件?

解决方案

在QMT中,导出数据到Excel主要依赖于Python强大的第三方库 pandas。QMT内置了 pandasopenpyxl (或 xlwt),因此你可以直接将数据整理成 DataFrame 格式,然后调用 .to_excel() 方法保存。

以下提供两个具体的代码示例:

  1. 导出历史行情数据:获取指定股票的历史K线数据并保存。
  2. 导出回测持仓明细:在策略回测结束时,自动将最终持仓导出。

场景一:导出历史行情数据

这段代码会在策略初始化 (init) 时运行,获取指定股票、指定时间段的日线数据,并保存到电脑的指定路径。

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

def init(ContextInfo):
    # 1. 设置参数
    stock_list = ['600000.SH', '000001.SZ'] # 需要导出的股票列表
    start_date = '20230101'                 # 开始时间
    end_date = '20231231'                   # 结束时间
    period = '1d'                           # 周期:1d, 1m, 5m 等
    save_path = 'D:/qmt_data_export.xlsx'   # Excel保存路径 (请确保D盘存在,或修改为其他路径)

    print("开始获取数据...")

    # 2. 获取行情数据 (使用 get_market_data_ex 接口)
    # fields=[] 代表获取所有字段
    data_map = ContextInfo.get_market_data_ex(
        fields=[], 
        stock_code=stock_list, 
        period=period, 
        start_time=start_date, 
        end_time=end_date, 
        count=-1, 
        dividend_type='front', # 前复权
        fill_data=True, 
        subscribe=False
    )

    # 3. 数据处理与导出
    # 创建一个 Excel Writer 对象,用于写入多个 Sheet 或者合并数据
    try:
        with pd.ExcelWriter(save_path) as writer:
            has_data = False
            for code, df in data_map.items():
                if not df.empty:
                    # 将每只股票的数据写入单独的 Sheet,Sheet名为股票代码
                    # 注意:Excel Sheet名不能包含特殊字符,这里简单处理一下
                    sheet_name = code.replace('.', '_')
                    df.to_excel(writer, sheet_name=sheet_name)
                    print(f"已写入: {code}")
                    has_data = True
            
            if has_data:
                print(f"导出成功!文件路径: {save_path}")
            else:
                print("未获取到数据,无法导出。")
                
    except Exception as e:
        print(f"导出失败,请检查路径或文件是否被占用。错误信息: {e}")

def handlebar(ContextInfo):
    pass

场景二:导出回测持仓明细

这段代码利用了 stop(ContextInfo) 函数。该函数会在策略回测结束或停止运行时被调用。我们可以在这里获取最终的持仓信息并导出。

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

def init(ContextInfo):
    # 设置账号,回测模式下通常是系统自动分配的虚拟账号,
    # 但为了代码兼容性,建议显式设置或在回测界面配置好
    # 这里演示获取账号列表的第一个
    account_list = get_trade_detail_data('', 'STOCK', 'ACCOUNT')
    if len(account_list) > 0:
        ContextInfo.accid = account_list[0].m_strAccountID
        ContextInfo.set_account(ContextInfo.accid)
        print(f"当前设定账号: {ContextInfo.accid}")
    else:
        print("未找到账号信息")
        ContextInfo.accid = None

def handlebar(ContextInfo):
    # 这里写你的交易策略逻辑
    # 为了演示,我们在第一根K线买入一点股票,以便最后有持仓可以导出
    if ContextInfo.is_new_bar() and not ContextInfo.has_bought:
        # 示例:买入浦发银行
        order_shares('600000.SH', 1000, 'fix', 10.0, ContextInfo, ContextInfo.accid)
        ContextInfo.has_bought = True
    pass

def init(ContextInfo):
    # 初始化辅助变量
    ContextInfo.has_bought = False
    # 获取账号
    account_list = get_trade_detail_data('', 'STOCK', 'ACCOUNT')
    if len(account_list) > 0:
        ContextInfo.accid = account_list[0].m_strAccountID
        ContextInfo.set_account(ContextInfo.accid)
    else:
        ContextInfo.accid = 'test_account' # 回测时的默认名

def stop(ContextInfo):
    """
    策略停止或回测结束时调用此函数
    """
    print("策略运行结束,开始导出持仓明细...")
    
    if not hasattr(ContextInfo, 'accid') or not ContextInfo.accid:
        print("账号未设置,无法获取持仓。")
        return

    # 1. 获取持仓对象列表
    # 参数:账号ID, 账号类型(STOCK/FUTURE), 数据类型(POSITION)
    positions = get_trade_detail_data(ContextInfo.accid, 'STOCK', 'POSITION')

    # 2. 解析对象数据为字典列表
    pos_data = []
    for pos in positions:
        # 提取持仓对象中的关键属性
        # 注意:m_dMarketValue 等属性在回测中可能需要根据收盘价计算,
        # 这里直接读取对象属性,具体属性名参考API文档附录4
        item = {
            '证券代码': pos.m_strInstrumentID,
            '证券名称': pos.m_strInstrumentName,
            '持仓数量': pos.m_nVolume,
            '可用数量': pos.m_nCanUseVolume,
            '持仓成本': pos.m_dOpenPrice,
            '最新价': pos.m_dLastPrice,
            '浮动盈亏': pos.m_dFloatProfit,
            '市值': pos.m_dMarketValue,
            '交易市场': pos.m_strExchangeName
        }
        pos_data.append(item)

    # 3. 导出到 Excel
    save_path = 'D:/qmt_backtest_positions.xlsx'
    
    if len(pos_data) > 0:
        try:
            df = pd.DataFrame(pos_data)
            # 调整列顺序(可选)
            cols = ['证券代码', '证券名称', '持仓数量', '持仓成本', '最新价', '浮动盈亏', '市值']
            # 确保列存在于数据中
            final_cols = [c for c in cols if c in df.columns]
            df = df[final_cols]
            
            df.to_excel(save_path, index=False)
            print(f"持仓明细已成功导出至: {save_path}")
        except Exception as e:
            print(f"导出Excel失败: {e}")
    else:
        print("当前无持仓,未生成文件。")

关键点说明

  1. 文件路径:代码中的 save_path (如 D:/...) 必须是存在的路径。Windows系统中建议使用正斜杠 / 或者双反斜杠 \\ 来分隔路径。
  2. Pandas库:QMT自带了 pandas 库,所以直接 import pandas as pd 即可使用。
  3. stop() 函数:这是导出回测结果的最佳位置。stop(ContextInfo) 是QMT的一个回调函数,当你在界面上点击“停止”或者回测进度条走完时,系统会自动调用这个函数。
  4. get_trade_detail_data:这个函数返回的是一组 C++ 对象(Python Object),不能直接打印看内容,必须通过 pos.属性名 (如 m_strInstrumentID) 的方式提取数据。具体的属性列表可以在QMT API文档的“附录4 交易函数内含属性说明”中找到。

Q&A

Q: 为什么导出时提示 "Permission denied" 错误?
A: 这通常是因为你尝试写入的 Excel 文件当前正在被另一个程序(比如 Excel 软件本身)打开。请关闭该 Excel 文件后再次运行策略。

Q: 如何导出分钟级别的历史数据?
A: 在场景一的代码中,将 period 参数修改为 '1m', '5m', '30m' 等即可。

Q: 回测时 get_trade_detail_data 返回为空怎么办?
A: 请确保在 init 函数中正确设置了 ContextInfo.accid 并且调用了 ContextInfo.set_account()。在回测模式下,账号通常是模拟账号,可以通过打印 get_trade_detail_data('', 'STOCK', 'ACCOUNT') 的结果来查看系统分配的账号ID。