前言国内期货是按月挂牌交易的螺纹钢同时有 2505、2510、2511 等多个具体合约其中成交量最大的那个月份叫主力合约。每隔一段时间市场注意力会从近月移到远月主力就会换月。对做程序化的人来说换月那几天特别容易出事研究时用的是主连曲线实盘却要下在具体月份上旧月份里还有持仓没平掉程序已经对着新月份set_target_volume(3)结果新旧两月同时持仓保证金和风险度一起飙升。天勤量化TqSdk是国内常用的 Python 期货量化框架。它的核心对象是TqApi你创建一次TqApi在里面订阅行情、查持仓、下单整个程序靠反复调用wait_update()推进。主连合约在代码里常写作KQ.mSHFE.rb它的行情对象上有一个字段underlying_symbol表示当前主力对应的具体可交易合约。换月程序化的关键就是盯住这个字段何时变化并据此完成移仓而不是假装合约永远不变。一、先弄清几个名词名称是什么为什么会出现换月问题具体月份合约如SHFE.rb2510交易所真实可交易标的到期、流动性转移主力会换到别的月份主连KQ.m把历史上各阶段主力拼接成连续 K 线方便看长期趋势研究好用但不能直接当作单一合约的真实成交价TqApi天勤主连接变量常写作api订阅、交易、查询都通过它需api.close()释放get_quote(代码)取得某合约实时行情快照主连 quote 上有underlying_symbolunderlying_symbol主连当前指向的具体合约代码换月时这个字符串会变程序若不处理就会对错合约wait_update()阻塞等待一批业务数据更新不调用它内存里的行情和持仓都是旧的get_kline_serial订阅 K 线表表里有datetime列信号若在具体月上算换月后要改订阅的 symbolTargetPosTask天勤调仓工具把净持仓调到目标手数每个具体合约通常只能有一个 task 实例set_target_volume(n)设置目标净仓为 n 手真正发单在后续wait_update里换月要对旧月 set 0、对新月 set 目标get_position查柜台认可的持仓移仓是否完成以pos.pos为准pos.pos净持仓正为多、负为空旧月不为 0 就不能算移仓完成二、换月时程序里实际会发生什么现象原因若不管会怎样underlying_symbol从 rb2505 变成 rb2510交易所主力切换仍对旧月下单流动性差、滑点大或拒单主连 K 线连续、具体月 K 线有断档不同合约历史长度不同信号 bar 与执行合约对不上旧月pos.pos仍大于 0移仓未完成新旧两月叠仓保证金占用翻倍主连价格是拼接出来的研究序列不能默认它等于某一个具体合约从上市到退市的全部真实成交路径。实盘执行必须跟踪具体合约换月就是执行层重新对齐的过程。三、underlying_symbol 怎么读、为何刚订阅可能为空fromtqsdkimportTqApi,TqAuth,TqSimfromtqsdk.libimportTargetPosTask apiTqApi(TqSim(),authTqAuth(快期账户,密码))contKQ.mSHFE.rbq_contapi.get_quote(cont)whileTrue:api.wait_update()trade_symq_cont.underlying_symboliftrade_sym:break说明KQ.mSHFE.rb里SHFE是上期所rb是螺纹钢品种KQ.m表示主力连续。刚get_quote后各字段可能还是空的必须wait_update()收到行情包才有值这是天勤文档明确提醒过的。trade_sym形如SHFE.rb2510才是你应用TargetPosTask和get_position的执行合约。策略里应维护变量current_trade_sym每个交易日开盘或每次主循环检查trade_sym ! current_trade_sym若变化进入移仓流程不能静默改代码里的字符串却不平旧仓。四、移仓流程先平后开还是分批两种常见写法团队选一种写进策略说明书先平后开在旧 symbol 上set_target_volume(0)循环wait_update直到get_position(旧).pos 0再对新 symbol 创建TargetPosTaskset_target_volume(目标)。分批换月在数个交易日内逐步把仓位从旧月迁到新月降低单日冲击成本。注意天勤约束同一具体合约只能有一个TargetPosTask单例。换月后对新trade_sym建 task旧月 task 在平仓完成后不再设置新目标。若对同一 symbol 用不同的price或offset_priority重复创建 task会直接抛异常。defon_roll(old_sym,new_sym,target,tasks):ifold_symintasks:tasks[old_sym].set_target_volume(0)# 此处应循环 wait_update直到 get_position(old_sym).pos 0ifnew_symnotintasks:tasks[new_sym]TargetPosTask(api,new_sym,priceACTIVE)tasks[new_sym].set_target_volume(target)priceACTIVE表示对价下单买用卖一、卖用买一换月求成交时常用若改用PASSIVE排队在换月流动性混乱时可能久不成交。五、K 线订阅要不要跟着切信号来自主连 K 线、执行用underlying_symbol主连序列换月时内部标的会变通常不必重订主连get_kline_serial但要盯执行合约是否切换。信号来自具体月 K 线换月后应改get_kline_serial(new_sym, 周期秒数, data_length...)并重新做指标就绪判断——新月份历史 K 线变短均线可能出现 nan在 nan 上不能交易。日志建议记录换月日期、旧 sym、新 sym、移仓前后pos.pos、耗时便于与期货公司账单核对。六、模拟与回测注意TqSim天勤内置模拟账户、TqKq快期模拟换月行为依赖行情服务换月周应在模拟盘完整跑一遍移仓。回测若用主连换月附近成交假设与具体月实盘会有偏差样本外应用具体月再验证。总结主力换月那几天程序要把underlying_symbol的变化当成和「信号触发」同级的重要事件先发现切换再平旧月、对新主力恢复目标仓而不是继续对过期主力下单。天勤用主连做研究、用具体月做执行是常见分工TqApi里wait_update驱动一切更新TargetPosTask负责把净仓推到目标。把移仓顺序、超时与日志写进配置并在模拟盘演练一遍换月周就不容易变成持仓翻倍或流动性踩坑的突发事故。FAQ1underlying_symbol 长时间为空继续wait_update检查是否休市、网络是否正常、主连代码是否写对。勿在空字符串上下单。2换月当天信号和旧仓方向相反先处理旧仓风险风控优先再跟新信号避免两头加仓。3能否同时持有新旧两月做价差那是套利策略不是移仓两腿各自TargetPosTask比例单独管理。4个人投资者第一次写换月逻辑先用TqSim模拟打印每次underlying_symbol变化人工对照行情软件主力是否一致。风险提示以上内容用于移仓流程参考不构成投资建议。