加密市場的智慧訂單路由:一筆訂單,十二個場所,沒有 NBBO
你需要買入 400 BTC。Binance 的深度最好。OKX 和 Bybit 各自在寬一個 tick 的位置顯示出不錯的量。Coinbase 報出更好的表面價格,但計價是 USD,不是 USDT。Kraken 的盤口頂端看起來棒極了——而且已經陳舊了 400 毫秒。Upbit 更好,但你在韓國的合規批文並不存在,你的 KRW 也不存在。一個股票交易員面對這團亂局,會伸手去用智慧訂單路由器,然後就不用再想了,因為在股票市場,路由中最難的部分早在二十年前就被監管壓進了基礎管道里。在加密市場裡,你自己就是那管道。沒有合併行情,沒有訂單保護規則,沒有費用上限,沒有淨額結算——而且最根本的是,你無法在你的資本尚未存放的地方進行交易。
本文講的是——即便如此,依然要把路由器造出來:合併訂單簿,以及為什麼對著它做樸素的最優價路由會虧錢;把配置問題作為一個你確實能在子訂單截止時限內求解的凸規劃來處理;那條使加密 SOR 與資金庫管理密不可分的資本約束;跨場所的 maker 感知路由;以及那條能告訴你這一切是否奏效的 TCA 反饋閉環。它是 用 Rust 實現複雜套利執行 的工程兄弟篇——那篇講的是納秒;這一篇講的是決策。
股票 SOR 免費得到了什麼
值得精確地說清楚,美國股票路由從監管那裡繼承了什麼,因為清單上的每一項,都是你必須重建、或有意識地在缺少它的情況下過活的東西。
Regulation NMS(SEC,2005)做了三件在這裡重要的事。Rule 611,即訂單保護規則,禁止以劣於另一家交易所所顯示的受保護報價的價格成交——即"穿價成交"(trade-through)——這迫使每個經紀商要麼把訂單路由到最優顯示價,要麼用跨市場掃單(intermarket sweep orders)把它掃掉。Rule 610 把任何場所對拿取受保護報價所能收取的准入費上限設為每股 $0.003,於是各場所的顯示價可以在 30 mils 以內相互比較。而合併行情(那些 SIP)會發布全國最優買賣報價(National Best Bid and Offer),對"市場是什麼"給出一個單一的官方答案。
學術界對這套架構的裁決是:它出人意料地有效。O'Hara and Ye(2011),"Is market fragmentation harming market quality?"(Journal of Financial Economics 100(3), 459–474),考察了不同碎片化程度下的美國股票,發現碎片化程度更高的股票反而有更低的交易成本和更快的成交,價格也更接近隨機遊走。他們的總結是那句關鍵的話:美國股票的行為像"一個帶有多個入口的單一虛擬市場"。當智慧路由器加上穿價保護把碎片重新縫合起來時,碎片化是無害的。Foucault and Menkveld(2008),"Competition for Order Flow and Smart Order Routing Systems"(Journal of Finance 63(1), 119–158),在荷蘭市場揭示了這一機制:當第二個限價訂單簿(LSE 的 EuroSETS)進場與 Euronext 競爭時,合併深度反而增加了,而一個場所的流動性供給會被其穿價率直接抑制——忽略某個場所的路由器,會扼殺它報價的動力。
即使在這個受監管的世界裡,合併檢視在短時間尺度上也是一個謊言。Ding, Hanna, and Hendershott(2014),"How Slow Is the NBBO? A Comparison with Direct Exchange Feeds"(Financial Review 49(2), 313–332),把 SIP 的 NBBO 與用同一資料中心內的交易所直連行情構建的 NBBO 做了對比,發現在活躍標的上,每秒會出現數次錯位,通常持續一到兩毫秒——純粹是聚合與傳輸延遲造成的。記住這個數字:它是這樣一個問題的股票版本,而這個問題在加密裡要嚴重一到兩個數量級。
現在把這一切全部刪掉。加密沒有 NBBO,因為沒有 SIP。沒有穿價規則:一個場所會樂呵呵地在穿過另一場所報價五個 tick 的價位上給你成交,而且沒人會去申報任何東西。沒有費用上限:taker 費從不到一個基點的協商分層到 10 bps 的零售費率不等,於是顯示價排序與淨價排序經常相互矛盾。也沒有合併清算:每家交易所都是自己的孤島,各自持有預注餘額。你同時是 SIP、路由器和清算公司。
構建合併訂單簿,以及為什麼樸素的最優價路由會失敗
工程基線並不光鮮:N 路 WebSocket L2 行情、分場所的序列號缺口處理、符號與 tick 大小歸一化,以及計價貨幣歸一化(一個 BTC-USD 訂單簿和一個 BTC-USDT 訂單簿相差一個 USDT/USD 匯率,而這個匯率並不恆等於 1.0,偶爾還很不等於 1.0)。把歸一化後的訂單簿合併成一個按價格排序的階梯,給每個價位標註它的來源場所,以及——至關重要地——它所來自的快照的年齡。如果你合併後的訂單簿沒有把分場所報價年齡當作一等欄位來攜帶,那你造出來的是屏保,不是路由器。

