工法 量化方法論 · 回測
自己稽核自己的回測:三個不會報錯的 bug
2,306 字 · 約 7 分鐘
一份回測給 13.25 倍,我自己稽核完剩 2.26 倍。差距不是調參數調出來的,是三個不會拋例外的 bug:停牌的標的被當成買得到、改名的股票被算成兩個機會、淨值只在平倉那天更新。三個全部朝「策略看起來更好」的方向偏,而回測器沒有理由懷疑它們。這一篇是三個 bug 的形狀與可以照著跑的五個檢查。
一份跑了六年的美股回測,原始輸出是 13.25 倍、年化 +58.6%。我自己稽核完,誠實值是 2.26 倍、年化 +15.7%。
**差距不是調參數調出來的。**是三個 bug,三個都不會拋例外,三個都讓數字往好的方向偏。
前一篇(2026-08-27)講的是檢查因為錯誤的理由通過。**這一篇是它的下一層:資料本身是錯的,而檢查沒有理由懷疑它。**回測器讀到一列資料,它憑什麼知道那一列在當時買不到?
先給三個 bug 的形狀:
| bug | 症狀 | 往哪邊偏 |
|---|---|---|
| 停牌標的被當成可交易 | 單筆佔總報酬 80.5% | 高估報酬 |
| 代號當主鍵,改名的算兩次 | 13 組標的被買兩次 | 高估機會數 |
| 淨值只在平倉那天更新 | 回落 −14% 實際 −45% | 低估痛苦 |
三個的方向一致,而那不是巧合 —— 會讓數字變難看的 bug,你當天就會去查。
單筆佔總報酬 80.5%:先去查那一筆,不是先高興
稽核的第一步是把逐筆報酬排序,看最賺的那一筆。
單筆最大獲利是 +25,576%,佔總報酬的 80.5%。
它長這樣:連續 95 個交易日零成交,然後單日開在 124.7 倍。
回測沒有成交量檢查,所以它「買到了」也「賣掉了」。真實世界裡那是一檔停牌後重新掛牌的股票,那個價格上沒有對手方。
停牌本身是交易所的正常機制。美國證管會的投資人說明寫的是,停牌是為了讓市場參與者有時間消化重大消息(investor.gov,查證日期 2026-08-27)。對回測來說重點只有一句:停牌期間沒有可成交的價格,而歷史資料檔裡那幾天照樣有一列資料。
資料有一列,不等於你當時買得到。
拿掉那一筆,總報酬掉掉八成。
可以照著跑的檢查:任何回測只要有單筆佔總報酬超過一半,先去查那一筆是不是真的成交得了 —— 看它前後的成交量,不是看它的價格。
代號當主鍵:13 組改名的標的被買了兩次
樣本期內有 13 組標的改過 ticker。資料源用新舊代號各存了一份,於是回測把同一家公司的同一段行情當成兩個獨立機會,各買一次。
這件事在幣圈也踩過:同一支幣下市之後換一個合約重新上市。凡是「代號」不是主鍵的市場都會有。
正解不是自己維護一張改名對照表 —— 那張表會漏,而且漏了不會報錯。**正解是換一個不會變的鍵。**學術界處理這件事的標準做法是永久識別碼:CRSP 的 PERMNO 就是為此存在的,官方說明寫它 “fixed for the life of a security”(Morningstar Indexes,查證日期 2026-08-27)。
我當初用 ticker 當主鍵,**等於預設了一個會變的東西不會變。**而那個預設沒有寫在任何地方,所以也沒有人會去檢查它。
可以照著跑的檢查:把回測用的主鍵拿去問一個問題 —— 它在樣本期內會不會改?會改的話,同一個標的會不會出現兩次?兩次都被買的話,回測會不會告訴你?
淨值只在平倉那天更新:回落 −14% 其實是 −45%
記帳的函式只在部位關閉時重算淨值。所以持倉期間的浮虧從來沒有進過權益曲線。
回測報的最大回落 −14%
逐日重算之後 −45%
這一條最要命,因為它決定的不是報酬,是抱不抱得住。這份回測涵蓋六年(最後一年是 2026),而那六年裡的每一天都被這個 bug 蓋掉了。
對帳單會顯示 −45%,而人會在那裡停掉系統。**看到 −14% 的人不會為 −45% 做準備。**回落這個數字的用途從來不是拿來比較策略,是拿來預先決定「跌到哪裡我還撐得住」——而一個沒有經歷過持倉期間任何一天的數字,回答不了那個問題。
可以照著跑的檢查:把權益曲線的更新頻率印出來。如果它等於「平倉次數」而不是「交易日數」,那條曲線描述的不是你的帳戶。
稽核後剩下什麼
| 指標 | 稽核前 | 稽核後 |
|---|---|---|
| 總倍數 | 13.25× | 2.26× |
| 年化 | +58.6% | +15.7% |
| 最大回落 | −14% | −45% |
| 逐筆 t | — | 1.40(門檻 2.0,未過) |
t=1.40 沒過門檻,所以這條線在站上標的是「未證實」不是「可行」。判準怎麼定寫在方法論,帳戶的真金額在績效頁。
還有三個數字比年化更能決定它的下場:水下的日子佔 74%、最長 1.6 年沒有新高、隨機挑一天進場一年後有 42% 機率是虧的。
四分之三的日子帳戶低於前高,意思是大部分時間你看到的是虧損;1.6 年沒有新高,意思是你有整整一年半只能靠「規則說繼續」撐著。這三個數字沒有一個出現在原始回測的輸出裡。
三個 bug 的共同形狀
把三個排在一起:
| # | 回測相信了什麼 | 而真相是 |
|---|---|---|
| 一 | 檔案裡有一列,就買得到 | 停牌期間沒有對手方 |
| 二 | 代號是主鍵 | 代號會變,公司不會 |
| 三 | 淨值等於平倉時的淨值 | 帳戶每天都在動 |
**三個都是「回測相信了一個沒有人寫下來的假設」。**沒有人寫下來,所以沒有人檢查;沒有人檢查,所以它們安靜地全部朝同一個方向偏。
這種「資料集本身讓績效變好看」的偏差在學術上量過。Elton、Gruber 與 Blake 分析共同基金資料庫的存活者偏差,指出只保留活到今天的樣本會系統性地高估績效(Review of Financial Studies 9(4), 1996,摘要,查證日期 2026-08-27)。
而那是資料集帶來的偏差,我這三個是自己寫出來的 —— 量級差了一個數量級,而且沒有任何一篇論文會替你抓。
Bailey、Borwein、López de Prado 與 Zhu 的結論在同一個方向:只要試過的組態夠多,漂亮的回測績效幾乎是必然會出現的,跟策略有沒有邊際無關(Notices of the AMS 61(5), 2014,SSRN,查證日期 2026-08-27)。
兩篇合起來的意思是:**回測數字的預設狀態應該是「假的」,要有人證明它不是。**而證明它不是的最便宜方法,是自己先當一次稽核者。
你可以照著跑的五個檢查
- **排序逐筆報酬,看最賺的那一筆。**佔比超過一半就去查它的成交量,不是查它的價格。
- **問主鍵會不會變。**會變的話,同一個標的出現兩次的時候,回測會不會告訴你?
- **印出權益曲線的更新次數。**它應該等於交易日數,不是平倉次數。
- **把資料集的「生還條件」寫下來。**只有活到今天的標的才在裡面嗎?那個條件會決定你的結論。
- **把稽核前後的數字並排放。**只報稽核後的值,讀者無法判斷稽核做了什麼;並排放才看得出偏差的量級與方向。
第 5 條是最容易被跳過的,而它是唯一一條跟數字無關、跟可信度有關的。只給最終值等於要求讀者相信你;並排放是給他一個判斷的依據。
為什麼結論是「繼續」而不是「關掉」
稽核完剩 2.26 倍、t=1.40 沒過門檻 —— 那條線現在還開著,用小到不痛的錢。
因為它不是包裝過的大盤:beta +0.21、與大盤相關 +0.13、年化 alpha +12.8%。2022 崩盤那年策略 −5.1%,同期大盤 −18.2%。
**未證實 ≠ 已證否。**t=1.40 的正確處置是繼續累積樣本,看 t 往哪邊走 —— 不是加碼,也不是關掉。而且前提要先講好:頭六個月虧錢是預期內的,不准因此改規格。
完整的逐項稽核紀錄、六年的年度分解與那三個體感數字,在〈我的回測說 13.25 倍,稽核完剩 2.26 倍〉。
常見問題
這三個 bug 是新手才會犯的嗎? 第三個是。第一個與第二個不是 —— 它們的共同點是回測器沒有理由懷疑資料:檔案裡有一列價格,那一列的格式、日期、欄位全部合法。要抓到它們,你得先假設資料會騙你。
為什麼三個 bug 全部朝同一個方向偏? 因為方向不對的 bug 你當天就會去查 —— 2026-08-18 這次稽核裡三個 bug 全部同向,不是巧合。**一個讓回測變難看的錯誤會立刻被追究,一個讓它變好看的錯誤會被當成結果。**這不是統計現象,是注意力的分配方式。
稽核完要不要重新調參數? 不要。稽核的目的是知道原本那個數字錯在哪,不是找一組讓它回到 13.25 倍的參數。如果稽核之後第一件事是重調參數,那次稽核等於沒做。
同一種病在檢查那一層長什麼樣子? 五道建置閘門在 2026-08-27 同一天全部出問題而沒有一道報錯,原始碼與修法在〈怎麼寫一個不會因為錯誤理由通過的檢查〉。