问题描述
解决方案
在QMT中,导出数据到Excel主要依赖于Python强大的第三方库 pandas。QMT内置了 pandas 和 openpyxl (或 xlwt),因此你可以直接将数据整理成 DataFrame 格式,然后调用 .to_excel() 方法保存。
以下提供两个具体的代码示例:
- 导出历史行情数据:获取指定股票的历史K线数据并保存。
- 导出回测持仓明细:在策略回测结束时,自动将最终持仓导出。
场景一:导出历史行情数据
这段代码会在策略初始化 (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("当前无持仓,未生成文件。")
关键点说明
- 文件路径:代码中的
save_path(如D:/...) 必须是存在的路径。Windows系统中建议使用正斜杠/或者双反斜杠\\来分隔路径。 - Pandas库:QMT自带了 pandas 库,所以直接
import pandas as pd即可使用。 - stop() 函数:这是导出回测结果的最佳位置。
stop(ContextInfo)是QMT的一个回调函数,当你在界面上点击“停止”或者回测进度条走完时,系统会自动调用这个函数。 - 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。