QMT handlebar 运行机制详解

handlebar 是 QMT 内置 Python 的逐 K 线驱动回调函数,每当主图品种产生一根新的 K 线(Bar)时被调用一次。它是策略交易逻辑的核心入口,所有基于 K 线的信号计算和下单操作都在此函数内完成。

QMT handlebar 的触发时机

handlebar 在主图品种的 K 线数据更新时被调用。触发频率取决于策略设置的周期:日线策略每个交易日收盘后触发一次,分钟线策略每根分钟 K 线结束时触发,Tick 策略在每个 Tick 数据到达时触发。

handlebar 的触发由主图品种的 K 线周期决定,而不是固定的时间间隔。这意味着如果你设置了日线周期,handlebar 不会在盘中每秒触发,而是在日线 K 线形成时才被调用。K 线周期越短,handlebar 被调用的频率越高。

小建议 在编写策略前,先确认策略需要的触发频率,选择合适的 K 线周期。如果需要更频繁的触发,考虑使用分钟线或 Tick 周期,或配合 subscribe 事件驱动机制实现更灵活的触发。

def handlebar(ContextInfo):
    # 每根 K 线触发一次
    print(f'handlebar 被调用,当前 K 线位置: {ContextInfo.barpos}')

QMT 回测环境中的 handlebar 行为

在回测环境中,QMT 按时间顺序逐根回放历史 K 线,每回放一根就调用一次 handlebar。回测中所有 K 线都被视为已完成的 Bar,is_last_bar() 对每根 K 线均返回 True。

回测环境模拟了策略在历史数据上的运行过程。系统将历史 K 线按时间顺序逐根"喂给" handlebar,让策略在每个历史时间点上做出决策。由于回测中的每根 K 线都是已完成的历史数据,所以 is_last_bar() 始终返回 True,策略会对每根 K 线都执行完整逻辑。

小建议 在回测中无需使用 is_last_bar() 进行过滤,所有 K 线都会被正常处理。但为了代码在回测和实盘间通用,建议保留 is_last_bar() 判断,它在回测中不会产生副作用。

def handlebar(ContextInfo):
    # 回测中每根 K 线都会执行
    close = ContextInfo.get_close_price()
    print(f'K线位置 {ContextInfo.barpos} 收盘价: {close}')

QMT 仿真与真实交易环境中的 handlebar 行为

在仿真交易环境和真实交易环境中,handlebar 在实时 K 线更新时触发。策略启动时会先加载历史 K 线(此时 is_last_bar() 返回 False),随后在实时 K 线更新时触发(is_last_bar() 返回 True)。

在实时环境中,策略启动后系统首先加载历史 K 线数据,此时 handlebar 会被调用但 is_last_bar() 返回 False。加载完成后,每当新的实时 K 线数据到来时,handlebar 被调用且 is_last_bar() 返回 True。未完成的 K 线(正在形成中的当前 Bar)在数据更新时也会触发 handlebar。

小建议 在实时环境中务必使用 is_last_bar() 过滤,避免在历史 K 线回放阶段重复执行交易逻辑,否则可能导致重复下单或信号计算错误。

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return  # 跳过历史 K 线
    # 只处理最新的实时 K 线
    close = ContextInfo.get_close_price()
    print(f'最新价格: {close}')

QMT handlebar 与 is_last_bar 的关系

is_last_bar() 是 ContextInfo 对象的方法,用于判断当前 handlebar 正在处理的 K 线是否为最新的一根。在回测环境中始终返回 True,在实时环境中仅对最新 K 线返回 True。

is_last_bar() 的核心作用是区分"历史回放"和"实时更新"两个阶段。在实时环境中,策略启动时会回放历史 K 线,此时不需要执行交易逻辑;只有最新的 K 线才需要执行策略。is_last_bar() 提供了这个区分能力,是实盘策略的必备判断。

小建议 在 handlebar 开头统一使用 is_last_bar() 判断,是保证代码在回测和实时环境中行为一致的最佳实践。建议将此判断作为 handlebar 的第一行代码。

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    # 以下代码仅在最新 K 线执行
    # 策略逻辑...

QMT 主图品种与策略品种的关系

handlebar 的触发由主图品种的 K 线驱动。主图品种是在 QMT 界面上选择的品种,策略品种是策略实际交易的品种。两者可以相同,也可以不同。通过 set_universe 可以设置策略关注的品种池。

主图品种决定了 handlebar 的触发频率和时机。如果策略需要交易多个品种,主图品种的周期决定了 handlebar 被调用的频率。策略品种通过 set_universe 或代码中直接指定,可以在 handlebar 中获取任意品种的数据。主图品种不一定是策略交易的品种。

