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_check、close_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}')