樸素路由器貪婪地在這個合併階梯上行走:先取最優淨價。它會因三個各不相同的原因而失敗,值得把它們分開來看,因為修復方式各不相同。
陳舊報價與延遲偏斜。 你的各個場所並不以相同的延遲送達資料。一個同機房行情在你據其行動時可能只有 3 ms 的年齡;而一個來自另一大洲場所的公開 WebSocket 可能已經陳舊了 300 ms。因此合併後的盤口頂端是不同過去時刻的一個混合體。當 BTC 在 200 ms 內變動 10 bps——這是家常便飯——那個陳舊場所的報價,會恰好在錯誤的一側,系統性地顯得誘人。把訂單路由到它們那裡,買來的是一場你早已輸掉的競速:報價已經消失,你的 IOC 空手而歸或只有部分成交,而等你重新路由時,新鮮的場所早已重新定價。這就是 Ding–Hanna–Hendershott 那個 1–2 ms 的 SIP 錯位問題,只不過你的錯位持續數百毫秒,而且沒有任何人有義務兌現任何東西。
幻影流動性。 把各場所的顯示量加總會高估,因為同一個做市商的存貨被同時報在好幾個地方。Van Kervel(2015),"Competition for Order Flow with Fast and Slow Traders"(Review of Financial Studies 28(7), 2094–2127),在碎片化的股票中記錄了這一點:在一個場所上的一筆成交,會在數毫秒內伴隨著競爭場所上大量限價單的撤單,這恰恰符合這樣一個模型的預測——快速流動性提供者在各處報出重複的量,一旦某處被擊中就把這些副本撤走。加密做市商在 Binance/OKX/Bybit 上跑的是同一套劇本,所以可觸達的合併深度實質上小於顯示的合併深度,而且你越是順序地(而非同時地)去打這些場所,這個缺口就越大。如果你的路由器一次向一個場所傳送子訂單,等待每一筆成交確認,那你就是在割自己的韭菜:每一筆成交都在向街上其他人發訊號去撤單。
費用會重排階梯。 一個顯示出最優原始價、卻帶 7.5 bps taker 費的場所,往往是訂單簿中最差的淨價。這聽起來明顯得都不值一提,可"最優顯示價"路由,恰恰是大多數第一代加密路由器(以及若干廠商產品)所實現的東西。扣費後比較是最低門檻;兄弟篇 maker-taker 費用與返佣 講了分場所的費用數學、VIP 分層動態,以及為什麼應該進路由器的是你的邊際費率——而不是標價。
路由最佳化
把子訂單問題形式化。你必須現在、以可成交方式,跨越場所 買入數量 。設 為在吃掉場所 訂單簿的 個單位後其邊際賣價(來自 L2 快照的一個非遞減階梯函數), 為其 taker 費, 為對場所 的陳舊性與逆向選擇的每單位懲罰(在下文中,由你自己的 markout 校準)。配置 求解
每個 都是凸的(一個非遞減函數的積分,加上一個線性項),所以該問題是凸的,而 KKT 條件道盡了整個故事:存在一個閾值 ,使得
而對於分不到任何量的場所,。用一句話說:像倒水一樣把訂單鋪到各場所上,在你所交易的每一處把邊際全包成本拉平。一個場所被排除,恰恰是在它的第一個單位——最優價,加費用,加陳舊性懲罰——都比別處的邊際單位更差的時候。

