问题描述
大家好,我是从聚宽迁移过来的用户,在开发 QMT 内置 Python 策略时遇到几个问题,已经折腾了几天了,客服让我来社区求助。
一、核心问题:回测无成交记录
现象:
策略运行后,日志中有 [BUY] 和 [SELL] 打印
但回测报告窗口是空白的(只有框子,没有内容)
用 get_trade_detail_data('test', 'stock', 'deal') 获取成交记录,返回空
用 get_trade_detail_data('test', 'stock', 'position') 获取持仓,也返回空
回测参数设置:
已按文档要求以"副图模式"执行
最大成交比例设置为 100
回测时间:2024-01-01 到 2024-12-31
初始资金:500000
问题:
回测报告空白是什么原因?策略到底有没有实际成交?
如何确认回测是否成交?有没有可靠的查看方式?
"当日买入"和"持仓明细"面板在回测中是否本来就没有内容?
二、下单函数选择疑问
我看到两种下单方式,不确定哪个对:
方式 1(网上找的,8 个参数):
passorder(23, 1101, accountid, stock, 11, price, volume, ContextInfo)
方式 2(官方文档,11 个参数):
passorder(opcode, ordertype, accountid, stock, price_type, price, volume, strategy_name, quicktrade, remark, C)
问题:
passorder() 到底是 8 个参数还是 11 个参数?
官方文档写的是 11 个参数,但网上大部分示例是 8 个参数,哪个是对的?
回测中应该用哪种下单方式?
三、其他疑问
order_target_value() 在回测中是否能用?官方文档没有提到这个函数
get_bar_timetag(barpos) 在回测中返回 0,这是正常的吗?有没有其他获取时间的方法?
代码中已经加了 #coding:gbk,还有哪里需要注意?
四、测试代码(简化版)
#coding:gbk
import pandas as pd
import numpy as np
def init(C):
C.accountid = "testS"
C.set_universe(['000820.SZ', '601700.SH'])
C.positions = {}
print('Strategy initialized')
def handlebar(C):
barpos = C.barpos
# Get current time
timetag = C.get_bar_timetag(barpos)
if timetag > 0:
import datetime
timestamp_sec = timetag / 1000.0
dt = datetime.datetime.fromtimestamp(timestamp_sec)
current_time = dt.hour * 100 + dt.minute
else:
current_time = 0
# Check buy signal at 14:50
if current_time == 1450:
for code in C.get_universe():
# Buy logic (simplified)
passorder(23, 1101, C.accountid, code, 5, -1, 100, C)
print('BUY %s' % code)
# Check sell signal at 14:30
if current_time == 1430:
for code in C.get_universe():
if code in C.positions:
passorder(24, 1101, C.accountid, code, 5, -1, 100, C)
print('SELL %s' % code)
日志输出:
Strategy initialized
BUY 000820.SZ
BUY 601700.SH
SELL 000820.SZ
SELL 601700.SH
但回测报告空白,get_trade_detail_data 返回空。
请问各位大佬,这是哪里做错了?是下单函数参数不对,还是回测配置有问题?
感谢!
解决方案
欢迎来到 QMT 量化平台!从聚宽(JoinQuant)等云端平台迁移到 QMT 客户端时,由于底层运行机制(特别是数据获取和回测撮合机制)的差异,确实容易遇到一些“水土不服”的问题。针对您提到的回测无成交、passorder 参数疑惑以及其他 API 问题,以下是详细的排查指南和解答:
一、 核心问题:回测无成交记录、报告空白
在 QMT 中,如果日志打印了买卖信号但回测报告空白,通常由以下几个原因导致:
1. 历史行情数据未下载(最常见原因)
QMT 是本地客户端,回测依赖本地数据。如果您没有在【数据管理】中下载对应时间段(2024-01-01 到 2024-12-31)、对应周期(如 1分钟线)的行情数据,策略虽然能空跑循环,但底层撮合引擎没有数据支撑,自然无法产生成交。
- 解决办法:点击客户端顶部的【数据管理】 -> 【补充数据】,选择您股票池中的股票和对应的周期,下载完整的历史数据。
2. 周期设置不匹配
您的代码中使用了 current_time == 1450(即 14:50)的逻辑。这要求策略必须运行在 1分钟(1m) 周期下。如果您的策略默认周期是日线(1d),get_bar_timetag 解析出来的时间通常是 15:00:00 或 00:00:00,永远无法触发 14:50 的条件。
- 解决办法:在策略编辑器的【基本信息】中,将“默认周期”改为“1分钟线”,并在回测参数中确认主图周期为 1分钟。
3. 错误的查询函数
您使用了 get_trade_detail_data 来查询回测成交记录。注意:get_trade_detail_data 仅用于实盘或模拟盘获取真实柜台的交易明细,在回测模式下无效(返回空)。
- 解决办法:在回测模式下,如果想在代码中获取记录,应使用
get_result_records(recordtype, index, ContextInfo)函数(例如get_result_records('dealdetails', C.barpos, C))。回测完成后,直接查看界面右侧的“绩效分析”和“操作明细”面板即可。
二、 下单函数 passorder 参数疑问
问题:passorder() 到底是 8 个参数还是 11 个参数?
答案:两者都对。
根据 QMT 官方 API 文档,passorder 的完整定义如下:
passorder(opType, orderType, accountid, orderCode, prType, modelprice, volume[, strategyName, quickTrade, userOrderId], ContextInfo)
- 必填参数(8个):
opType,orderType,accountid,orderCode,prType,modelprice,volume,ContextInfo。 - 可选参数(3个):
strategyName(策略名),quickTrade(快速交易标志),userOrderId(自定义订单ID)。这三个参数被包裹在[]中,代表可以省略。
结论:在回测中,直接使用 8 个参数的方式(如您代码中的 passorder(23, 1101, C.accountid, code, 5, -1, 100, C))是完全正确且最常用的。
三、 其他疑问解答
1. order_target_value() 在回测中是否能用?
能用。 QMT 提供了丰富的封装下单函数,包括 order_target_value(目标价值下单)、order_value(指定价值下单)、order_lots(指定手数下单)等。这些函数在回测和实盘中均支持。官方文档中明确包含了该函数:
order_target_value(stockcode, tar_value[, style, price], ContextInfo[, accId])
2. get_bar_timetag(barpos) 返回 0 是正常的吗?
不正常。 返回 0 通常意味着:
- 当前 K 线索引(
barpos)越界或无效。 - 本地没有该股票的历史数据,导致无法获取时间戳。
请务必先去【数据管理】补充数据。
四、 测试代码修正建议
为了让您的策略顺利跑通,请参考以下修正后的代码,并确保在 1分钟周期下运行,且已下载数据:
#coding:gbk
import datetime
def init(C):
# 设置虚拟回测账号
C.accountid = "testS"
C.set_account(C.accountid)
C.set_universe(['000820.SZ', '601700.SH'])
print('Strategy initialized')
def handlebar(C):
# 获取当前时间戳
timetag = C.get_bar_timetag(C.barpos)
if timetag <= 0:
return
# 转换为 datetime 对象
dt = datetime.datetime.fromtimestamp(timetag / 1000.0)
current_time = dt.hour * 100 + dt.minute
# 14:50 买入逻辑
if current_time == 1450:
for code in C.get_universe():
# 使用 order_value 或 passorder 均可
# 这里演示使用 8参数的 passorder,买入 100股
passorder(23, 1101, C.accountid, code, 5, -1, 100, C)
print(f'[{dt}] BUY {code}')
# 14:30 卖出逻辑
if current_time == 1430:
for code in C.get_universe():
# 回测中获取当前持仓,应使用 get_result_records 或自行维护
# 简单起见,这里直接尝试卖出 100股
passorder(24, 1101, C.accountid, code, 5, -1, 100, C)
print(f'[{dt}] SELL {code}')
总结排查步骤:
- 检查【数据管理】是否下载了 2024 年的 1分钟线数据。
- 检查策略【基本信息】和【回测参数】中的周期是否设为了“1分钟线”。
- 运行回测,查看右侧“操作明细”面板,即可看到真实的成交记录。