问题描述
解决方案
在 QMT(Quant Trader)平台进行分钟级别(或任何 K 线周期)的实盘或模拟交易时,handlebar(ContextInfo) 函数在最后一根 K 线(实时行情)上是基于 Tick 驱动的。这意味着每来一个分笔数据(Tick),handlebar 就会被调用一次。
如果不加控制,简单的 if condition: buy() 逻辑会导致在同一分钟内重复下单。
要保证交易信号只在新 K 线生成时触发一次,主要有以下三种常用方法:
方法一:使用全局变量记录 K 线位置(推荐)
这是最稳健的方法。通过在 ContextInfo 中记录上一次交易或运行逻辑时的 K 线索引(barpos)或时间戳(timetag),来判断当前是否进入了新的 K 线。
逻辑:
- 在
init中初始化一个变量记录 K 线位置。 - 在
handlebar中,获取当前 K 线位置。 - 比较当前位置与记录的位置,如果不同,说明是新 K 线,执行逻辑并更新记录。
代码示例:
# -*- coding: gbk -*-
def init(ContextInfo):
# 1. 初始化全局变量,用于记录上一次处理的 K 线索引
# -1 表示尚未处理过任何 K 线
ContextInfo.last_processed_barpos = -1
# 设置账号等其他初始化操作
# ContextInfo.set_account('您的账号')
def handlebar(ContextInfo):
# 获取当前 K 线索引
current_barpos = ContextInfo.barpos
# 2. 判断是否为新的一根 K 线
# 如果当前索引 大于 上次处理的索引,说明新 K 线生成了(或者刚启动策略)
if current_barpos > ContextInfo.last_processed_barpos:
# --- 策略逻辑开始 ---
# 获取行情数据
close_price = ContextInfo.get_market_data(['close'], period=ContextInfo.period, count=1)
print(f"新 K 线生成,当前索引: {current_barpos}, 最新收盘价: {close_price}")
# 在这里编写你的交易信号逻辑
# if condition:
# passorder(...)
# --- 策略逻辑结束 ---
# 3. 关键步骤:更新已处理的 K 线索引
# 标记当前 K 线已经处理过,防止同根 K 线后续 Tick 重复触发
ContextInfo.last_processed_barpos = current_barpos
else:
# 如果索引没有变化,说明还是在同一根 K 线的不同 Tick 中
# 这里可以什么都不做,或者只做一些盘口观察逻辑
pass
方法二:使用 ContextInfo.is_new_bar()
QMT API 提供了一个原生函数 is_new_bar(),用于判断当前 Tick 是否为当前 K 线的第一个 Tick。
逻辑:
直接调用 ContextInfo.is_new_bar(),如果返回 True,则执行交易逻辑。
优缺点:
- 优点:代码简洁。
- 缺点:在实盘网络波动极大或数据包丢失的极端情况下,如果“第一个 Tick”丢失,可能会导致整根 K 线漏单(虽然概率较低)。
代码示例:
# -*- coding: gbk -*-
def init(ContextInfo):
pass
def handlebar(ContextInfo):
# 判断是否为新 K 线的第一个 Tick
if ContextInfo.is_new_bar():
print(f"检测到新 K 线,当前时间: {ContextInfo.get_bar_timetag(ContextInfo.barpos)}")
# 执行交易逻辑
# passorder(...)
方法三:使用 do_order() 配合收盘价模型
如果你习惯于**“K 线走完确认信号,在下一根 K 线开盘立即下单”**(即收盘价模型),QMT 提供了 do_order 机制。
逻辑:
- 策略逻辑写在
handlebar中,基于最后一根 K 线(实时变动)计算信号。 - 只有当这根 K 线彻底走完(即下一根 K 线的第一个 Tick 到来时),QMT 才会真正发出信号。
- 需要配合
do_order(ContextInfo)函数使用,或者在非日线周期下依赖平台机制。
注意:这种方式通常用于日线策略,或者不需要盘中立即抢单的策略。对于分钟级策略,通常建议使用方法一,配合 quickTrade=1(立即下单)参数,以便在信号触发的瞬间立即成交,而不是等到下一分钟。
总结与建议
对于分钟级别的量化策略,强烈建议使用【方法一:全局变量记录 K 线位置】。
理由:
- 可控性强:你可以完全控制信号触发的逻辑,不受 API 底层 Tick 推送机制的偶发影响。
- 避免重复:通过
barpos的严格比对,物理上杜绝了同一根 K 线重复执行交易代码块的可能性。 - 兼容性:这种写法在回测和实盘中逻辑一致性较好。
关于下单函数的补充:
在使用 passorder 或 order_shares 等下单函数时,请确保 quickTrade 参数设置为 1(立即下单),否则系统可能会默认等到 K 线结束才报单,这会违背你“新 K 线生成即触发”的初衷。
例如:
# quickTrade=1 表示立即触发,不等待 K 线结束
passorder(23, 1101, account, code, 5, -1, 100, "strategyName", 1, "remark", ContextInfo)
Q&A
Q: ContextInfo.barpos 在实盘中是实时更新的吗?
A: 是的。每当时间跨过 K 线切分点(例如 10:00 到 10:01),barpos 会自动加 1。
Q: 如果策略重启了,last_processed_barpos 会重置吗?
A: 是的,策略重启后 init 会重新运行,变量会重置。如果不想在重启瞬间重复交易,可以在 init 中先获取一次当前的 barpos 赋值给 last_processed_barpos,这样策略会从“下一根”K 线开始交易。
Q: 这种方法支持多只股票吗?
A: 支持。handlebar 是由主图行情驱动的。如果你交易多只股票,且它们都在同一个周期下,主图的 barpos 变化代表时间推进,你可以在逻辑块内循环遍历你的股票池进行信号计算。