前言
国内期货趋势量化程序的典型链路是:天勤TqApi订阅螺纹钢 5 分钟 K 线,双均线算出「目标净持仓 5 手」这一交易信号,执行层调用TargetPosTask.set_target_volume(5),task 在后续每次api.wait_update()里向期货公司发单、撤单、对价改价,柜台回报刷新到get_position("SHFE.rb2510").pos(净持仓手数,正为多、负为空)。
排查实盘时,日志连续三天写着 target=5,但pos一直是 4 或 6,差一两手对不齐。第一反应往往是「均线算错」,更常见却是:部分成交、场上还有status=="ALIVE"的在途单、同一合约重复建了 task、或手写insert_order与 task 抢仓位。set_target_volume当下并不成交,target 与 pos 短期不一致可以是正常现象。下面按天勤单例约束、调仓时序和排查顺序,说明怎么区分正常过渡与真故障。
一、先弄清几个名词
| 名称 | 是什么 | 偏差排查里怎么用 |
|---|---|---|
set_target_volume(n) | 告诉 task 目标净持仓手数 | 只改意图,不立刻成交 |
get_position(symbol).pos | 柜台确认的净持仓 | 真相,正负表示多空 |
TargetPosTask | 天勤自动调仓类 | 在 wait_update 里执行 |
wait_update() | 收行情与交易回报 | 不调就不会推进 task |
get_order() | 当前委托字典 | 看在途 ALIVE 单 |
volume_left | 委托剩余未成交量 | 部分成交时大于 0 |
status | 委托状态 ALIVE/FINISHED | 是否在途 |
min_volume/max_volume | 大单拆分参数 | 分批导致短期对不齐 |
offset_priority | 开平顺序 | 平今失败时 pos 卡住 |
二、为什么 target 和 pos 可以短期不一致
TargetPosTask文档写得很清楚:set_target_volume当下不下单,下单撤单发生在之后的wait_update()。因此下列情况属于正常现象:
- 刚调用
set_target_volume(5),下一行就读pos,仍是旧值。 - 单子已报入交易所,部分成交,
pos在 4 与 5 之间。 - 启用了
min_volume/max_volume拆分,多帧wait_update才凑满目标。 - 对价
price="ACTIVE"追价时,旧单撤完新单未成交,中间有空窗。
需要告警的是:连续多个交易时段、无 ALIVE 单、无拒单,而pos仍与 target 差值不变。
三、天勤单例约束:重复建 task 会埋雷
target_pos_task.py里TargetPosTask用单例元类:同一账户同一symbol只能有一个 task 实例。若用不同price、offset_priority、min_volume再建一次,会直接抛异常。
更隐蔽的是:同一合约既用TargetPosTask,又手写insert_order。文档明确禁止混用,否则 task 内部状态与柜台持仓错位,表现为 target 已改但 pos 长期不对,或莫名多出一笔委托。
排查时全局搜索该symbol是否只有一处TargetPosTask,以及是否还有insert_order调用。
四、推荐排查顺序
api.wait_update()至少跑几帧,确认不是读太早。- 读
pos = api.get_position(symbol).pos,记target(自己维护或 task 日志)。 - 遍历
get_order(),筛symbol相同且status == "ALIVE"的项,看volume_left与direction。 - 若有拒单,读
last_msg;开平、资金类错误会导致 task 停在某一手数。 - 查是否拆分模式:看创建 task 时的
max_volume。 - 多账户模式是否漏传
account参数,导致读到别的账户持仓。
可落地的日志字段建议每次调仓记:bar_datetime、target、pos、alive_volume(在途剩余量之和)、delta=target-pos。
deflog_pos_gap(api,symbol,target,logger):api.wait_update()pos=api.get_position(symbol).pos alive=sum(o.volume_leftforoinapi.get_order().values()ifo.instrument_idinsymbolando.status=="ALIVE")logger.info("pos_gap symbol=%s target=%s pos=%s alive_left=%s",symbol,target,pos,alive,)五、纠偏与恢复
确认无在途单且pos仍不对时:
- 以
get_position为准重置本地 state,再set_target_volume到真实目标。 - 若怀疑 task 内部错乱,进程级重启:停机、对账、重建
TargetPosTask(改创建参数必须重启,见发版边界)。 - 不要连续狂调
set_target_volume试图「催成交」,容易触发报单频率规则。
六、典型 last_msg 与偏差共存
| last_msg 关键词 | 常见后果 |
|---|---|
| 资金不足 | target 不变,pos 落后 |
| 非交易时段 | 夜盘衔接误发单 |
| 平今不足 | 空头减仓卡住 |
| 超过最大委托量 | 部分成交 |
拒单后若不读last_msg,会误以为 task 还在正常追价。
七、模拟与实盘偏差对比
TqSim往往更快对齐;实盘 ACTIVE 追价在波动大时可能反复撤单重报,gap持续更久。应用同一套log_pos_gap在两种环境各跑一天,建立「正常 gap 持续时间」基线。
总结
期货量化里目标仓与净持仓短期不一致,多数是天勤TargetPosTask的正常过渡:调仓在wait_update里分步完成,部分成交和拆单都会拉长对齐时间。持续对不齐时,按 pos、在途 ALIVE 单、last_msg、是否混用insert_order、是否重复建 task 这条链排查;日志里同时记 target 与 pos,比单看信号可靠得多。在TqSim或TqKq上把偏差场景跑一遍,再挂实盘,能少踩很多「以为程序坏了其实是成交慢」的坑。
FAQ
1)差 1 手要不要强行补单?
先看在途单和部分成交;无在途且拒单已排除,再set_target_volume一次即可,避免与 task 内部循环打架。
2)反手时偏差更大正常吗?
反手要先平后开,中间帧pos可能经过 0 或反向,属于正常,用日志看全过程。
3)模拟盘会对得更齐吗?
TqSim成交模型更理想,实盘偏差更大;模拟对齐不能代表实盘一定对齐。
4)能否不用 TargetPosTask?
可以全程insert_order,但要自己管开平和在途单,工作量更大。
本文基于天勤 TqSdk 公开 API 与源码整理,不构成投资建议。实盘前请在仿真环境验证。