回測過擬合的機率(PBO)
🧠 為什麼挑冠軍會灌水
🎯 什麼情境該想到我
當你「從很多次嘗試裡挑出表現最好的那一個,正準備相信它」的時候。
★ 挑最大值這個動作本身就會灌水:試的次數越多,冠軍越可能只是運氣最好的雜訊。
⚙️ 怎麼用
0. 先理解為什麼會灌水(可自己驗算)
假設 N 個嘗試其實全是雜訊,各自的分數是均值 0、標準差 1 的獨立常態。最大值的期望會隨 N 上升:
| 試了幾次 N | 期望最大值(模擬 2,000 次) | 上界 √(2 ln N) |
|---|---|---|
| 10 | ≈ 1.55σ | 2.15σ |
| 100 | ≈ 2.51σ | 3.03σ |
| 880 | ≈ 3.20σ | 3.68σ |
(2026-10-25 自己模擬;√(2 ln N) 是常態極值的標準上界,不是這篇論文推的。)
換算成回測:500 個交易日估出來的年化 Sharpe,標準誤約 √(252/500) ≈ 0.71。所以從 880 組全是雜訊的參數裡挑冠軍,年化 Sharpe 期望就有 3.2 × 0.71 ≈ 2.3。實際的參數組合彼此高度相關,有效的 N 會小一些,但量級就是這樣。論文第 3.2 節也說:樣本內 Sharpe 落在 1 到 3 之間,「tell us nothing」。
1. 記下你到底試了幾次
- 參數組合、模型版本、特徵組合、提示詞版本,都算一次嘗試。失敗的也要算。
- 論文把只報告成功組合的做法比作新藥試驗剔除無效受試者,也就是「抽屜問題」。
2. 拿「雜訊冠軍」當基準線
- 冠軍分數沒有明顯高過上表對應 N 的「純雜訊期望最大值」,就先當成運氣。
- 能量化時,直接評估挑選流程:回測用 工具-回測過擬合機率PBO,問的是樣本內冠軍在樣本外會不會掉到中位數以下。
3. 看「越追高分,樣本外越差」的跡象
- 把每次嘗試的(挑選用資料上的分數,另一段資料上的分數)畫成散點。斜率是負的,就是過擬合的典型樣子。
- 論文的說法是:「The model is so fit to past noise, that it is often rendered unfit for future signal.」
4. 別把防過擬合的指標變成新目標
- 例如改成挑 PBO 最低、或驗證集分數最高的那組,等於換一個指標繼續過擬合。
- 論文引 Goodhart 定律:「when a measure becomes a target, it ceases to be a good measure」。
同一個陷阱的其他樣子(我的延伸,不是論文內容)
| 情境 | 「試很多次」指的是 | 對應做法 |
|---|---|---|
| 調模型超參數 | 在同一份驗證集上反覆比較 | 留一份只在最後用一次的測試集 |
| A/B 測試 | 每天看結果,一顯著就停 | 事先定好樣本數和停止條件 |
| 刷 benchmark | 反覆改版,直到公開排行榜分數上升 | 見 基準污染與分數失真 |
| 提示詞迭代 | 在同一批範例上反覆修 | 修的範例和驗收用的範例分開 |
🧪 我實際套用的紀錄
- 2026-10-25:重現論文實驗。在純隨機漫步上試 880 組季節策略,樣本內冠軍看起來很好,但 PBO 是 0.56–0.84,也就是半數以上的情況下,冠軍到了樣本外連中位數都不如。
⚠️ 注意 / 什麼時候不適用
- 只試一兩次、而且事先就決定好方案時,這個問題很小。麻煩的是事後從很多結果裡挑。
- 上表假設各次嘗試彼此獨立。參數相近的組合高度相關,有效的 N 比名目上的 N 小,灌水幅度會比表上低,但不會消失。
- 冠軍不可信,不代表全部都爛。N 個嘗試都好、只是彼此差不多時,挑哪個都可以;這種情況也會讓 PBO 偏高。
🔗 相關工具
- 工具-回測過擬合機率PBO —— 在回測上量化這件事的具體方法
- 基準污染與分數失真 —— LLM 評估上的同一個問題:私有評估集調參調久了,也會被過擬合
📐 怎麼量化:CSCV 與 PBO
🎯 什麼情境該想到我
當你「掃了一堆參數組合,挑出回測最好的那組,正要決定要不要上線」的時候。
★ 它不評估某一個策略好不好,而是評估「你挑策略的流程」有沒有在挑雜訊。
⚙️ 怎麼用
1. 準備矩陣
- 把試過的 N 組參數,每期損益排成 T×N 矩陣 M(每列同一時間點)。
- 所有試過的組合都要放,包括失敗的。藏掉失敗的組合,PBO 會被低估。
- 用最佳化演算法引導搜尋時,放每次搜尋收斂後的結果,不放中間步驟。
2. 跑 CSCV
- 依時間切 S 段(預設 S=16)。
- 窮舉 C(S, S/2) 種「哪一半當樣本內」的組合。
- 每種組合:在樣本內挑冠軍,看它在樣本外的相對排名 ω = rank/(N+1),算 λ = ln(ω/(1−ω))。
- PBO = λ ≤ 0 的比例。
Node 實作(2026-10-25 自己寫並驗證過)。關鍵技巧是 Sharpe 只需要每段的 sum、sum of squares、count,任意段組合可以直接加總,不用重掃資料。880 個策略 × 12,870 種組合,約 1 秒跑完:
function pbo(M, S = 16) {
const T = M.length, N = M[0].length, L = Math.floor(T / S);
const blk = Array.from({ length: S }, (_, s) => { // 每段每策略的 sum、sumsq
const sum = new Float64Array(N), sq = new Float64Array(N);
for (let t = s * L; t < (s + 1) * L; t++)
for (let n = 0; n < N; n++) { sum[n] += M[t][n]; sq[n] += M[t][n] ** 2; }
return { sum, sq };
});
const sharpe = (blocks) => { // 合併多段算 Sharpe(只比排名,不必年化)
const out = new Float64Array(N), cnt = blocks.length * L;
for (let n = 0; n < N; n++) {
let a = 0, b = 0;
for (const s of blocks) { a += blk[s].sum[n]; b += blk[s].sq[n]; }
const mu = a / cnt;
out[n] = mu / Math.sqrt(Math.max(b / cnt - mu * mu, 1e-18));
}
return out;
};
const logits = [];
const comb = (start, pick) => { // 窮舉 C(S, S/2)
if (pick.length === S / 2) {
const oos = [...Array(S).keys()].filter((s) => !pick.includes(s));
const R = sharpe(pick), Rb = sharpe(oos);
let best = 0;
for (let n = 1; n < N; n++) if (R[n] > R[best]) best = n;
let rank = 1; // 1 = 樣本外最差,N = 最好
for (let n = 0; n < N; n++) if (Rb[n] < Rb[best]) rank++;
const w = rank / (N + 1);
logits.push(Math.log(w / (1 - w)));
return;
}
for (let s = start; s < S; s++) comb(s + 1, [...pick, s]);
};
comb(0, []);
return logits.filter((l) => l <= 0).length / logits.length; // PBO
}3. 判讀
| 結果 | 意思 | 動作 |
|---|---|---|
| PBO > 0.05 | 挑選流程很可能在挑雜訊 | 不要上線;減少參數維度或拉長資料 |
| PBO 低,但樣本外虧損機率高 | 沒過擬合,但策略本身就不賺錢 | 不是調參問題,回頭檢查策略邏輯 |
| PBO 低,且樣本外冠軍隨機支配整體 | 挑選流程確實有效 | 可以進下一關(模擬盤、小資金) |
順便看績效衰退:把每種組合的(樣本內冠軍 Sharpe,樣本外 Sharpe)畫成散點。斜率是負的,代表越去追樣本內最高分,樣本外越差。
參數怎麼選(可自行驗算)
- S:組合數是 C(S, S/2),S=16 時是 12,870,論文誤印為 12,780。PBO 是比例估計,標準差最大是 √(1/(4·12870)) ≈ 0.0044,夠精確。
- 4 年日資料切 16 段,每段約一季,能保住日、週、月的序列結構。
- 要研究季效應就改用 S=12(924 種),或把資料長度加倍。
- N:要遠大於 10。N 太小時 ω 只有幾個離散值,PBO 的解析度不夠。
- T:取「實際用來挑策略的資料長度」的兩倍,因為 CSCV 每次只用一半資料挑。
🧪 我實際套用的紀錄
- 2026-10-25:用上面的程式碼重現論文第 6 節。月度季節策略,進場日 × 持有天數 × 多空,共 880 組,1,000 個交易日,每種各 3 個隨機種子:
- 純隨機漫步:PBO 0.56/0.60/0.84(論文 55%)
- 每月前 5 天埋入 +0.25σ 效應:PBO 0.00/0.16/0.05(論文 13%)
- 觀察:我的每一次 PBO 都剛好等於樣本外虧損機率。原因是參數格點裡做多、做空成對出現,每個策略都有一個鏡像,樣本外中位數 Sharpe 恰好是 0,「低於中位數」就等於「虧錢」。格點不對稱時,兩者就不會相等。
⚠️ 注意 / 什麼時候不適用
- 不檢查回測本身對不對:手續費、滑價設錯,或用到當時拿不到的未來資料,PBO 一樣會給出漂亮的數字。這些要另外檢查。
- 不能拿 PBO 當挑策略的目標,例如「挑 PBO 最低的那組參數」。這樣就是在對 PBO 過擬合(Goodhart 定律)。它只用來評估挑選流程。
- 只涵蓋資料期間內的市場狀態:資料期間外換了行情,它無能為力。
- PBO 高不代表策略全爛:N 個策略都很好又彼此差不多時,PBO 也會高。
- 對稱切分不一定最適合:績效序列自我相關很強時,切段可能破壞時間結構。對績效分布有更多了解時,針對模型設計的方法會更準。
🔗 相關工具
- 工具-試越多次冠軍越不可信 —— 這個工具背後的通用道理:為什麼「挑最好的」本身就會灌水
⚠️ 限制與誤用
- 不檢查回測本身對錯
- 要交出所有試過的組合
- 別拿 PBO 當目標函數