2026年十一月3日 · 执行

我们在模拟盘上做了一笔基差交易。只成交了一条腿。

我们在模拟盘上做了一笔基差交易。只成交了一条腿。

函数名叫 enter_pack。这就该是警告了。一个研究 agent 写了一套 BTC 期现套利:永续比现货贵出超过 8 个基点时,买入现货、做空永续,然后坐着不动。资金费率和基差会把剩下的事做完。回测在同一毫秒、按中间价、再加一跳礼貌滑点,把两条腿都成交了。权益曲线是一段楼梯。没人会说它刺激。这正是卖点。

周一,我们把它推上了模拟盘。

周一:总会成交的组合

第一个信号在 11:17 UTC 落地。屏幕上的基差是 9.4 bps,永续偏贵。回测里,enter_pack 返回两笔成交,残差为 0。模拟盘上,它返回的是现货在卖一成交了 0.40 BTC,以及一笔挂在永续上、并未成交的空单。

现货付了 4 bp 的 taker 手续费,外加大约 1 bp 的价差。我们想要的永续价格还在盘口上,然后就不在了。20 秒后,报价基差变成了 5 bps,因为我们刚刚吃了现货。40 秒后永续成交了,已经是行情走完之后。我们组合成交了。我们组合成交了,但基差已经不够覆盖进出成本。

回测会记一笔「+9.4 bps 持有收益,残差 0」。成交记录上是 4 bps 的现货 taker 成本、40 秒没有对冲的多头,以及大约 5 的入场缺口。我只有 1 个样本,却仍然对那段楼梯过于自信。

周二:我们打开了那个函数

enter_pack 接收一组腿和一个目标中间价。它循环,按 mid * (1 ± slip) 成交每一条腿,然后写入一行组合持仓。没有第二个时间戳。没有「第二条腿不存在」的分支。单元测试断言的是 residual == 0。

那是一种让你觉得自己像个成年人的测试。它永远不会失败。

我们把组合拆成两张各自有状态的订单:已提交、部分成交、已成交、已取消。未完成变成了一等对象。历史回放仍然没有真实盘口,于是我们作弊。用从周一等待时间里抽一次的结果来延迟第二腿成交(均值 26 秒,有一条我不喜欢的尾部),再按那个窗口里现货的走幅去漂移它的价格。粗糙,但午饭前楼梯上已经长出了缺口:有的组合以 3 bps 入场,有的组合从未成为组合。

周三:3 秒,然后平掉

如果第二条腿在 3 秒内没有上账,就把第一条腿平掉。我们本该从这里起步。听起来很保守,直到你给中止定价。平仓意味着你吃现货进去,再吃现货出来。2 笔 taker 手续费、2 段价差,没有持有收益,没有资金费率。在改写后的回放里,这些中止并不罕见。它们扎堆出现在基差看起来最「真实」的那些 K 线上,而那些 K 线通常正是已经有东西在现货里流动的时候。

到周三夜里,我们已经有 17 个模拟信号可以争论了。

174 天内的模拟信号
7两条腿都成交的组合
18 bps残留多头后来遭遇的逆向波动
回测,周一上午模拟盘,截至周三
入场两条腿都在中间价,同一个时间戳现货在卖一成交;永续 5–40 秒后,或者永远不成交
残差永远是 017 个信号里有 10 个没有对冲
在手交易的手续费2 笔 taker2 笔 taker,中止则是 4 笔
PnL 驱动因素基差 + 资金费率等待期间现货怎么走
组合在两张订单都成交时才算完成,否则它就是一笔你并未按这个规模去下的方向性仓位。残差存在的整段时间里,都要对照实时盘口盯市。如果你盯不了市,你就不能跑这个策略。

一位同事还有一本 notebook,把组合当成一只上市产品,BTC_CARRY,外加一笔固定 2 bp 的「执行税」。我喜欢那本 notebook。它产出的楼梯和 agent 做出来的一样。税是一个均值。孤儿单是一个分布。均值不会在流动性稀薄的一小时里被碾过;剩下的 0.40 BTC 现货可以。

周四:残留,然后咖啡

一旦还要排队等待,3 秒几乎把所有东西都中止了。我们把模拟盘放宽到 15 秒。周四 02:11 UTC,现货成交了,永续没有。我还处在「它会挂着,它会被吃掉」的哀悼阶段,就去煮咖啡了。

02:11:08 成交 BTCUSDT 0.40 买入
02:11:08 已提交 BTCUSDT-PERP 0.40 卖出,post-only
02:11:41 仍在挂单
02:26:00 残差盯市 −18.1 bps

BTC 在一段安静的窗口里跌了 18 bps。残差就是整笔交易。资金费率打印出来时,贷记的是我们并没有持有的永续。现货只是坐在那里做多。原始回测的隔夜路径里从未出现过单腿仓位,所以它从未问过我们问每一本其他账本的那个问题:这段残留能打出的最差一根 K 线是什么,以及一笔我们本不打算持有的仓位,止损该放在哪里?

那 7 个完整组合也不是英雄。等到第二条腿成交时,我们自己吃现货已经压到了指数上,入场基差比触发信号的那张快照差了 3–5 bps。8 bps 的阈值只是屏幕上的一个数字。

下次我们不会再做的事

我们不会再把多腿当成一个单一成交函数上线。同样的规则适用于永续-现货套利、日历价差,或者 agent 用两张限价单提交、然后在两边都按中间价「成交」时庆祝的看涨垂直价差。

  1. 每条腿的订单状态,包括部分成交。把组合当成两张订单的父单。
  2. 一个真正会触发的最大孤儿计时器,以及一条出场要付两段价差的平仓路径。
  3. 残差按盘口盯市,止损按残留规模来设。组合净头寸是错误的名义本金。
  4. 两条腿都上账之前,不贷记持有收益,不计提资金费率。未完成就是未完成。
  5. 凡是有两张订单的东西,拒绝把中间价对中间价当作默认。如果必须猜测,就往薄的那条腿猜得更差,往第二腿成交猜得更晚。

而且我们不会让 test_pack_is_atomic 进代码库。原子性是撮合引擎的属性,不是辅助函数的属性。BTC 现货和 BTC 永续是两本有时押韵的账本。模拟盘是它们停止押韵的地方,而这正是那段楼梯曾经有趣的唯一原因。

回测期现套利执行模拟交易加密货币期货
← 全部文章