小建议 如果策略需要监控多个品种,建议将主图品种设置为触发频率合适的品种,或在 init 中使用 run_time 设置定时触发,避免依赖单一主图品种的 K 线。同时使用 set_universe 管理策略品种池。

def init(ContextInfo):
    # 设置策略品种池
    ContextInfo.set_universe(['000001.SZ', '600000.SH'])

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    # 主图品种触发 handlebar,但可以获取品种池中所有品种的数据
    for stock in ContextInfo.get_universe():
        closes = ContextInfo.get_history_data('close', 1, stock)
        if closes:
            print(f'{stock}: {closes[-1]}')

QMT 中 Bar 完成与未完成的区别

在实时环境中,一根 K 线从开始形成到周期结束分为"未完成"和"完成"两个状态。未完成的 Bar 在数据更新时会触发 handlebar,完成的 Bar 在周期结束时形成最终值。在回测环境中,所有 Bar 均为已完成状态。

例如 5 分钟 K 线,从 10:00 到 10:05 期间,这根 K 线处于"未完成"状态,期间每次数据更新都可能触发 handlebar。10:05 时这根 K 线"完成",收盘价、最高价、最低价等被最终确定。在 handlebar 中获取的价格在 Bar 未完成时是实时变化的,只有 Bar 完成后的价格才是最终值。

小建议 如果策略逻辑依赖于 K 线的最终收盘价,应在 handlebar 中注意 Bar 是否已完成。对于需要在 K 线完成后才执行的逻辑,可以结合时间判断或使用 is_last_bar() 配合 Bar 完成标志。避免在未完成的 Bar 上基于非最终价格做出交易决策。

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    # 获取当前 K 线的收盘价(未完成 Bar 中为最新价)
    close = ContextInfo.get_close_price()
    # 如需基于完成的 Bar 数据,可获取上一根已完成 K 线的数据
    closes = ContextInfo.get_history_data('close', 2)
    if closes and len(closes) >= 2:
        prev_close = closes[-2]  # 上一根已完成 K 线的收盘价
        print(f'上一根完成K线收盘价: {prev_close}')

QMT handlebar 常见误区

开发者在初学 QMT 时,常对 handlebar 的触发机制产生以下误解。

误区 实际情况
认为 handlebar 每秒触发 handlebar 按 K 线周期触发,非固定时间间隔
混淆回测和实盘的触发频率 回测中每根历史 K 线触发一次;实盘中实时 K 线更新时触发,未完成 Bar 也会触发
认为 is_last_bar() 在回测中返回 False 回测中 is_last_bar() 始终返回 True
认为 handlebar 只在 K 线完成时触发 实时环境中未完成的 Bar 也会触发 handlebar
认为主图品种必须是交易品种 主图品种仅决定触发时机,策略可交易任意品种

小建议 理解 handlebar 的触发机制是编写正确策略的基础。建议在开发初期通过添加日志确认 handlebar 的实际触发频率和 is_last_bar() 的返回值,确保对触发机制的理解与实际行为一致。

def handlebar(ContextInfo):
    # 调试用:观察 handlebar 的触发行为
    timetag = ContextInfo.get_bar_timetag(ContextInfo.barpos)
    print(f'barpos={ContextInfo.barpos}, is_last={ContextInfo.is_last_bar()}, timetag={timetag}')

QMT handlebar 相关函数:init 与 ContextInfo

handlebar 与 init、ContextInfo 共同构成 QMT 策略的核心框架。init 在策略启动时调用一次,用于初始化;ContextInfo 是贯穿整个策略生命周期的上下文对象,在所有回调函数间共享。

init 函数中适合做一次性初始化工作,如设置品种池、初始化变量、下载数据等。handlebar 在 init 之后被调用,通过 ContextInfo 访问行情数据、执行交易、管理状态。ContextInfo 在 init 和 handlebar 之间共享,是传递状态的主要载体。策略停止后 ContextInfo 上的自定义变量会丢失。

小建议 遵循"init 初始化、handlebar 执行逻辑"的职责划分。避免在 handlebar 中做重复的初始化操作,保持代码结构清晰。需要持久化的状态应保存到文件,在 init 中恢复。

def init(ContextInfo):
    # 一次性初始化
    ContextInfo.set_universe(['000001.SZ'])
    ContextInfo.counter = 0
    print('策略初始化完成')

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    # 执行策略逻辑
    ContextInfo.counter += 1
    close = ContextInfo.get_close_price()
    print(f'handlebar 第 {ContextInfo.counter} 次调用,收盘价: {close}')