預測市場看起來像是世界上最容易回測的東西。價格介於 [0, 1]。報酬不是 1 美元就是一無所有。沒有槓桿、資金費率、基差,也沒有 UTC 00:00 永續合約那些怪問題。我們已經有一套能處理加密貨幣永續合約的回測器,包含手續費、資金費率和市場衝擊;原本打算花兩天調整,接著就開始產生策略。
結果花了 8 天。以下是大致依序整理的紀錄,包括股權曲線看起來驚人、而且確實驚人的那一天。
第 1 天:本該輕而易舉的轉接層
一開始的對應方式是一對一,感覺很俐落。市場就是一種商品。YES 價格就是價格。買進 YES 是做多,買進 NO 是做空。結算時強制以 1.00 或 0.00 平倉。把每小時價格序列餵進同一個事件迴圈,就完成了。
這套對應方式接觸真實資料大約 90 分鐘就撐不住了。最先出問題的是報酬序列。我們整個風險層——部位大小、波動率目標、Sharpe 報表——都假設商品會持續存在,並以對數報酬衡量。市場價格從 0.04 跌到 0.00,對數報酬未定義,但確實是全額損失。而且,結算前 3 天價格為 0.04 的市場,和結算前 4 分鐘價格為 0.04 的市場,本質上完全不同,即使兩筆資料都顯示 0.04。
最後我們把整條序列改用距離結算的時間來參數化,而不是用時鐘時間,並將 PnL 視為終局結果,而非盯市損益。這個決定是對的,但也讓後續所有報表程式碼都失效了。
第 2 天:手續費公式就是策略的一部分
在 Kalshi,交易手續費不是按基點計算的折扣,而是隨價格呈二次變化:大約是 0.07 × contracts × P × (1−P),並在委託單層級無條件進位到美分。也就是說,手續費恰好在五五波市場最有意思時最高,在機率極低或極高的區間則幾乎免費。
| 價格 | 每份合約手續費 | 所需優勢(機率百分點) | 手續費占投入比例 |
|---|---|---|---|
| 5¢ | 0.33¢ | 0.33 | 6.7% |
| 10¢ | 0.63¢ | 0.63 | 6.3% |
| 25¢ | 1.31¢ | 1.31 | 5.3% |
| 50¢ | 1.75¢ | 1.75 | 3.5% |
| 75¢ | 1.31¢ | 1.31 | 1.8% |
| 90¢ | 0.63¢ | 0.63 | 0.7% |
| 95¢ | 0.33¢ | 0.33 | 0.35% |
第三欄才是重點:以 50¢ 買進並持有到結算,要損益兩平,你對機率的估計必須比市場高出 1.75 個百分點。不是相對高出 1.75%。是絕對差距。若在結算前出場而不是持有到底,手續費要付兩次,還得加上價差。
Polymarket 的 CLOB 過去手續費結構截然不同,交易本身的費用較接近零,成本幾乎全落在價差和深度上。因此,同一套策略邏輯在不同交易場所會有完全不同的經濟效益;任何採用單一手續費模型的跨平台比較都是虛構的。我們現在為每個交易場所設定各自的手續費函式,不再使用單一數值。
如果你只看預測市場回測報告中的一行,就看以機率百分點表示的手續費。若某策略聲稱在 50¢ 市場有 1.2 個百分點的預測優勢,那麼在 Kalshi 上,光是手續費就足以讓它虧損,其他成本都還沒算。這件事應該在報告首頁就清楚列出,而不是要讀者自己推算。
第 3 天:委託簿像階梯,而且深度很淺
我們的市場衝擊模型是在 BTC 永續合約委託簿上校準的平方根函式,形狀完全不對。以 1 美元商品和 1¢ 最小跳動單位來說,整個委託簿只有 99 個可能的價格檔位;中等流動性的市場可能在最上層掛著幾百份合約,接著就是一段空檔。
以下是我們採樣的一個經濟數據市場快照,YES 方向,距離結算約 30 小時:
| 檔位 | 數量(份合約) | 買進累計成本 |
|---|---|---|
| 42¢ | 310 | 310 份 @ 均價 42.0¢ |
| 43¢ | 85 | 395 份 @ 均價 42.2¢ |
| 45¢ | 140 | 535 份 @ 均價 42.9¢ |
| 49¢ | 600 | 1,135 份 @ 均價 46.1¢ |
1,000 份合約的委託——風險金額 500 美元,在加密貨幣市場裡不值一提——就會讓實際成交均價移動 4 美分。4 美分比手續費高出一倍以上。這裡沒有可套用的平滑函式;你必須逐檔吃單,不然就是憑空編造。我們把這類資產的 sqrt 模型刪掉,改寫成逐檔走訪委託簿的程式。速度比較慢,但結果正確。
我在這段過程中,發現自己竟然因為這些市場「小到不值得在意」而感到煩躁。市場之所以小,是因為定價的對象是一個有期限的真實世界問題,而星期三會關心美國初領失業救濟金人數的人就那麼多。市場的規模就是這個市場本身。抱怨它,就像抱怨一張撲克桌只能坐 9 個人。
第 4 天:股權曲線美得不像話的那天
1,400 個市場,Sharpe 4.1。曲線平滑。每個研究員看到這種結果都該心頭一沉;我後來確實沉了,只是那時我已經截圖了,而且過了大約 40 分鐘才察覺。
bug 出在重採樣器。低流動性市場的歷史價格會以不規則時間戳回傳,所以我們把資料重新索引到固定的每小時網格。每條封存序列的最後一筆都是結算值,1.00 或 0.00。我們重新索引時使用了最近鄰填補,卻沒有設定方向限制,因此只要某市場的最後一筆成交在結算前幾小時,結算值就會沿著空檔向前回填。模型於是從今天這列讀到了明天的答案,而且偏偏發生在那些可以放大部位、又不會碰到深度限制的低流動性市場。
最近鄰填補用在連續交易的商品上沒問題。但如果商品的最後一次觀測值就是真相,它便成了偷看未來的機器。我們現在會斷言所有填補都只能向前進行,並標記結算資料列,確保它不會參與任何特徵計算。這個斷言只有 9 行,是我們整週寫下最有價值的程式碼。
第 5–6 天:1,412 個市場,大約 180 筆賭注
預測市場的樣本數陷阱,看起來親切又無害。你結算了 1,412 個市場,就以為自己有 1,412 筆觀測值,接著據此計算 t 統計量。但同一個星期日的 14 場 NFL 比賽,共用同一套天氣系統、傷兵報告發布時間和投注資金流。51 個州級選舉市場共用同一股全國性政治風向。「X 會在 3 月 31 日前發生嗎」和「X 會在 6 月 30 日前發生嗎」只是到期日不同的同一筆賭注,而且會一起結算。
我們依事件來源和結算日期為市場分群,算出來的有效獨立樣本數大約只有 180。以我們研究的效果幅度來說,這不足以區分真實優勢和雜訊;不論做多少逐市場自助抽樣都補救不了,因為重抽樣的單位必須是群組,而不是資料列。這裡算錯,會讓顯著性悄悄膨脹到原來的 2 到 3 倍。
第 7 天:結算時間本身也是機率分布
以下是我們完全沒建模、後來才慘痛發現的事情:
- 提前結算。「12 月 31 日前」的市場,可能在事件於 8 月 4 日實際發生時就結算。你的資金會提早回來,而實際持有時間從來就不是合約名稱暗示的那樣。
- 爭議。由預言機結算的市場有異議期。事件發生後,部位可能還要懸而未決好幾天;少數情況下,結果甚至會從看似理所當然的判讀翻盤。
- 作廢。來源資料有歧義時,市場會取消,所有人都會獲得退款。在我們的樣本中,這類市場不到 1%,但往往集中在模型最容易判定錯價的那些模糊問題上。
- 資金鎖定。90 天實現的 3 個百分點優勢,和 6 小時實現的 3 個百分點優勢不能相提並論。若回測只報告總優勢,卻沒有以資金占用時間為分母,它永遠會偏好較慢的那一種。
我們在模擬器中加入了持有成本項目和結算日期抖動。兩者都不精確,但都比暗自假設結算必定準時、恰好發生在標示日期來得好。
第 8 天:哪些事應該跳過,哪些事該先做
- 整套以報酬為基礎的架構都跳過。別把波動率目標風險層硬套在終局支付的商品上。寫一套以機率和投入金額為核心的下注部位管理層。我們花了 2 天試著讓舊架構配合新商品。
- 先建立手續費函式,再做策略。把每個預計交易場所的損益兩平優勢曲線印出來,貼在交易桌上方。這能在許多點子耗掉一次回測之前,就先淘汰其中約一半。
- 從第 1 天就逐檔走訪委託簿。從連續市場搬來的任何參數化市場衝擊模型,都會以對你有利的方式出錯。
- 先分群,再計數。決定有效樣本數的分群鍵時,就要同時決定交易標的範圍,不要等到算出自己喜歡的 Sharpe 才做。
- 對填補方向加上斷言。每次合併資料都加一行。如果序列最後一筆是真相,就直接把向後填補視為 bug,而不是寄望之後檢查時才會注意到。
這 8 天沒有白費,主要是因為預測市場去掉了加密貨幣回測中最容易讓人自欺欺人的模糊地帶。沒有資金費率可以含糊帶過,也沒有標記價格慣例可以爭辯。只有一個問題、一個期限和一個答案。在這裡,回測若有錯,你能清楚指出錯在哪裡。
← 所有文章


