期货 K 线算信号 tick 级止损:天勤双序列 wait_update 触发规则
前言
国内期货趋势量化里,开仓信号多在 K 线上算:天勤程序get_kline_serial("DCE.m2509", 300)订豆粕 5 分钟线,均线金叉时TargetPosTask.set_target_volume(5)开多。止损若也等下一根 K 线收盘才检查,夜盘一波急杀时,可能要等 5 分钟才发现亏损扩大,比 tick 级止损多滑很多个最小变动价位。去年改一条豆粕策略:开仓逻辑在 K 线,止损改到 tick(或quote.last_price)触发,回撤明显收敛,代价是主循环要分清「谁有权改 target」。
天勤允许同一TqApi里同时订get_kline_serial和get_tick_serial,共用api.wait_update()收包。下面说明 K 线管开仓、tick 管止损的触发分工,避免两者同帧打架。
一、两个序列各自负责什么
| 序列 | API | 触发粒度 | 适合逻辑 |
|---|---|---|---|
| K 线 | get_kline_serial(symbol, duration_seconds, data_length) | 新 bar | 开仓、加减仓信号 |
| Tick | get_tick_serial(symbol, data_length) | 每笔成交聚合 | 止损、移动止盈 |
get_tick_serial返回 tick 表,含datetime、last_price、volume等(以objs.py为准)。tick 表也会随wait_update更新,用is_changing(ticks.iloc[-1], "datetime")判断新 tick。
二、主循环结构示意
原则:开仓权在 K 线,平仓权可拆给 tick;同一帧只应有一个模块写 target。
klines=api.get_kline_serial(SYMBOL,300,data_length=500)ticks=api.get_tick_serial(SYMBOL,data_length=2000)task=TargetPosTask(api,SYMBOL,price="ACTIVE")target=0entry_price=Nonestop_price=NonewhileTrue:api.wait_update()# K 线:新 bar 算信号(用已收盘 bar)ifapi.is_changing(klines.iloc[-1],"datetime"):bar=klines.iloc[-2]signal=calc_signal(klines)# 你的均线/突破逻辑ifsignal==1andtarget<=0:target=3entry_price=bar["close"]stop_price=entry_price-2*quote.price_tick*10task.set_target_volume(target)# Tick:仅在有仓时检查止损iftarget!=0andapi.is_changing(ticks.iloc[-1],"datetime"):last=ticks.iloc[-1]["last_price"]iftarget>0andlast<=stop_price:target=0task.set_target_volume(0)eliftarget<0andlast>=stop_price:target=0task.set_target_volume(0)quote需api.get_quote(SYMBOL)提前取得,用于price_tick。
三、常见坑
- tick 止损与 K 线反向信号同帧触发:应定优先级,通常止损优先于开新仓。
- 未用
is_changing过滤,每个包都扫 tick,CPU 升高。 data_length太小,tick 表被截断,极端行情下指标辅助字段丢失。- 停盘期间 tick 不更新,止损应暂停或改用 last_price 快照规则。
- 同一合约混用
insert_order与TargetPosTask,tick 层改 target 后 task 状态错乱。
四、止损价维护
移动止损可在 tick 分支更新stop_price,但不要每 tick 调set_target_volume;只有触发击穿时才调仓。若用 ATR 止损,ATR 仍建议用 K 线算,tick 只比较价格与阈值。
五、回测与实盘差异
TqBacktest对 tick 精度取决于订阅与回测设置;K+tick 双序列在回测里能跑通,但 tick 级止损的成交假设仍比实盘理想。应用TqSim对价模式试一轮,记录止损滑点。
六、用 quote.last_price 的轻量替代
若觉得get_tick_serial订阅太重,可以只对交易品种get_quote,在止损分支判断api.is_changing(quote, "last_price")。这在不少商品上足够驱动止损,但要注意:行情推送频率低于真实 tick 时,止损会有延迟。股指等高频品种仍建议 tick 表。
quote=api.get_quote(SYMBOL)whileTrue:api.wait_update()iftarget>0andapi.is_changing(quote,"last_price"):ifquote.last_price<=stop_price:target=0task.set_target_volume(0)七、状态机:避免双写 target
维护显式状态FLAT / LONG / STOPPING比仅用target整数更安全。进入STOPPING后忽略 K 线开仓信号,直到pos确认为 0 再回FLAT。这能避免 tick 止损与 K 线反向信号同帧打架。
| 状态 | 允许 K 线开仓 | 允许 tick 止损 |
|---|---|---|
| FLAT | 是 | 否 |
| LONG/SHORT | 否(除非加仓规则) | 是 |
| STOPPING | 否 | 否 |
总结
K 线算信号、tick 管止损,是国内期货程序化里很常见的分工。天勤允许在同一TqApi里并行get_kline_serial与get_tick_serial,靠wait_update统一收包,用is_changing把两种触发拆开;开仓逻辑放在新 K 线,止损逻辑放在新 tick,且避免双写 target。把优先级和停盘过滤写清楚,比单纯把止损周期改成 1 分钟 K 线更接近真实风控意图。
FAQ
1)tick 数据量很大怎么办?
缩小data_length,止损只关心最新价;或用is_changing(quote, "last_price")替代全表扫描。
2)能否只用 quote 不用 tick_serial?
可以,get_quote的last_price更新也能驱动止损,粒度取决于行情推送频率。
3)多品种怎么订?
每个交易品种各一对 serial;watch 列表外的品种不要订 tick。
4)反手算开仓还是止损?
建议拆成先平后开两帧,由 task 自动处理开平顺序。
本文基于天勤 TqSdk 公开 API 整理,不构成投资建议。
