3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在QMT平台上从回测转向真仓(实盘)交易时,环境的差异会导致许多在回测中不易察觉的问题暴露出来。以下是针对QMT Python API整理的常见编程错误及避免方法,涵盖了信号处理、下单逻辑、数据获取和环境配置四个维度。
在实盘中,最致命的错误是信号闪烁(Repainting)。这是指在当前K线尚未走完时,根据盘中临时价格发出了信号,但K线收盘后信号消失,导致实盘买入但回测中没有该笔交易。
在 handlebar 函数中,对于最后一根K线(实时K线),close 价格是随 Tick 变动的。
# 错误写法
def handlebar(ContextInfo):
# 获取当前K线数据
close = ContextInfo.get_market_data(['close'])
# 如果盘中价格突破,立即下单
if close > ma20:
passorder(...)
后果:盘中价格瞬间突破MA20触发买入,但收盘回落跌破MA20。你的持仓还在,但策略逻辑上不该持有。
barpos - 1)做决策。ContextInfo.get_tick_timetag() 判断时间。is_last_bar 和 is_new_bar:# 正确写法:在下一根K线的起始时刻,基于上一根K线的收盘价下单
def handlebar(ContextInfo):
# 如果不是最新的一根K线,或者是最新K线但不是新的一根(即盘中Tick),则跳过
if not ContextInfo.is_last_bar():
return
# 只有在实盘中,新K线生成的第一个Tick才执行逻辑(避免盘中重复计算)
# 注意:is_new_bar() 在回测和实盘表现略有不同,需结合具体逻辑
if ContextInfo.is_new_bar():
# 获取历史数据(不包含当前正在跳动的K线)
history_data = ContextInfo.get_market_data_ex(..., count=2)
# 使用 history_data.iloc[-2] (上一根) 进行判断
QMT的 handlebar 在实盘模式下,最后一根K线每收到一个Tick(分笔数据)就会运行一次。如果不加控制,策略会在几秒钟内把资金全部打光。
# 错误写法
def handlebar(ContextInfo):
# 只要条件满足,每个Tick都会触发一次下单
if condition:
passorder(23, 1101, account, code, 5, -1, 100, ContextInfo)
get_trade_detail_data 确认当前没有持仓。ContextInfo 存储状态标记。# 正确写法
def init(ContextInfo):
ContextInfo.has_ordered = False # 初始化标记
def handlebar(ContextInfo):
# 每日开盘重置标记(示例逻辑)
if is_new_day(ContextInfo):
ContextInfo.has_ordered = False
if condition and not ContextInfo.has_ordered:
passorder(...)
ContextInfo.has_ordered = True # 标记已下单
QMT的 get_market_data 接口非常灵活,但也是新手噩梦。根据传入参数的不同(单股vs多股,单时间点vs时间序列),它返回的数据类型会在 pandas.Series、pandas.DataFrame 和 pandas.Panel 之间变化。
假设返回的是 DataFrame,结果只传了一只股票导致返回了 Series,代码报错 AttributeError。
get_market_data_ex:该接口返回格式更统一(字典形式 {code: DataFrame}),不易出错。# 推荐写法
def handlebar(ContextInfo):
# 获取多只股票数据,返回字典
data_map = ContextInfo.get_market_data_ex(
['close'],
['600000.SH', '000001.SZ'],
period='1d',
count=10
)
# 即使只传一只股票,也建议按列表传,处理方式保持一致
df_600000 = data_map['600000.SH'] # 这是一个DataFrame
在回测中,QMT可能会自动处理停牌(取决于 skip_paused 参数),但在真仓交易中,向停牌或跌停的股票发送卖单,或者向涨停的股票发送买单,会导致废单或无法成交。
ContextInfo.is_suspended_stock(code)。UpStopPrice 和 DownStopPrice(通过 get_instrumentdetail 或行情数据),下单价格不要超过限制。def check_validity(ContextInfo, code, price):
# 检查停牌
if ContextInfo.is_suspended_stock(code):
return False
# 检查涨跌停
detail = ContextInfo.get_instrumentdetail(code)
if price >= detail['UpStopPrice'] or price <= detail['DownStopPrice']:
# 根据策略逻辑决定是否追涨杀跌,或者避开
pass
return True
QMT底层是C++,Python脚本文件默认编码格式要求严格。
# -*- coding: gbk -*- 或 #coding:gbk。如果不加,中文注释会导致编译错误或乱码。不要使用 Python 原生的 global 关键字来跨函数传递策略状态(如持仓标记、计数器)。QMT 的运行机制可能会重置脚本环境。
ContextInfo 对象上。# 错误
my_counter = 0
def handlebar(ContextInfo):
global my_counter
my_counter += 1
# 正确
def init(ContextInfo):
ContextInfo.my_counter = 0
def handlebar(ContextInfo):
ContextInfo.my_counter += 1
passorder 函数需要传入准确的资金账号字符串。新手常犯错误是传了数字类型而不是字符串,或者弄错了账号类型(股票 vs 期货)。.SH (如 600000.SH).SZ (如 000001.SZ)Q: 为什么我的策略在回测很完美,实盘却不交易?
A:
init 中是否调用了 ContextInfo.set_account(account_id) 绑定账号。passorder 中的 quickTrade 参数。回测通常默认在K线结束时撮合,实盘如果需要在信号触发瞬间下单,需设置 quickTrade=1(立即下单)或 2(即使是历史K线也下单,慎用)。Q: 如何在盘中获取最新的实时价格(Tick数据)?
A: 使用 ContextInfo.get_full_tick([code])。它返回一个字典,包含最新的买一卖一、最新价、成交量等详细盘口数据。不要用 get_market_data 去取高频切片,效率较低。
Q: passorder 下单后,如何确认成交了?
A: 不要假设下单即成交。必须通过 order_callback (委托回报) 和 deal_callback (成交回报) 回调函数来接收通知,或者在 handlebar 中轮询 get_trade_detail_data 查询委托状态。
Q: 策略报错 "name 'ContextInfo' is not defined" 是怎么回事?
A: 这是一个常见的误解。ContextInfo 是QMT平台回调 init 和 handlebar 时自动传入的参数。你不能在这些函数之外(例如模块层级)直接使用 ContextInfo。所有的逻辑都必须包含在平台调用的函数内部。