把報酬打亂幾千次之後,你的成績排在哪裡——以及這個數字不回答什麼。
回測跑完,Sharpe 看起來不錯。但你心裡那個問題沒被回答:這是策略真的抓到了什麼,還是你剛好在一段上漲行情裡隨便切了幾刀?一個能回答這題的方法是把資料打亂幾千次,看你的成績在那堆亂數裡排第幾——這就是工作頁回測分頁那一列「MCPT p 值」在做的事。這篇講它在檢定什麼、怎麼讀、以及它過關之後仍然沒回答的那個問題。
MCPT(Monte Carlo Permutation Test,蒙地卡羅排列檢定)是一個統計檢定:它把回測期間的報酬序列隨機重排很多次,每次都用同一組進出場位置重新算一次 Sharpe,得到一整條「純靠運氣會長什麼樣」的分布,再看你的實際成績落在這條分布的哪個位置。
| 項目 | 內容 |
|---|---|
| 虛無假設 | 這支策略選中的那些報酬期,不比隨機選的好 |
| p 值定義 | 打亂後的 Sharpe 大於或等於實際 Sharpe 的比例 |
| 判讀 | p < 0.05 → 95% 信心水準下有統計顯著的優勢 |
| 檢定範圍 | 整段回測資料,沒有訓練/測試切分 |
p 值越小,代表「隨便打亂也能打贏你」的情況越少見。以預設的 2000 次為例,p = 0.05 表示這 2000 次裡有 100 次的亂數成績不輸你;p = 0.005 表示只有 10 次。
這是整個設計裡最關鍵、也最容易誤解的一點:被打亂的是前向報酬序列,你的部位序列全程固定。
每一次置換做的事: position(你的部位) ← 固定不動,迴圈外只算一次 × vol_scalar(部位縮放) ← 固定不動,迴圈外只算一次 − fee_cost(手續費) ← 固定不動,迴圈外只算一次 × 打亂後的報酬序列 ← 只有這一項每次重抽 = 這一次置換的 Sharpe
因為手續費與部位縮放都在迴圈外算完,每一次置換承受的交易成本與槓桿都一樣。剩下唯一的變數就是「報酬出現的順序」——也就是你挑中的那些時間點,比隨機挑的時間點好嗎。
因為那是一道送分題。打亂一個二元的部位陣列,會造出遠多於策略本身的進出場切換;在 fee = 0.0005 之下,每一次置換都要承受大約 30 到 40 倍的手續費拖累,所有置換後的 Sharpe 都會被逼成深度負值。不管你的策略有沒有真優勢,p 值都會漂亮得沒有意義。
它是自動的,不是選配。每一支 Type A 策略跑回測時都會跑一次,結果直接寫進回測統計、顯示在工作頁回測分頁。
| 設定 | 預設 | 說明 |
|---|---|---|
| 置換次數 | 2000 | 可在策略裡用 MCPT_N 覆寫,但仍受下面那條預算公式限制 |
| 亂數種子 | 42 | 私有亂數器,不動全域種子——同一份回測永遠得到同一個 p 值 |
| 手續費 | 策略自己的 FEE | 回測用什麼就傳什麼 |
| 關掉 | MCPT = False | 整個跳過,不寫任何 MCPT 欄位 |
兩個值得記住的行為:掃參數時不跑 MCPT(一次掃描是幾十到幾百次回測,加上它會慢到不能用),所以你看到的 p 值永遠來自「採用參數之後的那一次回測」;而策略上線之後,每一次 live 或排程觸發都會把同一組 MCPT 欄位原樣帶著走——同一份碼、同一組參數,p 值仍然成立,所以上線中的策略會一直看得到它。
工作頁的回測分頁不判過不過,它只顯示數字,加一句說明。p < 0.05 時寫的是:
沒過的時候寫的是:
真正的「過不過」出現在策略庫的三道品質關卡。關卡 2 叫「統計顯著」,說明寫「MCPT 隨機置換檢定 p < 0.05」,而實際判定要兩個條件同時成立:
| 條件 | 門檻 |
|---|---|
| p 值 | < 0.05 |
| 置換次數 | ≥ 1000 |
| 組合策略(Type C) | 顯示「MCPT 不適用(組合策略)」,不算失敗 |
三道關卡全部通過或不適用,策略才會掛上「已驗證」標章。分工講白一點:你的機器只負責算出 p 值與置換次數,「這樣算不算過」是上架端的判斷。
拿 workspace 附的教學範例 btc_sma_cross 跑一次(BTCUSDT 1h,2022-01-01 起,SMA_FAST = 45/SMA_SLOW = 100,參數一字未改,41,287 根 K 棒)。以下所有數字都來自 2026-09-17 那一次執行。
| 回測 | 值 |
|---|---|
| 總報酬 | +123.95% |
| Benchmark(買入持有) | +64.36% |
| 最大回撤 | −44.93% |
| Sharpe Ratio | 0.6751 |
| 交易筆數 | 478 |
| 已付手續費(占本金) | 23.90% |
| MCPT | 值 |
|---|---|
| p 值 | 0.0160 |
| 置換次數 | 2000 |
| 直方圖紅線「實際 Sharpe」 | 0.8985 |
| 分布範圍 | −0.8565 ~ 1.4218 |
回測 stdout 自己印的那一行是:
MCPT p-value: 0.0160 (n=2000, significant edge at 95%)
怎麼讀:p = 0.0160 代表 2000 次隨機打亂裡,有 32 次的 Sharpe 不輸給它——換句話說,這組進出場時點在這段歷史上不太像是隨便挑的。置換次數 2000 也高於 1000 的閘門要求,所以這一項是過的。
這是本文最重要的一段。上面那支策略在同一天、同一份碼之下,另外跑了一次 walk-forward 樣本外驗證:
| 檢定 | 它問的問題 | 2026-09-17 那次執行 | 結果 |
|---|---|---|---|
| MCPT | 在這組固定的部位序列上,這個成績分不分得出實力與運氣? | p = 0.0160(n = 2000) | 過 |
| Walk-forward | 照歷史挑參數這件事本身,到下一段還管不管用? | 樣本外效率 −0.334(樣本外 Sharpe −0.45) | 遠低於門檻 |
一個過、一個沒過,而且兩個都沒算錯——因為它們回答的根本不是同一個問題。MCPT 檢定的是「這一組已經決定好的進出場,放在這段歷史上不像亂猜」;walk-forward 檢定的是「用歷史挑參數這個動作,換到沒看過的下一段還成不成立」。前者過關不蘊含後者。
這不是在說這支策略壞掉了——它是 workspace 附的教學範例,重點在兩個檢定的語意差異。完整的 walk-forward 數字與怎麼讀,見〈樣本外驗證怎麼做〉。
如果你讓 agent 把 MCPT 的直方圖畫出來傳進對話,那張圖的圖例上會印著一行字:
Actual OOS Sharpe = ⟨你的數字⟩
OOS 是 out-of-sample(樣本外)的縮寫。那行字是舊註解的殘留標籤,不要照著理解。 MCPT 從頭到尾跑在整段回測資料上,沒有任何訓練窗、測試窗、或比例切分——它不可能是樣本外的東西,上面那張對照表就是實例。
工作頁回測分頁裡的那張圖沒有這個問題,紅線標的是「實際 Sharpe」。另外,對話裡那張圖是手動重跑產生的,手動重跑用的參數跟自動那次不一定一樣,所以圖上的數字也不必然等於分頁上那一列。
自動 MCPT 實際跑幾次,不完全由你決定。它有一條執行預算公式:
實跑次數 = min( MCPT_N, 20000, max( 200, 4e8 ÷ K 棒數 ) )
K 棒越多,4e8 ÷ K 棒數 越小。上面那次執行是 41,287 根 K 棒,離天花板還很遠,所以 2000 次一次都沒被砍。但官方文件記載的對照是:4 萬根 → 2000 次;20 萬根(5 分線兩年)→ 2000 次;100 萬根(1 分線兩年)→ 400 次。
問題在於上架閘門卡的是置換次數 ≥ 1000。照公式推算,K 棒數超過約四十萬根時實跑次數就會低於 1000——就算 p = 0.001,關卡照樣判不通過。而畫面上完全沒有提示:被砍的訊息只寫進主機的 log,工作頁的說明文字照樣帶入被砍過的次數,沒有任何警示樣式。
看回上面那兩張表:同一次回測,Sharpe Ratio 卡寫 0.6751,直方圖紅線的「實際 Sharpe」寫 0.8985,差了 0.22,肉眼就看得出來。這不是 bug,是兩條不同的算法:
| 項目 | MCPT 內部的 Sharpe(0.8985) | 回測分頁的 Sharpe Ratio(0.6751) |
|---|---|---|
| 報酬怎麼算 | 簡單報酬,不分開盤/收盤段 | 隔夜段與盤中段分開計價 |
| 部位縮放 | 一定會套:30% 目標波動、2 倍槓桿上限 | 策略自己寫了才有 |
關鍵在第二列:自動 MCPT 不會把策略自己的目標波動設定傳進去,它一律用函式的預設值。btc_sma_cross 沒有做波動度目標化,但它的 MCPT Sharpe 仍然是「套了 30% 目標波動、2 倍上限之後」的數字(目標波動在做什麼,見〈波動度目標化怎麼運作〉)。兩個數字不一樣是正常的,介面目前沒有說明這件事。
失敗時它是安靜的:沒有任何錯誤訊息,回測照常成功,前端就是少那一列。原因至少有四種,而畫面不區分:
| 原因 | 發生了什麼 |
|---|---|
| 策略寫了 MCPT = False | 直接跳過 |
| 這次回測 0 筆交易 | 沒有部位可以檢定,跳過 |
| 主機上的 lib 是舊版 | 載入失敗,跳過 |
| 資料太短、Sharpe 算出 NaN、或任何例外 | 跳過,回測統計照寫 |
寧可不寫也不寫錯的理由很實際:任何 NaN 或 inf 都會讓整台機器的策略上傳被打回,所以只要數字不乾淨,這幾個欄位就整組不寫。
Type C(組合策略,N 檔標的配一組權重)的回測不跑 MCPT。原因是這個檢定的結構是「一條價格序列對一條部位序列」,組合策略沒有這個結構。值得誠實講的是:沒有替代檢定。
| 想做的事 | 實際情況 |
|---|---|
| 把整個組合壓成一條報酬序列再重抽 | 那是對已實現報酬做 bootstrap,答的是另一個問題,而且產生不出 p 值——p 值正是 MCPT 存在的全部意義。這條路是明文禁止的,agent 也不准自己手刻一個東西頂替 |
| 期待關卡卡片顯示什麼 | 「MCPT 不適用(組合策略)」,不是「未通過」——它不算失敗,不影響「已驗證」標章 |
置換次數是多少? 說明文字裡的那個次數低於 1000,這個 p 值就過不了上架閘門,不管它多小。先看次數,再看 p 值。
這是哪一次回測的 p 值? 掃參數的過程不跑 MCPT。你手上的 p 值只屬於「採用參數之後跑的那一次回測」;中間改過碼就要重跑才算數。
紅線跟卡片對不起來? 正常。上面那次執行是 0.8985 對 0.6751,算法不同而已,不用去 debug。
你是不是把「顯著」讀成「會賺」了? 同一支策略可以 p = 0.016 過關、樣本外效率 −0.334 慘敗。p 值對未來沒有任何陳述。
那一列根本不見? 先確認是四種原因裡的哪一種(策略關掉了/0 筆交易/lib 舊版/資料不夠),不要當成「檢定沒過」。
p 值看懂了,下一個問題就是上面那張對照表的右半邊:換一段沒看過的資料還成不成立。那條路是樣本外驗證,工作頁有獨立的分頁在做。策略庫裡掛「已驗證」標章的官方策略,關卡 2 過的就是本文講的這個檢定——每一支都附真實回測,數字可以直接看:策略庫。