前言策略写成while True之后有人在里面做机器学习推理、拉数据库、甚至time.sleep(5)结果行情更新变慢、下单延迟、断线重连也跟不上。期货程序化里主循环的唯一职责应该是尽快回到wait_update重活往外搬。天勤TqSdk的wait_update会阻塞到收到业务包并在此期间驱动后台任务例如TargetPosTask发单。理解这一点就能解释“为什么循环里不能长时间占着 CPU”。下面分场景说明怎么改写法。一、wait_update 实际在干什么根据TqApi.wait_update文档每次调用会把待发网络包真正发出去订阅、报单等尝试收包并更新内存中的业务截面给后台任务执行机会如调仓 task无数据则挂起等待因此两次wait_update之间隔得越久行情和处理回报都越滞后。这不是“多线程就能随便 sleep”能解决的——默认策略线程模型就是同步wait_update驱动。二、典型阻塞写法与改法阻塞写法风险改法time.sleep(n)等下一根 K 线n 秒内无更新用is_changing(kl.iloc[-1], datetime)过滤大矩阵回测式计算放主循环CPU 占满放子进程/定时批处理或缩小计算频率同步 HTTP 调钉钉IO 阻塞异步队列 单独线程消费主循环只入队读大文件、写数据库同上收盘后批处理盘中只写内存队列在协程里调wait_update直接抛错协程内用register_update_notify官方写明不能在协程中调用wait_update需要在协程里等更新时应使用register_update_notify。三、正确的主循环骨架fromtqsdkimportTqApi,TqAuth,TqSim apiTqApi(TqSim(),authTqAuth(账户,密码))klapi.get_kline_serial(SHFE.rb2510,60,data_length200)whileTrue:api.wait_update()# 仅在新 K 线时算信号示例ifnotapi.is_changing(kl.iloc[-1],datetime):continue# 轻量逻辑几行判断 set_target_volume要点先wait_update再判断要不要算用字段级is_changing避免每个 tick 跑完整策略需要超时守护时可用deadline参数非简单用法文档提醒 deadline 过小可能任务堆积四、必须做重计算时怎么办方案 A降频只在 K 线收盘或持仓变化时算不在每个last_price跳动时算。方案 B队列主循环把原始数据丢进queue.Queue工作线程算信号写回“目标仓”变量主循环只读该变量并set_target_volume。注意线程安全目标仓用简单 int 即可。方案 C参数扫描离线批量回测用多进程不要塞在实盘while True里团队专题里常强调多进程回测与实盘循环分离。五、TargetPosTask 与阻塞的关系TargetPosTask的下单、撤单在set_target_volume之后的 wait_update里执行。你若在set_target_volume后去 sleep调仓也会停住。task.set_target_volume(2)api.wait_update()# 必须继续调用六、如何发现“已经堵住”日志时间戳间隔突然变大quote.datetime明显落后于交易所时间下单后很久get_order仍无状态变化可在循环末尾打time.time()与quote.datetime对比若单次循环耗时经常超过数百毫秒就要剖开 profile。总结while True本身没问题问题在循环体占用时间过长。天勤以wait_update为心跳循环里应尽快返回等待下一包用is_changing做字段过滤用 K 线datetime变化代替 sleep 等 bar。重计算、IO、通知发送移到降频、队列或收盘后批处理协程环境勿直接wait_update。TargetPosTask依赖后续wait_update才能真正发单set 目标仓后不能长时间阻塞。FAQ1能不能两个线程各写一个 wait_update不要。一个TqApi实例应单线程驱动wait_update。2deadline 怎么用wait_update(deadlinetime.time()5)超时返回 False可用于顺带做心跳检测默认值是无限等待。3回测也会“阻塞”吗回测由历史数据推进同样依赖wait_update主循环里大计算仍会变慢回测但不影响真实网络。4register_update_notify 何时需要需要在 asyncio 协程、Jupyter 异步单元里等更新时使用与同步策略写法不同。风险提示本文讨论程序结构不构成投资建议。
量化主循环 while True 一直跑:天勤里怎样不堵住行情更新
前言策略写成while True之后有人在里面做机器学习推理、拉数据库、甚至time.sleep(5)结果行情更新变慢、下单延迟、断线重连也跟不上。期货程序化里主循环的唯一职责应该是尽快回到wait_update重活往外搬。天勤TqSdk的wait_update会阻塞到收到业务包并在此期间驱动后台任务例如TargetPosTask发单。理解这一点就能解释“为什么循环里不能长时间占着 CPU”。下面分场景说明怎么改写法。一、wait_update 实际在干什么根据TqApi.wait_update文档每次调用会把待发网络包真正发出去订阅、报单等尝试收包并更新内存中的业务截面给后台任务执行机会如调仓 task无数据则挂起等待因此两次wait_update之间隔得越久行情和处理回报都越滞后。这不是“多线程就能随便 sleep”能解决的——默认策略线程模型就是同步wait_update驱动。二、典型阻塞写法与改法阻塞写法风险改法time.sleep(n)等下一根 K 线n 秒内无更新用is_changing(kl.iloc[-1], datetime)过滤大矩阵回测式计算放主循环CPU 占满放子进程/定时批处理或缩小计算频率同步 HTTP 调钉钉IO 阻塞异步队列 单独线程消费主循环只入队读大文件、写数据库同上收盘后批处理盘中只写内存队列在协程里调wait_update直接抛错协程内用register_update_notify官方写明不能在协程中调用wait_update需要在协程里等更新时应使用register_update_notify。三、正确的主循环骨架fromtqsdkimportTqApi,TqAuth,TqSim apiTqApi(TqSim(),authTqAuth(账户,密码))klapi.get_kline_serial(SHFE.rb2510,60,data_length200)whileTrue:api.wait_update()# 仅在新 K 线时算信号示例ifnotapi.is_changing(kl.iloc[-1],datetime):continue# 轻量逻辑几行判断 set_target_volume要点先wait_update再判断要不要算用字段级is_changing避免每个 tick 跑完整策略需要超时守护时可用deadline参数非简单用法文档提醒 deadline 过小可能任务堆积四、必须做重计算时怎么办方案 A降频只在 K 线收盘或持仓变化时算不在每个last_price跳动时算。方案 B队列主循环把原始数据丢进queue.Queue工作线程算信号写回“目标仓”变量主循环只读该变量并set_target_volume。注意线程安全目标仓用简单 int 即可。方案 C参数扫描离线批量回测用多进程不要塞在实盘while True里团队专题里常强调多进程回测与实盘循环分离。五、TargetPosTask 与阻塞的关系TargetPosTask的下单、撤单在set_target_volume之后的 wait_update里执行。你若在set_target_volume后去 sleep调仓也会停住。task.set_target_volume(2)api.wait_update()# 必须继续调用六、如何发现“已经堵住”日志时间戳间隔突然变大quote.datetime明显落后于交易所时间下单后很久get_order仍无状态变化可在循环末尾打time.time()与quote.datetime对比若单次循环耗时经常超过数百毫秒就要剖开 profile。总结while True本身没问题问题在循环体占用时间过长。天勤以wait_update为心跳循环里应尽快返回等待下一包用is_changing做字段过滤用 K 线datetime变化代替 sleep 等 bar。重计算、IO、通知发送移到降频、队列或收盘后批处理协程环境勿直接wait_update。TargetPosTask依赖后续wait_update才能真正发单set 目标仓后不能长时间阻塞。FAQ1能不能两个线程各写一个 wait_update不要。一个TqApi实例应单线程驱动wait_update。2deadline 怎么用wait_update(deadlinetime.time()5)超时返回 False可用于顺带做心跳检测默认值是无限等待。3回测也会“阻塞”吗回测由历史数据推进同样依赖wait_update主循环里大计算仍会变慢回测但不影响真实网络。4register_update_notify 何时需要需要在 asyncio 协程、Jupyter 异步单元里等更新时使用与同步策略写法不同。风险提示本文讨论程序结构不构成投资建议。