回測賺的是實力還是運氣:MCPT p 值怎麼看

把報酬打亂幾千次之後,你的成績排在哪裡——以及這個數字不回答什麼。

最後更新 2026-09
重點整理
  • MCPT 是全樣本置換檢定:把回測期間的報酬序列隨機打亂(預設 2000 次),看有多少次的 Sharpe 比你的實際成績還好。沒有任何樣本內/樣本外切分。
  • 打亂的是報酬序列,你的進出場位置固定不動——所以每一次置換的手續費與部位縮放都跟本尊完全一樣,比的只有「你挑中的時間點好不好」。
  • 每一支 Type A 策略回測都會自動跑,種子固定 42,同一份回測永遠得到同一個 p 值,不用下任何指令。
  • 上架閘門要兩個條件同時成立:p < 0.05 且置換次數 ≥ 1000。資料太長時置換次數會被自動砍到 1000 以下,p 值再漂亮也過不了,而且畫面上沒有任何提示。
  • 過了也不代表什麼。同一支教學範例策略在 2026-09-17 那次執行裡,MCPT p = 0.016 過關,但它的樣本外效率是 −0.334。p 值回答的是「分不分得出實力與運氣」,不是「這支策略會賺」。

回測跑完,Sharpe 看起來不錯。但你心裡那個問題沒被回答:這是策略真的抓到了什麼,還是你剛好在一段上漲行情裡隨便切了幾刀?一個能回答這題的方法是把資料打亂幾千次,看你的成績在那堆亂數裡排第幾——這就是工作頁回測分頁那一列「MCPT p 值」在做的事。這篇講它在檢定什麼、怎麼讀、以及它過關之後仍然沒回答的那個問題。

什麼是 MCPT?

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 值都會漂亮得沒有意義。

提示: 打亂部位=在測「一個瘋狂進出場的策略贏不贏得了你」;打亂報酬=在測「你挑中的那些時間點,比隨機挑的好嗎」。後者才是問題本身。

這個 p 值是怎麼跑出來的

它是自動的,不是選配。每一支 Type A 策略跑回測時都會跑一次,結果直接寫進回測統計、顯示在工作頁回測分頁。

設定預設說明
置換次數2000可在策略裡用 MCPT_N 覆寫,但仍受下面那條預算公式限制
亂數種子42私有亂數器,不動全域種子——同一份回測永遠得到同一個 p 值
手續費策略自己的 FEE回測用什麼就傳什麼
關掉MCPT = False整個跳過,不寫任何 MCPT 欄位

兩個值得記住的行為:掃參數時不跑 MCPT(一次掃描是幾十到幾百次回測,加上它會慢到不能用),所以你看到的 p 值永遠來自「採用參數之後的那一次回測」;而策略上線之後,每一次 live 或排程觸發都會把同一組 MCPT 欄位原樣帶著走——同一份碼、同一組參數,p 值仍然成立,所以上線中的策略會一直看得到它。

p 值多少算過關?判定不在你的機器上

工作頁的回測分頁不判過不過,它只顯示數字,加一句說明。p < 0.05 時寫的是:

隨機打亂 {n} 次,只有 {pct} 的結果比它好;低於 0.05 是慣用門檻。

沒過的時候寫的是:

隨機打亂 {n} 次,有 {pct} 的結果比它好——沒過 0.05 的慣用門檻,這組結果分不出是實力還是運氣。

真正的「過不過」出現在策略庫的三道品質關卡。關卡 2 叫「統計顯著」,說明寫「MCPT 隨機置換檢定 p < 0.05」,而實際判定要兩個條件同時成立

條件門檻
p 值< 0.05
置換次數≥ 1000
組合策略(Type C)顯示「MCPT 不適用(組合策略)」,不算失敗

三道關卡全部通過或不適用,策略才會掛上「已驗證」標章。分工講白一點:你的機器只負責算出 p 值與置換次數,「這樣算不算過」是上架端的判斷。

一支真實策略的 MCPT:2026-09-17 那次執行

拿 workspace 附的教學範例 btc_sma_cross 跑一次(BTCUSDT 1h,2022-01-01 起,SMA_FAST = 45SMA_SLOW = 100,參數一字未改,41,287 根 K 棒)。以下所有數字都來自 2026-09-17 那一次執行。

回測
總報酬+123.95%
Benchmark(買入持有)+64.36%
最大回撤−44.93%
Sharpe Ratio0.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 的閘門要求,所以這一項是過的。

提示: 這是 END = None 的回測,跑到執行當天為止,所以它本身也是一張快照。同一支策略在〈回測是什麼?回測報告怎麼看〉裡用的是另一次執行,總報酬、Sharpe、benchmark 三個數字都跟這裡不一樣。引用任何回測數字都要標執行日期,包括你自己拿去跟朋友講的那組。

同一支策略,MCPT 過了、樣本外沒過