對於階梯函數訂單簿,水填充解是通過在經費用與懲罰調整過的合併階梯上做一次貪婪行走計算出來的——所以貪婪式深度行走本身並沒有錯;它正是精確解,前提是你貪婪行走的是淨邊際成本、用的是打折後的量,而不是原始顯示價。這個區別,就是路由器與屏保之間的全部差異。
對這個一般問題的經典處理是 Cont and Kukanov,"Optimal order placement in limit order markets"(Quantitative Finance 17(1), 21–39, 2017; arXiv:1210.1625)。他們把跨場所的訂單下達——包括限價單與市價單之間的拆分、費用與返佣,以及一個對執行風險的懲罰——形式化為一個凸最佳化,推導出單場所限價/市價拆分的顯式閉式解,併為多場所情形給出一個隨機逼近演算法,能在不到 200 ms 內計算出跨越十二家交易所的配置。這個框架早於加密而出現,卻幾乎原封不動地遷移過來,因為它從一開始就不曾假設有 NBBO——它只假設了分場所的訂單簿、分場所的費用,以及成交的不確定性,而這恰恰就是加密的處境。
可成交一側(帶陳舊性折價的凸水填充)的一個精簡、誠實的示意:
import math
from dataclasses import dataclass
@dataclass
class Venue:
name: str
asks: list[tuple[float, float]] # (price, displayed size), sorted
taker_fee: float # fractional, e.g. 0.0002 = 2 bps
quote_age_ms: float
kappa: float # phantom-liquidity decay rate, 1/ms
lam: float # staleness/toxicity penalty, $ per unit
def allocate(venues: list[Venue], Q: float):
ladder = [] # (marginal all-in cost, accessible qty, venue)
for v in venues:
surv = math.exp(-v.kappa * v.quote_age_ms) # P(level still there)
for price, size in v.asks:
cost = price * (1.0 + v.taker_fee) + v.lam
ladder.append((cost, size * surv, v.name))
ladder.sort()
fills, remaining = {}, Q
for cost, qty, name in ladder:
take = min(qty, remaining)
fills[name] = fills.get(name, 0.0) + take
remaining -= take
if remaining <= 1e-12:
break
return fills, remaining # remaining > 0 => book too thin: slice parent
十六行邏輯;全部的智慧都活在輸入裡。(顯示量隨報價年齡蒸發得有多快)是從你自己的 IOC 成交率、作為發單時報價年齡的函數校準出來的。 來自分場所的 markout(見最後一節)。兩者都是測出來的,不是猜出來的。
一個算例。 跨三個場所買入 BTC:
| 場所 | Taker 費 | 報價年齡 | 賣單(價格 × 量) |
|---|---|---|---|
| A(深、新鮮) | 2.0 bps | 10 ms | 87,000 × 3.0; 87,010 × 4.0; 87,025 × 6.0 |
| B(費便宜、陳舊) | 1.0 bps | 250 ms | 86,995 × 1.5; 87,015 × 2.0 |
| C(表面最優、費高) | 7.5 bps | 20 ms | 86,990 × 2.0; 87,000 × 3.0 |
原始行情說 C 有最優賣價(86,990),其次是 B。做費用調整,階梯就徹底重排:C 的頂層淨價為 ,是螢幕上最差的流動性。B 的頂層淨價 87,004,A 的淨價 87,017。對 B 的顯示量施加 30% 的陳舊性折價()再做水填充:從 B 的第一層取 1.05 BTC,從 A 的第一層取 3.0,從 B 的第二層取 1.4,從 A 的第二層取 4.0,從 A 的第三層取 0.55。全包均價:**每 BTC 87,038.8:差 1.9 bps,在一個 10-BTC 的子訂單上約 $166,而這會在一整天裡每一個母單的每一個子單上覆利疊加。請注意這個包袱點:在行情上有著最優顯示價的那個場所,從最佳化後的路由器那裡得到了零流量。在加密裡,沒有人強迫你在那裡交易——路由穿過一個"受保護報價"根本不是一個概念——而正確的配置經常乾脆完全忽略那個表面上的最優價。
那個凸規劃仍然忽略的東西:同時性(要在同一毫秒裡把所有場所的子單打出去,否則 van Kervel 的撤單會在執行中途重新給場所定價)、離散手數與最小名義額(把連續解取整,再貪婪地修補),以及根本不去跨價差的這個選項——那是第 5 節的主題。至於在這些場所邏輯執行之前,大額母單應當如何隨時間被切片這個更深層的問題,見 Almgren–Chriss 最優執行;SOR 決定的是一個子單去哪裡,不是子單何時發生。
資本約束:SOR 就是資金庫管理
上面的一切都默默地假設了你能夠在場所 上交易 。在股票市場,這個假設是免費的:一個主經紀商、淨額結算,現在交易、稍後再挪錢。在加密裡,它是整個系統的約束繫結項。交易所要求預注餘額——你不能用停在 Binance 上的 USDT 去吃 Kraken 的賣價。所以真正的問題是
其中 是你在場所 上的可用餘額(買入時用計價資產計,賣出時用基礎資產計)。現在 KKT 條件寫作 ,其中 是餘額上限的乘子。在觸及上限的場所上,:那裡的邊際一美元執行起來比全市場的水位更便宜,而你卻被迫把流量推向更貴的場所。那個乘子不是抽象概念—— 字面上就是如果場所 此刻多存在一個單位的餘額,你每單位能省下多少美元。在你預測的流量上求和,它就是你為一次再平衡轉賬所願意付出的金額,而再平衡決策就變成任何資金庫系統都能執行的一個比較:在如下情形挪動存貨
右邊既不小,也不恆定。鏈上 BTC 需要 2–6 個確認(20–60 分鐘)交易所才會入賬;ERC-20 轉賬要幾分鐘外加 gas,而 gas 恰恰在市場繁忙時飆升;TRC-20 和 Solana 通道更快更便宜,卻並非處處都支援;而每家交易所又都加上自己的提現處理佇列,它會從幾分鐘拉長到幾小時——恰恰是在波動事件期間,而那正是你的路由器最想把存貨挪走的時候。完整的轉賬成本算術——費用、延遲分佈,以及你在途中承擔的價格風險——在 資金費率跨交易所套利那篇文章 裡已經算清楚了,而它可以逐字遷移過來:一次 SOR 再平衡與一次套利腿的轉賬是同一個物件,連成本一側都一樣。
這也是為什麼在這裡要內化的學術成果不是一篇執行論文,而是 Makarov and Schoar(2020),"Trading and Arbitrage in Cryptocurrency Markets"(Journal of Financial Economics 135(2), 293–319)。他們記錄了持續數天到數週的跨交易所價格偏離——包括 2018 年初一度超過 40% 的韓國"泡菜溢價"——並證明交易成本無法解釋它們;能解釋的是緩慢移動、受資本管制的套利資本。加密場所不是 O'Hara–Ye 那個"帶有多個入口的單一虛擬市場"。它們是由緩慢、昂貴的管道連線起來的部分分割的池子,而你的路由器就活在那種分割之中。一個沒有資金庫模型的加密 SOR,是一個 cosplay 的股票 SOR。

