「你們的系統有在跑嗎?我看帳上買的點跟回測完全不一樣。」
這句話比「系統當了」更難處理。程式當機有 stack trace,有錯誤訊息,有明確的失敗點可以追。但「都在跑、都沒報錯,就是買錯地方」這種問題,往往讓人第一反應是懷疑資料來源、懷疑訊號計算,而不是去質疑「你的回測和實盤,對同一件事的定義到底有沒有一樣」。
3Q 台股量化平台在某次實盤追蹤中發現:同一檔標的,回測系統顯示應在 10/8 以 74.3 元進場,實盤卻在 10/14 以 78.7 元進場——晚了四個交易日,貴了將近 6%。目前這筆是虧損的。而另一筆用不同策略組合的持倉,實盤與回測的進場日完全重合,目前是獲利的。
兩筆都沒有報錯。差距全由進場時間點解釋。
這篇文章記錄我們怎麼找到根因、怎麼修、以及怎麼用現有持倉倒推驗證修正方向是對的——不是事後補救,而是把兩套系統重新校準到同一條規則。
問題為什麼不好察覺 #
量化系統的可信度建立在一個隱含前提上:你相信回測的數字代表「如果按這套邏輯操作,歷史上會發生什麼」。當這個前提失效,不是因為數字算錯,而是因為回測與實盤對「按這套邏輯操作」的詮釋根本不同——問題就變得極度隱蔽。
對比幾種常見的量化 bug:
| 問題類型 | 可見程度 | 典型症狀 |
|---|---|---|
| 程式崩潰 | 立即可見 | 有錯誤訊息、有 log |
| 資料來源異常 | 中等 | 訊號出現在奇怪的時間點 |
| 計算邏輯錯誤 | 中等 | 指標數值明顯不合理 |
| 進場定義不對齊 | 極低 | 都在跑、都沒報錯、但結果偏移 |
「進場定義不對齊」之所以特別難察覺,有兩個原因。
第一,它不是每次都出現。如果市場在訊號觸發後立刻單邊上漲,實盤和回測可能差一兩天就進場,價差不明顯,沒人發現問題。只有遇到「訊號連亮多天、但有一層每天重算的過濾條件」的組合,問題才會被放大成幾天甚至幾週的落差。
第二,它在兩套系統上都是「正確執行」。回測按自己的定義算,實盤按自己的定義執行,雙方都沒有做錯任何事——只是兩件事從來都不是同一件事。
兩套邏輯的差距從哪裡來 #
要理解這個偏移,需要先理解 MACD 類技術指標的特性:訊號一旦觸發,往往會連續數日都顯示同一個方向。
以這次出問題的策略組合(MACD→MR)為例,10/7 出現 SELL 訊號,10/8 起連續多天都顯示 BUY。對回測引擎來說,它的統計口徑是:
「上一個 SELL 之後的第一個 BUY 日才算進場。」
這是回測引擎在計算歷史績效時採用的隱含規則。後面連續出現的 BUY,在回測眼裡都不是新的進場機會,而是「同一波訊號的延續」。
但實盤的 Phase 1 邏輯是:
「當天只要是 BUY 就進場。」
這兩句話看起來差不多,但加上一層每天重算的 permutation gate(統計顯著性過濾,p 值每天波動,有時過、有時不過)之後,效果就完全不同了。
實盤的執行路徑是:
- Day 1(10/8):BUY 訊號亮,但 gate 未通過 → 不進場
- Day 2(10/9):BUY 訊號亮,gate 未通過 → 不進場
- ...(中間幾天同樣情況)
- Day 6(10/14):BUY 訊號亮,gate 通過 → 進場,78.7 元
回測的執行路徑是:
- Day 1(10/8):SELL 後第一個 BUY 日 → 直接進場,74.3 元
- Day 2 之後的 BUY:無視,不重複計算
這就是那 6% 的來源。不是訊號算錯,不是資料有問題,而是「何時算第一次觸發」在兩套系統裡從來就不是同一個定義。
為什麼這個邊界容易被忽略 #
寫回測引擎的人傾向於把「SELL 後第一個 BUY」設計成預設行為,因為這樣可以避免同一波行情重複計算績效。這是合理的工程決策。
但當這個假設沒有被明確文件化,實盤工程師在開發執行層時,很自然地會寫出「BUY 就進場」——他可能根本不知道回測引擎有這個隱含口徑。
這個問題在以下情況特別容易出現:
- 策略引擎和執行引擎是分開開發的:不同時期、不同人寫,沒有統一的「進場定義文件」。
- 訊號具有連續性:動能類(MACD、RSI 延伸)比均值回歸類更容易連亮多天。
- 有多層過濾條件且每天重算:統計顯著性過濾(permutation gate)、流動性過濾等,讓實盤可能在第 N 天才通過所有條件。
如果你的策略是「訊號只亮一天」的類型(例如純粹的突破訊號,隔天就轉向),這個問題不會出現。問題的溫床是「連續訊號 × 多層過濾」的組合。
修正方向:加一道時間門檻 #
確認根因之後,修法其實很清楚:讓實盤的進場判斷與回測的統計口徑對齊。
具體做法是在 Phase 1 加入一道時間門檻:
「今天必須是這組訊號對『上一個 SELL 之後的第一或第二個 BUY 日』,超過第二天一律不追。」
邏輯上,這道門檻做了幾件事:
- 消除追高風險:訊號連亮多天後才通過 gate 的情況,直接攔下,不追。
- 與回測口徑對齊:「第一或第二個 BUY 日」的容忍度略寬於回測的「第一天」,但核心概念相同——不允許在訊號已經連亮很久之後才進場。
- 停損後同波不再重進:如果同一波訊號中已經因停損被移出,不在同一波的後段重新進場。被掃出後追回高點,等同二次追高。
被攔下的標的不刪除,放入觀察池,等下一輪完整的 SELL → BUY 循環。這讓策略不是「永遠放棄這檔」,而是「等一個乾淨的入場機會」。
用現有四檔倒推驗證 #
修正邏輯之後,最重要的步驟是:用已知結果驗證這套邏輯是否真的有辨別力。
我們拿當時帳上的四筆持倉做倒推:
| 策略組合 | 訊號延遲天數 | 新規則判定 | 實際損益方向 |
|---|---|---|---|
| BOLLINGER→RSI(標的 A) | 0 天 | 可進 | 獲利 |
| BOLLINGER→RSI(標的 B) | 0 天 | 可進 | 獲利 |
| MACD→MR(標的 C) | 6 天 | 攔下 | 虧損 |
| MACD→MR(標的 D) | 25 天 | 攔下 | 虧損 |
結果與實際盈虧方向完全吻合。
這個驗證的意義不只是「新規則能解釋過去」。更重要的是:兩筆被攔下的倉位都是MACD→MR 策略,兩筆可進的都是 BOLLINGER→RSI——這說明問題有策略選擇性,不是所有策略都中招。BOLLINGER→RSI 的訊號結構不同,沒有連續亮燈的特性,所以回測與實盤一直都是對齊的。
這個對比也提示了一件事:不同策略組合需要分別驗證進場定義是否對齊,不能假設統一套用就沒問題。
踩過的坑:幾個容易誤導診斷方向的現象 #
坑一:先懷疑資料來源
看到回測與實盤進場價差了 6%,第一直覺是「是不是行情資料不一樣?回測用的是收盤價,實盤用的是開盤價?」這個方向走下去會花很多時間比對資料,但最終通常排除。6% 的差距超出了單日開收盤差距的合理範圍,更像是時間偏移,不是價格計算問題。
正確的診斷順序是:先比時間,再比價格。如果進場日期本來就不同,價差是時間差的副產品,不需要另外解釋。
坑二:以為換策略參數就能解決
「那就把 permutation gate 的閾值調低一點,讓它更早通過」——這個想法看起來直接,但實際上治標不治本。調低閾值會讓更多訊號通過,但不會保證通過的那天一定是第一天。只要 gate 是每天重算的,就永遠存在「第 N 天才通過」的可能性。
真正的問題是進場定義,不是過濾條件的鬆緊程度。
坑三:把「修好後沒虧」當成驗證
修完邏輯之後,如果等下一批實盤結果來驗收,週期太長,而且未來的市場環境不一定會重現問題場景。正確的驗證方式是用已知結果的現有持倉倒推:你知道每筆倉位的實際損益,用新邏輯重新判斷每筆是否「可進」,看判定結果與損益方向是否一致。四筆四中,才算有足夠信心。
坑四:忽略策略間的差異
發現問題後,容易陷入「所有策略都有這個問題」的過度修正。實際上,BOLLINGER→RSI 因為訊號結構不同,從來都沒有這個問題。把時間門檻套用到本來就對的策略上,只會無謂地增加複雜度,甚至可能攔下本來該進的倉位。修正前,必須先確認問題的邊界在哪裡。
這個工法可以搬去哪裡用 #
「回測與實盤的定義不對齊」不是量化交易專屬的問題。任何系統只要同時存在「離線計算的評估邏輯」與「線上執行的觸發邏輯」,就有可能出現同樣的裂縫。
幾個類似的場景:
促銷觸發系統:行銷人員用歷史資料回算「如果在客戶連續三天沒登入時發優惠券,轉換率是 X%」——但執行系統的條件是「今天沒登入就發」,遇到客戶每天都開 App 但不結帳的情況,回算與執行的目標客群可能完全不同。
製造業預警通知:設備異常預警的回測是「狀態轉為異常後第一個小時通知維修」,但執行邏輯是「狀態是異常就推播」——設備長時間處於臨界狀態時,值班人員可能每小時收到幾十則重複通知,且每則都是「第一次」的語氣。
補貨建議系統:回測口徑是「庫存首次低於安全量時觸發建議」,執行邏輯是「只要庫存低於安全量就顯示建議」——結果採購人員每天看到同樣的建議,不知道是新出現的還是上週就在了,喪失行動力。
這三個場景有一個共同的結構:「連續狀態」配上「逐次判斷的觸發邏輯」,一旦沒有明確定義「第一次觸發的邊界」,評估系統與執行系統就會悄悄走向不同的詮釋。
解法也有共同的形式:在執行邏輯加入「距上一次相反狀態已過幾個週期」的判斷,只允許在合理窗口內觸發,超出窗口的進入觀察列而非立即執行。
抽象成可搬移的除錯框架 #
這次排查過程最有價值的部分,不是那道時間門檻,而是整個診斷的路徑:
步驟一:先比時間,不比參數。 發現回測與實盤結果不一致時,第一步是對齊時間點——兩套系統在哪一天做了什麼決定。找到時間差,才能找到定義差。
步驟二:從兩套系統各自「向前追溯」。 回測在第一天進場,是因為什麼條件?實盤在第六天進場,是因為第幾天才滿足什麼條件?把兩條路徑並排,差距自然浮現。
步驟三:找到「最小不一致點」。 不是「回測和實盤整套邏輯都不一樣」,而是「在某一個具體判斷上,兩套系統的定義不同」。這次是「第一個 BUY 日」的定義。找到這一點,修法就清楚了。
步驟四:用已知結果驗證,不要等未來。 修完之後,拿現有的已知結果倒推。判定結果與損益方向一致,才算確認邏輯修正方向是對的。
步驟五:確認問題邊界,不要過度修正。 找出哪些策略受影響、哪些不受影響,只修有問題的部分。
這個框架的核心假設是:兩套系統的行為差距,幾乎都可以被解釋成某個隱含假設的不一致。 找到那個假設,就找到了問題。
如果你的量化系統也有「都在跑、都沒報錯、但買的點不對」的症狀,或者你正在設計一套需要同時維護「離線評估」與「線上執行」的自動化規則,歡迎和我們聊聊——這類問題的診斷方法,我們有實際做過的經驗。
