2026年九月2日 · 微觀結構

限價單沒成交:談排隊順位、maker 回測,以及你憑空算出的 Sharpe

限價單沒成交:談排隊順位、maker 回測,以及你憑空算出的 Sharpe

你上週把筆記本傳給我:同一個訊號、同一個標的範圍、同樣 14 個月的 BTCUSDT 永續合約資料。唯一改變的是執行方式。你不再跨價差成交,而是把買單掛在價差內一個 tick,Sharpe 就從 0.42 升到 2.14。你問我這是不是真的,還是你哪裡弄壞了。

你確實弄壞了某個地方。我想帶你一步步找出問題,因為這個錯誤只在一行,背後的教訓卻遠不止這一行。

這是你從引擎裡貼出來的成交規則:

if bar.low <= limit_price: fill(limit_price)

這段程式的意思是:如果市場在這一分鐘內有成交價來到我的價格或更低,我就以我的價格成交。實際上它編碼的是價格碰到你的價位。碰到不等於成交。兩者之間隔著一整條排隊隊伍,而你的模型把隊伍當成空的。

排在你前面的是什麼

當你在 BTCUSDT 永續合約的 84,120.0 掛出買單時,你會排到當時已掛在那裡的所有委託後面。這個合約的最佳買賣價檔位通常有 4 到 30 BTC,視時段而定;你的樣本期間中位數約為 12 BTC。你的委託只有 0.4 BTC。要讓你成交,賣方必須在你還掛著、而且價位尚未被撤單清空或被市場向上突破之前,以足夠的總量打到該價位,清掉所有比你早到的人。

所以回測該問的不是「價格有沒有到 84,120.0」,而是「我的委託掛在那裡時,有沒有至少 12.4 BTC 的市價賣單在 84,120.0 成交」。這是截然不同的事件。在你的資料裡,兩者發生的次數相差約 3 倍。

12.4 BTC觸價時排在你前面的委託量中位數
100%回測假設的成交率
31%觸價後確實清空你前方隊伍的比例
4,180筆交易實際只剩約 1,300 筆

3 種成交規則,3 種不同策略

我用相同進場訊號和 3 種執行模型重新跑了你的策略。alpha、手續費和資金費率都相同,只有成交邏輯不同。

成交規則成交筆數成交後平均 60 秒 edgeSharpe
觸價:low <= limit4,180+2.6 bp2.14
嚴格穿價:low < limit - 1 tick1,712+0.4 bp0.61
依價位成交量模擬排隊1,306+0.9 bp0.77

中間那一列是大家最先想到的簡易修正:只有市場價格嚴格穿過你的價格才算成交,理由是既然價格越過你,就表示你的委託一定被吃掉了。方向上沒錯,也能戳破大部分幻想。不過這種做法有個嚴重偏差,稍後我會說明。

最下面那一列才是你該採用的做法。不需要 L3 資料,也不用逐筆重建委託簿。你手上已經有大部分需要的資料。

用 aggTrades 建立排隊模擬

取得聚合成交串流,而不是 K 線資料。每筆成交都帶有價格、數量、時間戳記和 maker 方向標記,可以看出主動方是買方還是賣方。這些資訊足以為你自己的被動委託做出堪用的模擬:

  1. 委託送出時,記錄你掛單價位的掛單量。如果你只有 100ms 的委託簿資料,就用最近一次快照;和你正在修正的誤差相比,這點誤差很小。
  2. 設定queue_ahead = resting_size。採取保守假設,把自己排在最後。除非是你建立了這個價位,否則你本來就會排在最後。
  3. 逐筆檢查成交串流。每一筆價格等於或低於你買價的主動賣出成交,都會按成交量扣減queue_ahead。數值降到負數時,你就在那個時間點以掛單價成交。
  4. 如果價格離開一個 tick,不要把排隊量重設為 0,而是讓它逐漸衰減。價位失去關注後,排在你前面的一些委託會撤掉,另一些則不會。根據我的重建結果,離開最佳價位每滿 1 秒衰減 30-40%,比假設完全不撤單或全部撤單都更接近實際情況。
  5. 如果你的策略會撤單後重新掛單,就把重新掛單視為新委託,排在新價位的隊伍末尾。很多人會漏掉這一步,而剩下的幻想就藏在這裡。

你可能會想質疑第 2 步。沒錯,有時你確實會排在前面,因為你是在該價位剛形成時就掛單。可以,那就實際記錄,不要自行假設。記下掛單時前方的委託量,讓資料告訴你有多少委託真的很早掛上。你的策略裡這個比例是 11%,因為你的訊號是在價格變動後才觸發,也就是你加入的價位早已形成,上面也早已有一群人在排隊。

