QMT run_time 定时任务机制详解

run_time 是 QMT 内置 Python 的定时任务机制,通过 ContextInfo.run_time 注册定时函数,在指定时间间隔或特定时间点重复执行策略逻辑。它独立于 handlebar 的 K 线触发,适用于盘前检查、定时下单和收盘统计等场景。

QMT ContextInfo.run_time 的调用方式

ContextInfo.run_time 在 init 函数中调用,用于注册一个定时任务。调用后,指定的函数会按照设定的时间规则重复执行。run_time 的基本参数包括:函数名(字符串)、定时规则(字符串)、起始时间(字符串)和 ContextInfo 对象。

run_time 的第一个参数是函数名的字符串形式,不是函数对象本身。定时规则用字符串描述执行频率或具体时间。起始时间指定定时任务开始生效的时间点。注册后,QMT 会在满足时间条件时自动调用指定的函数,无需在 handlebar 中手动管理。

小建议 在 init 中集中注册所有定时任务,便于管理和维护。函数名使用有意义的名称,如 pre_market_checkclose_summary 等,提高代码可读性。确保被注册的函数已定义,且函数签名接收 ContextInfo 参数。

def init(ContextInfo):
    # 注册定时任务:每分钟执行一次
    ContextInfo.run_time('timer_task', '1min', '20200101093000', ContextInfo)
    print('定时任务已注册')

def timer_task(ContextInfo):
    # 定时执行的逻辑
    print(f'定时任务执行,当前时间: {ContextInfo.get_bar_timetag(ContextInfo.barpos)}')

QMT run_time 定时规则的写法

run_time 的定时规则支持两种写法:间隔模式和定时模式。间隔模式如 '1min''5min''1d' 表示每隔指定时间执行一次。定时模式如 '093000''093000,150000' 表示在每天指定时间点执行。

间隔模式的规则字符串由数字和时间单位组成,常用单位包括 s(秒)、min(分钟)、d(天)。定时模式的规则字符串为 6 位时间格式 HHMMSS,表示每天在该时间点执行。多个时间点用逗号分隔。起始时间参数决定了定时任务从何时开始生效。

小建议 根据策略需求选择合适的定时规则。盘前检查使用定时模式(如 '092500'),定时下单使用间隔模式(如 '5min'),收盘统计使用定时模式(如 '150500')。避免设置过短的间隔(如 '1s'),这可能导致回调堆积影响策略性能。

def init(ContextInfo):
    # 间隔模式:每 5 分钟执行一次
    ContextInfo.run_time('periodic_check', '5min', '20200101093000', ContextInfo)
    
    # 定时模式:每天 9:25 执行盘前检查
    ContextInfo.run_time('pre_market_check', '092500', '20200101092500', ContextInfo)
    
    # 定时模式:每天 15:05 执行收盘统计
    ContextInfo.run_time('close_summary', '150500', '20200101150500', ContextInfo)

def periodic_check(ContextInfo):
    print('5分钟定时检查执行')

def pre_market_check(ContextInfo):
    print('盘前检查执行')

def close_summary(ContextInfo):
    print('收盘统计执行')

QMT run_time 与 handlebar 的区别

run_time 和 handlebar 是两种独立的触发机制。handlebar 由 K 线更新驱动,run_time 由时间规则驱动。两者可以同时存在于同一策略中,互不替代。run_time 注册的函数与 handlebar 在同一线程中执行。

handlebar 的触发依赖于主图品种的 K 线更新,如果主图品种没有行情更新,handlebar 就不会被调用。run_time 的触发只依赖时间,与行情无关,即使没有行情更新也会按时执行。这使得 run_time 适合执行不依赖行情触发的任务,如盘前准备、定时检查、超时撤单等。

小建议 根据"触发依据"选择机制:依赖行情变化用 handlebar,依赖时间用 run_time,需要实时响应用 subscribe。三者可以配合使用,例如用 run_time 做盘前初始化和收盘统计,用 handlebar 做信号计算,用 subscribe 做实时风控。

对比维度 handlebar run_time
触发依据 K 线更新 时间规则
是否依赖行情 是,需要行情更新 否,按时间触发
回测支持 支持 支持
典型场景 信号计算、指标判断 盘前检查、定时下单、收盘统计
函数定义 固定函数名 handlebar 自定义函数名
注册方式 自动调用 需在 init 中手动注册

QMT run_time 在回测和实盘中的行为差异

run_time 在回测环境和实时环境中均可使用,但行为存在差异。在回测环境中,定时任务按照回测时间轴触发,执行时刻取决于回测的时间推进速度。在仿真交易环境和真实交易环境中,定时任务按照真实系统时间触发。

