唯一的工具是 rolling walk-forward,而它驗的不是一組參數。
你在教科書上、甚至在我們自己的〈回測是什麼?回測報告怎麼看〉裡都讀過同一句話:不要在全部資料上最佳化,至少留 20–30% 做樣本外驗證。然後你打開工作頁,把每個分頁都點過一遍,找不到那個比例設定。
沒找到是正常的,產品沒有這個功能。樣本外這件事在 Blave 上只有一個實作:第五分頁的 rolling walk-forward,而它填的是天數、驗的東西也跟簡單切分不太一樣。這篇講清楚差在哪、那個分頁上的每個數字怎麼讀。
〈如何避免策略過擬合〉裡有一張表,把三種做法標了等級:全資料最佳化(無效)、前 70% 訓練後 30% 測試(有效但有限)、滾動窗口重選(最真實)。那張表是概念層的判斷;產品層的事實是——只有第三種有按鈕。
| 做法 | 概念上 | 在 Blave 上 |
|---|---|---|
| 用全部歷史最佳化,回報全部歷史 | 樣本內,必然樂觀 | 回測分頁本來就是在整段歷史上算成績,所以才需要另外驗 |
| 切一刀:前段訓練、後段測試 | 有效但只能用一次 | 沒有這個功能,找不到比例設定 |
| Rolling walk-forward:每輪重選、測下一段 | 最接近實盤的做法 | 工作頁第五分頁「樣本外驗證」 |
差別不只是介面。簡單切分驗的是「這一組參數在後段資料上好不好」;rolling walk-forward 驗的是「用歷史挑參數」這個做法本身在這支策略上還站不站得住——它把「用一段歷史挑參數」這個動作重複做了 n 次,每次都用沒看過的下一段來記分。
一句話:在每一個訓練窗裡重新挑一次參數,套到緊接著的測試窗;把所有測試窗接成一條。
| 輪 | 訓練窗 | 測試窗 |
|---|---|---|
| 1 | 2022-01-05 → 2024-12-30 | 2024-12-31 → 2025-01-29 |
| 2 | 2022-02-04 → 2025-01-29 | 2025-01-30 → 2025-02-28 |
| 3 | 2022-03-06 → 2025-02-28 | 2025-03-01 → 2025-03-30 |
| … | (中間各輪同樣每次往前挪一個測試窗,共 20 輪) | |
| 20 | 2023-07-29 → 2026-07-23 | 2026-07-24 → 2026-08-22 |
測試窗首尾相接到日:輪 1 停在 2025-01-29、輪 2 從 2025-01-30 接上去,中間沒有空隙也沒有重疊——20 段接起來就是那條樣本外序列。
訓練窗則是大幅重疊:三年的窗每一輪只往前挪 30 天,所以任兩輪的訓練期幾乎是同一段資料。重疊的東西拼不成一條連續的線——這就是為什麼圖上只有樣本外那一條。
實作上它跑兩趟:第一趟把每一組候選參數的訊號在全歷史上只算一次,切出各輪訓練片段定價;第二趟只重算各輪的勝出組合,把測試片段串成一條、用一次定價算完整段。所以它比「跑 n 次完整掃描」快得多——下面那個 20 輪、4.7 年小時線的例子,2026-09-17 那次執行 3.5 秒跑完(介面確認框給的是通則:「通常兩分鐘左右跑完,結果會出現在這個分頁。」)。
Rolling 是訓練窗每一輪整個往前滑;anchored 是起點固定、訓練窗越滾越長。產品只提供 rolling,anchored 刻意不提供。 你跟 agent 要 anchored,它會回一句固定的話就照跑 rolling:
也不用想著繞過去:跑 walk-forward 的那支 lib 是唯讀的,常駐程式會直接拒絕編輯;而且結果檔裡沒有欄位可以標記「這是 anchored 跑的」,真的改了,畫面還是會照 rolling 顯示。
分頁上只有兩個輸入框:訓練窗與測試窗,單位都是天,旁邊即時顯示會切出「共 n 輪」。兩個都不填,就走這張預設表:
| 資料長度(天) | 訓練窗(天) | 測試窗(多久重選一次,天) |
|---|---|---|
| ≥ 1185 | 1095(三年) | 30 |
| 455–1184 | 365(一年) | 30 |
| < 455 | 測試窗的 4 倍 | 總天數 ÷ 12,最少 30 |
三年那一階是有理由的:它比較接近「用戶從整段回測裡挑一組固定參數」的真實做法。中等長度的歷史若直接套比例公式,訓練窗會短於一年,所以中間補了 365/30 這一階。
兩個硬上下限會直接擋下來,不給半調子的結果:少於 3 輪不跑、也不寫結果檔(「資料只夠 {n} 輪,至少要 3 輪才看得出樣本外表現」);超過 1000 輪一樣擋(「把測試窗調大」)。末尾不足一輪的資料不會硬湊,圖下方會註明「末尾 {d} 天不足一輪,未納入」。
結論卡只看一個數字:
WFE = 樣本外 Sharpe ÷ 樣本內平均 Sharpe
分子是拼接後那一整段樣本外序列的 Sharpe;分母是各輪訓練窗裡選中那一格自己的 Sharpe 的平均。四檔標籤照這個分界(都是「小於」,所以 0.5 落在「剛過門檻」、0.7 落在「高於門檻」):
| WFE | 結論卡顯示 |
|---|---|
| 算不出來 | 樣本外效率:無法判斷 |
| < 0.2 | 樣本外效率:遠低於門檻 |
| < 0.5 | 樣本外效率:低於門檻 |
| < 0.7 | 樣本外效率:剛過門檻 |
| ≥ 0.7 | 樣本外效率:高於門檻 |
而那個數字旁邊的說明,請照字面讀:
一個很小或負的分母會把整個診斷倒過來:一支樣本內從來就不強的策略,反而會因為「沒吐回什麼」而拿到很高的「效率」。所以有兩道防線:
教學範例策略 btc_sma_cross(BTCUSDT 1h,2022-01-01 起、共 1,720 天,SMA 45/100,參數一字未改)。資料落在第一階,預設窗 1095/30,切出 20 輪(測試窗合計 600 天),末尾 25 天不足一輪、未納入。
| 項目 | 樣本外(測試窗接起來,600 天) | 樣本內(20 輪訓練窗各自算完取平均) |
|---|---|---|
| Sharpe | −0.45 | 1.3471 |
| 年化報酬 | −16.55% | +50.16% |
| 最大回撤 | −46.57% | — |
| 交易筆數 | 183 | — |
樣本外效率 WFE = −0.45 ÷ 1.3471 = −0.334 → 遠低於門檻
三件事值得指出。第一,沒有任何一輪被排除——20 輪的訓練窗 Sharpe 全都大於 0,分母是完整的 20 輪平均、不是挑剩的。
第二,分母 1.3471 遠高於 0.25,第二道防線沒啟動,除法有效:「WFE 是負的」和「WFE 無法判斷」是兩回事,前者是算出來的結果,後者是拒絕作答。
第三,那兩欄涵蓋的期間本來就不同——樣本內是 20 個互相重疊的訓練窗的平均,樣本外是其後那 600 天,所以不是乾淨的同期對照;WFE 要量的就是這個落差本身。
同一天、同一份碼,這支策略的 MCPT p 值是 0.0160(n = 2000),stdout 自己印 significant edge at 95%——照上架閘門的判準是過的。
| 檢定 | 它問的問題 | 結果 |
|---|---|---|
| MCPT | 在這組固定的部位序列上,這個成績分不分得出實力與運氣? | p = 0.0160 → 過 |
| Walk-forward | 照歷史挑參數這件事本身,到下一段還管不管用? | WFE = −0.334 → 遠低於門檻 |
兩個都沒算錯,因為它們回答的不是同一個問題。這也是為什麼 p 值再漂亮都不能當成樣本外驗過了(見〈MCPT p 值怎麼看〉)。順帶說清楚:這不是在說這支策略壞掉了——它是 workspace 附的教學範例,這裡用它示範的是兩個檢定的語意差異。
每一輪選參數用的是 3×3 鄰域平均(跟參數掃描同一條規則,也就是〈如何避免策略過擬合〉講的挑高原不挑峰值),但輪表上回報的訓練 Sharpe 是選中那一格自己的數字。這是刻意的:
| 分母用什麼 | 後果 |
|---|---|
| 該格自己的 Sharpe(現行) | 分母偏高,WFE 偏保守 |
| 3×3 鄰域平均 | 鄰域平均在結構上必然低於被它包住的那一格 → 分母被壓小 → WFE 灌水,整個分頁讀起來過度樂觀 |
分母灌水的診斷不如不做。
兩輪交界的地方,序列帶的是新那一輪自己的部位——是被剪開,不是歸零。強制每個接縫平倉等於憑空捏造 n−1 筆沒人交易過的出場。
換參數的成本不會被漏掉:接縫處權重改變,定價時照收手續費。也因為每一輪的數字都是從同一條拼好的序列切出來的、不重新定價,輪表加總就等於整段總數。
訊號在全歷史上算(rolling 指標才準),但每個訓練片段各自跳過自己開頭的暖身根數——不是只有第一輪。沒有這一步,第一輪會在指標還沒熱的 K 棒上訓練、挑到雜訊,各輪的有效長度也不一樣、不可比。上面那張輪表就是實證:策略的 START 寫的是 2022-01-01,但輪 1 的訓練窗從 2022-01-05 才開始——差的那幾天正是 WARMUP = 100 根小時線(約 4.2 天)被扣掉。
測試窗不裁切,因為它已經熱了,而且必須連續才能拼接。順帶回答一個進階讀者常問的問題:這裡不需要 embargo 隔離期——測試窗就緊貼在挑它參數的訓練窗後面,它之後的資料完全沒被用到。
跑完之後沒有「套用建議參數」這個按鈕,agent 也不准因為這個結果去改你的策略碼。空狀態就把話講在前面了:
為什麼不能抄?看那次執行的參數漂移。20 輪只選出 4 組不同參數:
(45,100) ×14 → (55,80) → (45,100) → (45,80) ×3 → (35,80)
前 14 輪都停在同一格。就算「大部分時候都選 45/100」看起來像個結論,那條 −0.45 的樣本外曲線也不是 45/100 的成績——它是 4 組參數接力跑出來的一整段,不屬於其中任何一組,所以沒有一組可以被「採用」。你要求套用時,agent 會回固定的一句:
參數漂移網格會畫出每一輪選中哪一格、被選中幾輪,虛線框標出你目前的參數,圖例也明寫「最後一輪選中的參數只是參考,不是建議值」——那次執行最後一輪選的是 35/80,只代表「最後那一輪的訓練窗剛好挑到這一格」。另外,這個分頁不顯示 p 值、也永不跑 MCPT;跑一次 walk-forward 在 agent 的工作守則裡算一次迭代。
輪數夠不夠? 少於 3 輪根本不會跑。輪數太少時,WFE 只是一兩個測試窗的運氣。
有沒有輪被排除? 每輪明細會註明哪幾輪訓練窗 Sharpe ≤ 0。上面那次一輪都沒被排除;被排除的輪越多,代表這支策略連在自己的訓練窗裡都常常挑不到東西。
WFE 是「無法判斷」還是一個負數? 不一樣。「無法判斷」代表樣本內平均低於 0.25、這個比值沒有意義;負數代表除法做了、樣本外那段是虧的。
參數漂移得多兇? 看「{n} 輪選出 {u} 組不同參數」那一行。上面那次是 20 輪 4 組;每輪都跳到完全不同的格子,等於每次重選都在追不同的東西。
你是不是在找建議參數? 沒有。要換參數走參數掃描的穩健點,不是抄最後一輪。
跑過一次之後,如果 WFE 掉得很難看,問題通常不在樣本外這一步,而在挑參數那一步——那是〈如何避免策略過擬合〉的範圍。如果你手上還沒有值得驗的策略,策略庫裡的官方策略每一支都附真實回測與品質關卡結果,可以先看數字再決定:策略庫。