2026年九月26日 · 研究

我們試著重現一套策略的回測,看看數字在哪裡出現偏差。

我們試著重現一套策略的回測,看看數字在哪裡出現偏差。

我們重新執行一份存檔的回測,得到的權益曲線卻不一樣。策略程式碼相同、日期區間相同、交易商品也相同,期末餘額卻差了 1.8%,還有 3 筆交易的成交時間差了 1 根 K 棒。

這樣的差異足以讓研究結果難以信任。如果隊友、未來的自己,或模擬交易服務都無法重現這次執行結果,你就無從判斷某項變更究竟改善了策略,還是只改變了實驗。以下是我們找出偏差來源的過程。

第 1 天:我們先記下「相同執行」的定義

我們一開始犯的錯,是把策略檔案當成整個實驗。事實並非如此。這次執行也取決於輸入資料、引擎版本、交易日曆、商品中繼資料和執行設定。程式碼只描述了計算過程的一部分。

在變更任何內容之前,我們先建立執行清單,記錄策略的 commit、資料快照 ID、日期區間、交易平台、費率表、資金費率來源、成交模型和軟體版本。我們也儲存了產生的委託和成交紀錄,因為光看權益曲線,無法判斷兩次執行最早從哪裡開始出現差異。

項目應記錄的內容重要原因
市場資料快照 ID、結構描述版本、調整方式供應商會修補歷史資料並修訂公司行動
執行設定費率級別、資金費率序列、成交與衝擊設定預設值和帳戶假設會改變結果
執行環境程式碼 commit、引擎和相依套件版本函式庫可能改變排序、四捨五入方式或指標
輸出結果委託、成交、持倉和績效指標顯示兩次執行從哪裡開始出現分歧

第 2 天:我們比對交易紀錄,而不是 Sharpe

摘要指標反而讓我們分心。兩次執行的 Sharpe 幾乎相同,但成交紀錄顯示,最早的差異出現在資金費率結算時。一種做法是按結算時間戳記當下已開倉的持倉收取費率;另一種則使用該時間戳記再平衡後的持倉。

策略程式碼沒有變。改變的是引擎的事件排序。一次小幅版本更新明確訂出了事件順序;過去則取決於兩個事件碰巧如何排序。

我們修訂了執行規範,明訂事件順序:先對結算前持有的部位套用資金費率,再處理該時間戳記的策略決策。確切慣例會因交易平台和引擎而異。沒有明確寫下來,才是問題所在。

第 3 天:「相同」的資料檔案其實並不相同

固定事件順序後,剩下的差異集中在幾筆股票交易。供應商修正了歷史拆股調整。我們的檔案名稱和資料列數都和之前相同,因此看起來像是沒有變動。

現在我們會為每份不可變更的資料快照建立指紋,並一併保存調整政策。雜湊值能告訴我們位元組是否變了,卻無法解釋變更原因。因此,執行清單也會記錄來源、擷取時間和轉換版本。對會修訂的資料來說,這些細節也是結果的一部分。

可重現的回測必須能回答:「它看到的是哪個版本的過去?」

第 4 天:我們找到一個不顯眼的預設值

最後一項差異是 maker 費率被設為 0,因為策略設定中漏了這個欄位。較新的引擎套用了帳戶的預設費率。光是這項預設值,就讓幾筆邊際交易發生變化,足以解釋大部分期末餘額差距。

我們明確設定會影響經濟結果的參數,並讓引擎把解析後的設定寫入執行紀錄。探索階段使用預設值很方便,但要比較不同時間的結果時,預設值就不是可靠的依據。

3個偏差來源
1.8%起初的期末餘額差異
0只看 Sharpe 相同的參考價值

下次我們會省下的工夫

我們花了半天比較彙總指標,才去查看第一筆不同的成交。下次別從這裡開始。將兩份事件紀錄依時間戳記排序,再比對最早出現的差異;後續差異往往都是由同一個原因引起。

我們也不會再以為,只要有容器映像檔,執行結果就能重現。它能固定大部分軟體環境,卻無法固定外部資料檔、執行時才取得的費率表,或供應商修訂過的歷史資料。

回測結果改變時,保留兩次執行的清單和紀錄,再一次處理一個偏差來源。真正有用的成果不只是能重新跑出來的曲線,而是能說明哪些資料和假設產生了這個結果,以及下一次執行為何可能不同的紀錄。

可重現性回測資料工程模擬交易
← 所有文章