在回测中,时间是由历史数据驱动的虚拟时间。run_time 的触发时机取决于回测引擎如何推进时间——只有当回测时间推进到定时规则指定的时间点时,定时函数才会被调用。在实时环境中,run_time 严格按照系统时钟触发,与 K 线更新无关。

小建议 在回测中验证定时任务时,注意观察定时函数是否按预期时间触发。如果回测中定时任务未触发,可能是回测的时间精度或数据周期不够细。在实时环境中,确保系统时间准确,避免因时间偏差导致定时任务在错误时刻执行。

def init(ContextInfo):
    ContextInfo.run_time('timer_task', '5min', '20200101093000', ContextInfo)
    ContextInfo.task_count = 0

def timer_task(ContextInfo):
    ContextInfo.task_count += 1
    # 记录每次执行的触发时间
    timetag = ContextInfo.get_bar_timetag(ContextInfo.barpos)
    print(f'定时任务第 {ContextInfo.task_count} 次执行,时间戳: {timetag}')

QMT run_time 常见问题

开发者在使用 run_time 时常遇到定时任务不执行、执行时间不准确或与 handlebar 冲突等问题。

问题 原因 解决方法
定时任务不执行 函数名拼写错误或函数未定义 检查 run_time 的函数名参数与实际函数名是否一致
定时任务不执行 起始时间设置在未来 确认起始时间早于当前时间
定时任务不执行 策略未启动运行 确认策略已启动且处于运行状态
执行时间不准确 回测时间精度不足 使用更细的 K 线周期提高回测时间精度
与 handlebar 冲突 两者在同一线程执行,互相阻塞 避免在任一函数中执行耗时操作

小建议 注册定时任务后,在定时函数中添加日志确认是否按预期触发。如果定时任务在回测中不执行,尝试将起始时间设置为回测开始日期之前。在实时环境中,确保起始时间已过,否则定时任务不会触发。

def init(ContextInfo):
    # 确保函数名与字符串一致
    ContextInfo.run_time('my_timer', '1min', '20200101000000', ContextInfo)
    print('run_time 已注册,函数名: my_timer')

def my_timer(ContextInfo):
    # 确认函数被调用
    print(f'my_timer 被调用')

QMT run_time 使用场景

run_time 适用于需要按固定时间执行但不依赖 K 线触发的场景,包括盘前检查、定时下单、收盘统计、超时撤单等。

盘前检查需要在开盘前执行,此时可能还没有 K 线更新,handlebar 不会被触发,run_time 是唯一选择。定时下单适用于按固定时间间隔调仓的策略,不依赖 K 线完成。收盘统计需要在收盘后执行,此时当天最后一根 K 线可能已经完成但 handlebar 不再触发。超时撤单需要在指定时间后自动撤销未成交委托,run_time 可以精确控制撤单时机。

小建议 将 run_time 的定时函数与 handlebar 的策略逻辑分离,各自承担明确职责。在定时函数中执行完后添加日志,便于后续审计和排查。涉及交易的定时函数注意使用正确的 quickTrade 参数。

def init(ContextInfo):
    ContextInfo.set_universe(['000001.SZ'])
    
    # 盘前检查:每天 9:25 执行
    ContextInfo.run_time('pre_market', '092500', '20200101092500', ContextInfo)
    
    # 定时下单:每 30 分钟执行
    ContextInfo.run_time('scheduled_order', '30min', '20200101093000', ContextInfo)
    
    # 收盘统计:每天 15:05 执行
    ContextInfo.run_time('daily_summary', '150500', '20200101150500', ContextInfo)
    
    ContextInfo.order_count = 0

def pre_market(ContextInfo):
    # 盘前检查:确认账户状态、持仓情况
    account = ContextInfo.get_account()
    print(f'盘前检查 - 可用资金: {account.m_dAvailable}')

def scheduled_order(ContextInfo):
    # 定时下单逻辑
    ContextInfo.order_count += 1
    print(f'第 {ContextInfo.order_count} 次定时检查')

def daily_summary(ContextInfo):
    # 收盘统计
    positions = ContextInfo.get_positions()
    print(f'收盘统计 - 持仓品种数: {len(positions)}')
    for stock, pos in positions.items():
        print(f'  {stock}: {pos.m_nVolume}股, 盈亏: {pos.m_dProfit}')

def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
    # handlebar 负责 K 线信号计算
    close = ContextInfo.get_close_price()
    print(f'当前收盘价: {close}')