這是本文最重要的一段。上面那支策略在同一天、同一份碼之下,另外跑了一次 walk-forward 樣本外驗證:

檢定 它問的問題 2026-09-17 那次執行 結果
MCPT在這組固定的部位序列上,這個成績分不分得出實力與運氣?p = 0.0160(n = 2000)
Walk-forward照歷史挑參數這件事本身,到下一段還管不管用?樣本外效率 −0.334(樣本外 Sharpe −0.45遠低於門檻

一個過、一個沒過,而且兩個都沒算錯——因為它們回答的根本不是同一個問題。MCPT 檢定的是「這一組已經決定好的進出場,放在這段歷史上不像亂猜」;walk-forward 檢定的是「用歷史挑參數這個動作,換到沒看過的下一段還成不成立」。前者過關不蘊含後者。

這不是在說這支策略壞掉了——它是 workspace 附的教學範例,重點在兩個檢定的語意差異。完整的 walk-forward 數字與怎麼讀,見〈樣本外驗證怎麼做〉。

圖上那行「Actual OOS Sharpe」是標錯的

如果你讓 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,工作頁的說明文字照樣帶入被砍過的次數,沒有任何警示樣式。

提示: 四十萬根這個臨界點是從公式(4e8 ÷ K 棒數 < 1000)推出來的算術,本文那次執行沒有觸發、未經實證;「1 分線兩年 → 400 次」則是官方文件記載的對照。另外,把 MCPT_N 調大救不了這件事——MCPT_N 只是把上限抬高,真正壓住次數的是 max(200, 4e8 ÷ K 棒數) 那一項。K 棒太多時只有兩條路:縮短回測區間,或換更長的 K 棒週期。

坑二:紅線那個 Sharpe 跟卡片上那個不是同一個數

看回上面那兩張表:同一次回測,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 那一列?

失敗時它是安靜的:沒有任何錯誤訊息,回測照常成功,前端就是少那一列。原因至少有四種,而畫面不區分:

原因發生了什麼
策略寫了 MCPT = False直接跳過
這次回測 0 筆交易沒有部位可以檢定,跳過
主機上的 lib 是舊版載入失敗,跳過
資料太短、Sharpe 算出 NaN、或任何例外跳過,回測統計照寫

寧可不寫也不寫錯的理由很實際:任何 NaN 或 inf 都會讓整台機器的策略上傳被打回,所以只要數字不乾淨,這幾個欄位就整組不寫。

組合策略沒有 MCPT,也沒有替代品

Type C(組合策略,N 檔標的配一組權重)的回測不跑 MCPT。原因是這個檢定的結構是「一條價格序列對一條部位序列」,組合策略沒有這個結構。值得誠實講的是:沒有替代檢定

想做的事實際情況
把整個組合壓成一條報酬序列再重抽那是對已實現報酬做 bootstrap,答的是另一個問題,而且產生不出 p 值——p 值正是 MCPT 存在的全部意義。這條路是明文禁止的,agent 也不准自己手刻一個東西頂替
期待關卡卡片顯示什麼「MCPT 不適用(組合策略)」,不是「未通過」——它不算失敗,不影響「已驗證」標章

拿到 p 值之後,先檢查這五件事

1

置換次數是多少? 說明文字裡的那個次數低於 1000,這個 p 值就過不了上架閘門,不管它多小。先看次數,再看 p 值。

2

這是哪一次回測的 p 值? 掃參數的過程不跑 MCPT。你手上的 p 值只屬於「採用參數之後跑的那一次回測」;中間改過碼就要重跑才算數。

3

紅線跟卡片對不起來? 正常。上面那次執行是 0.8985 對 0.6751,算法不同而已,不用去 debug。

4

你是不是把「顯著」讀成「會賺」了? 同一支策略可以 p = 0.016 過關、樣本外效率 −0.334 慘敗。p 值對未來沒有任何陳述。

5

那一列根本不見? 先確認是四種原因裡的哪一種(策略關掉了/0 筆交易/lib 舊版/資料不夠),不要當成「檢定沒過」。

誠實提醒: MCPT 檢定的是這一組固定的進出場位置放在這段歷史上好不好。它不知道這組參數是從幾百組候選裡挑出來的——挑選本身帶來的樂觀偏誤,它量不到。這就是為什麼挑參數要挑高原而不是峰值(見〈如何避免策略過擬合〉),也是為什麼本文那支策略 p 值過了、樣本外還是掉下去。工作頁自己的說明也只寫「0.05 是慣用門檻」——它是慣例,不是保證。

接下來

p 值看懂了,下一個問題就是上面那張對照表的右半邊:換一段沒看過的資料還成不成立。那條路是樣本外驗證,工作頁有獨立的分頁在做。策略庫裡掛「已驗證」標章的官方策略,關卡 2 過的就是本文講的這個檢定——每一支都附真實回測,數字可以直接看:策略庫