在實踐中,這會變成一個雙時間尺度的控制器。快環(毫秒級)在當前餘額之內,為每一個子訂單求解那個受約束的水填充。慢環(分鐘到小時級)盯著影子價格 的時間序列和預測流量,並在 的持續性分量清過轉賬成本門檻時安排轉賬——帶有滯回,因為在噪聲上把存貨在場所之間來回乒乓,正是你把自己的優勢捐給 Tron 網路的方式。機構交易臺用場外結算(Copper ClearLoop、Ceffu MirrorX)來壓縮這個問題:抵押品放在託管方那裡,並被映象到各場所,這對受支援的場所大幅縮短了轉賬延遲——它收窄了約束,但並不刪除約束,而且它引入了自己的一條交易對手方費用項。
Maker 感知路由與跨場所排隊博弈
一個只會跨價差的路由器,是把最便宜的流動性——你自己的——留著沒買。Cont–Kukanov 框架早已包含答案——他們的單場所閉式解,基於費用、排隊位置與執行風險厭惡,把一筆訂單在掛單與拿單之間拆分——而多場所版本對它做了推廣:在(maker 費、排隊長度、子單截止時限內的成交機率)佔優的場所上被動掛單,在即時性便宜的場所上拿單,並把未成交的被動殘餘,當作在截止時刻重新進入 taker 最佳化的流量來處理。
兩個加密特有的皺褶,使這件事比股票版本更豐富。
追逐費用是一個被測量出來的陷阱。 Battalio, Corwin, and Jennings(2016),"Can Brokers Have It All? On the Relation between Make-Take Fees and Limit Order Execution Quality"(Journal of Finance 71(5)),證明把限價單路由到返佣最高場所的美國經紀商,交付的成交在可測量的意義上更差——更低的成交率、更差的實現質量——因為那個返佣場所正是每一個其他返佣追逐者的訂單也待著的地方:最長的隊伍、最逆向的成交。加密裡的對應完全一致。付出最優 maker 返佣的場所,吸引來每一個做市商的被動報價;你的訂單加入一條深長的隊伍,而它主要在價格即將穿過你時成交。分場所的扣除 markout 後的 maker 經濟學(見下一節),經常把各場所的排名排得與其費率表恰好相反。
延遲偏斜是一場雙向博弈。 van Kervel 記錄為防禦性撤單的那同一股跨場所資訊流,從另一側看是一個進攻性訊號:Binance 盤口頂端的一筆成交,預示著 OKX 對應價位在數毫秒內的成交與撤單。因此一個 maker 感知的路由器必須(a)在其他場所的事件上重新給自己掛著的訂單定價——錨定到一個跨場所的微觀價格,而不是本地中間價——否則它就會變成那個被跨場所套利者宰割的慢速交易對手;並且(b)它可以刻意地玩這場博弈:在滯後的場所上掛單,在成交的那一刻在領先的場所上對沖。那就是跨場所排隊套利,它與 跨交易所套利執行 中被利用的延遲結構是同一個,只不過嵌在一個執行委託而非一個統計套利賬本之內。營運要求是完全一致的:場所 A 上的成交事件與發往場所 B 的對沖單,必須活在同一條個位數毫秒的程式碼路徑裡,否則那份優勢就歸了別人。
度量路由質量:markout 與排行榜
股票路由器受 Rule 605/606 披露規範的約束。沒有任何東西約束你的路由器,除了你自己的 TCA——來自 執行落差與 TCA 的框架就是記分牌;這裡是它專屬於路由器的那一片。
原子級的度量是分場所 markout:對每一筆成交,記錄在 時刻(對 )的(合併的、延遲校正後的)中間價,並帶符號,使得負值意味著市場朝著不利於你成交的方向變動。彙總成一張全包排行榜:
| 場所 | Taker 成交佔比 | 有效價差 (bps) | 費用 (bps) | 1s Markout (bps) | 全包成本 (bps) |
|---|---|---|---|---|---|
| A | 46% | 1.4 | 2.0 | −0.6 | 4.0 |
| B | 31% | 1.9 | 1.0 | −2.1 | 5.0 |
| C | 23% | 1.2 | 7.5 | −0.4 | 9.1 |
費用那一列說 B 是便宜的場所。全包那一列說 B 是貴的場所:它那些陳舊報價專挑市場已經在朝它們穿過時把你選擇性成交,而 1 秒的 markout 把這筆賬收了回來。這種反轉——費用排名對有效成本排名——是各交易臺第一次構建這張表時最一致的發現,而它恰恰就是應該回填進路由器的那個數字:凸規劃裡的 懲罰項,就是那個持續性的分場所 markout 虧空,被測量、被平滑、被更新。路由器與 TCA 構成一個閉環,否則兩者都不奏效。
一個方法論上的陷阱:從一個執行中的路由器構建出來的排行榜,會被選擇偏差汙染。如果路由器已經把困難的、有資訊含量的流量送去深度場所,把容易的流量送去便宜場所,那麼深度場所的 markout 就會顯得不公平地糟。乾淨的修法是刻意的隨機化——把百分之幾的子單以均勻隨機方式路由(一個 -greedy 路由器,量化交易者會認出這是一個帶凸最佳化先驗的 bandit)——這樣一個反事實就存在了。它在隨機化的那一小片上要花掉一些基點,而它是讓其餘 95% 的流量可被證明地路由良好的唯一途徑。
於是,這套技術棧是:一個延遲誠實的合併訂單簿;一次在淨邊際成本上、帶測量出來的蒸發與毒性參數的凸水填充;一個受餘額約束的快環,其影子價格驅動一個感知轉賬成本的慢環;尊重排隊與跨場所資訊流的被動掛單;以及隨機化的、基於 markout 的 TCA,把每一個參數反饋回去。這些元件沒有一個是單獨看很深的。系統才是——因為在加密裡,不像股票,沒有任何監管者替你造好其中任何一層,而市場會對每一個子訂單收取 2 bps,永遠收下去,直到你自己動手為止。
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.