你成交的,往往是你希望沒成交的那些

接下來才是最重要的部分,也是嚴格穿價規則會產生偏差的原因。

想想你的買單什麼時候會被完全吃掉。當賣壓大到足以吃完整個價位時就會發生。換句話說,依照定義,這正是市場向下穿越你價格的時刻。你最能確定成交的那些情況,正是成交後立刻處於不利位置的時候。

依成交發生的方式拆分成交紀錄,再計算 60 秒 mark-out:

成交類型成交占比60 秒 mark-out
價位有成交,價格反彈上漲38%+3.1 bp
價位有成交,價格持平21%+0.2 bp
價格穿越 2 個或更多 tick41%−2.4 bp

你的簡化回測納入了這 3 種情況,卻把它們全都當成免費成交。嚴格穿價規則幾乎只會納入第 3 種情況,因此 edge 比排隊模擬更大幅下滑。兩者都不正確。排隊模擬能給你合理的情況組合,而這個組合就是策略的關鍵:被動執行讓你賺到價差,也讓你承受逆向選擇;兩者的比例才是策略真正的樣貌。

以前股票交易部門的說法至今仍適用:maker 委託是你賣給市場的一份免費選擇權。市場會在對自己有利時行使它。你的回測收了權利金,卻忘了這份選擇權還有一筆到期支付。

沒成交的交易改變的是策略本身,而不只是成本

這是我最希望你仔細想想的部分。當 taker 執行模型不準時,你拿到的交易仍然正確,只是價格不對,修正手續費後大多就能補救。當 maker 執行模型不準時,你拿到的則是完全不同的一組交易。你原本 4,180 筆進場裡,大約有 2,900 筆根本不會發生。其中有些是最好的訊號,出現在價格急漲後又反轉的 K 線上;那正是市場在你沒進場的情況下迅速離開的走勢。

所以你的未成交分支必須有符合實際的邏輯。訊號失效前仍未成交時,策略會怎麼做?改用 taker 追價,承受價差和衝擊成本?往下重新掛單,接受不同的進場基準?跳過這筆交易、保持空手?每個選擇都會帶來截然不同的權益曲線,沒有一種能用「假設已成交」帶過。在我們的測試中,加上一條誠實的追價規則(未成交 20 秒後跨價,滑價上限為 3 bp),找回了約 1/3 的未成交機會,也補回天真模型與排隊模擬 Sharpe 差距的大約一半。這是個相當有意思的結果,只有在成交模型夠真實、問題本身有意義時才看得出來。

10 分鐘就能做的簡單健全性檢查:拿出你的即時模擬交易紀錄,和回測在同一段期間的結果相比。比成交率,不要比損益。如果回測中 100% 的掛單都成交,但模擬交易只有 34%,那就不是在比較策略,而是成交模型有錯。先修好這點,再看任何報酬數字。

還有兩件小事

只掛單拒單。如果你用只掛單來確保 maker 費率,而委託簿在你決策與交易所回覆之間移動,委託就會被拒絕,而不是留在簿上。在我們的模擬交易紀錄中,BTCUSDT 一般時段的拒單率是 3-6%;美國 CPI 數據公布後 1 分鐘內則超過 15%。遭拒的委託不是已成交委託,也不是掛著未成交的委託;那是一筆從未存在過的交易。如果回測沒有這種狀態,你的交易筆數就會在最關鍵的市場狀況下被高估。

自成交防範和你自己的市場足跡。以 0.4 BTC 的規模,你不會撼動 BTCUSDT,所以可以先略過。但你提過想把策略用在中型市值的山寨幣永續合約,那裡的最佳買賣價名義金額常低於 $15k。在那種市場,你的委託會是排隊量中很重要的一部分,而你記錄的前方排隊量,也包含你上一筆委託造成的影響。當你的規模超過掛單量約 10%,模擬就得考慮其他參與者會因你而調整行為。老實說,到那個程度,我會更相信模擬交易,而不是你我能寫出的任何模擬。

用排隊模擬重新跑一次,再把依成交類型拆分的 mark-out 表傳給我。如果反彈那一組仍貢獻大部分 edge,而穿價那一組沒有把 edge 全部吃掉,你或許真的有值得拿來做模擬交易的東西。如果整個成果都來自那 2,900 筆你根本不會成交的委託,那現在就弄清楚,總比盯著模擬帳戶看了 4 週,發現結果和筆記本承諾的完全不同要好。

限價單、排隊順位、逆向選擇、回測、加密貨幣期貨
← 所有文章