Lãi trong backtest là thực lực hay may mắn: cách đọc giá trị p của MCPT

Kết quả của bạn đứng ở đâu sau khi lợi nhuận bị xáo trộn vài nghìn lần — và con số đó không trả lời điều gì.

Cập nhật lần cuối 2026-09
Điểm chính
  • MCPT là kiểm định hoán vị trên toàn bộ mẫu: nó xáo trộn ngẫu nhiên chuỗi lợi nhuận của giai đoạn backtest (mặc định 2000 lần) và đếm xem bao nhiêu lần cho ra Sharpe tốt hơn kết quả thật của bạn. Hoàn toàn không có bất kỳ phân chia trong mẫu / ngoài mẫu nào.
  • Thứ bị xáo trộn là chuỗi lợi nhuận, còn các điểm vào lệnh và thoát lệnh của bạn thì đứng yên — nên mỗi lần hoán vị đều gánh đúng mức phí và mức co giãn vị thế như bản gốc, và thứ duy nhất được đem so là «những thời điểm bạn chọn có tốt hay không».
  • Nó tự chạy trong mọi backtest của chiến lược Type A, với hạt giống cố định là 42, nên cùng một backtest luôn cho ra cùng một giá trị p. Bạn không cần ra lệnh gì cả.
  • Cổng lên sàn đòi hai điều kiện đồng thời: p < 0.05 và số lần hoán vị ≥ 1000. Khi dữ liệu quá dài, số lần hoán vị bị tự động cắt xuống dưới 1000, nên giá trị p đẹp đến mấy cũng không qua — mà màn hình thì không hề báo gì.
  • Qua được cũng chẳng nói lên nhiều điều. Trong lần chạy ngày 2026-09-17 của chính chiến lược mẫu hướng dẫn đó, MCPT p = 0.016 đã đạt, nhưng hiệu suất ngoài mẫu của nó là −0.334. Giá trị p trả lời câu «có phân biệt được thực lực với may mắn không», chứ không phải «chiến lược này có lãi không».

Backtest chạy xong, Sharpe trông cũng được. Nhưng câu hỏi trong đầu bạn vẫn chưa được trả lời: chiến lược thật sự bắt được điều gì đó, hay bạn chỉ tình cờ chém vài nhát bừa bãi giữa một đoạn thị trường đang lên? Một cách trả lời là xáo trộn dữ liệu vài nghìn lần rồi xem kết quả của bạn xếp thứ mấy trong đống ngẫu nhiên đó — đúng là việc mà dòng «Giá trị p MCPT» trên tab Backtest của workspace đang làm. Bài này nói nó kiểm định cái gì, đọc thế nào, và câu hỏi mà nó vẫn chưa trả lời ngay cả khi đã đạt.

MCPT là gì?

MCPT (Monte Carlo Permutation Test, kiểm định hoán vị Monte Carlo) là một kiểm định thống kê: nó sắp xếp lại ngẫu nhiên chuỗi lợi nhuận của giai đoạn backtest thật nhiều lần, mỗi lần đều tính lại Sharpe trên đúng bộ điểm vào và thoát lệnh đó, để dựng nên trọn một phân phối «nếu thuần may mắn thì trông thế nào», rồi xem kết quả thật của bạn rơi vào đâu trên phân phối ấy.

MụcNội dung
Giả thuyết khôngNhững giai đoạn lợi nhuận mà chiến lược này chọn không tốt hơn những giai đoạn chọn ngẫu nhiên
Định nghĩa giá trị pTỷ lệ số lần Sharpe sau khi xáo trộn lớn hơn hoặc bằng Sharpe thật
Cách hiểup < 0.05 → Có lợi thế đạt ý nghĩa thống kê ở mức tin cậy 95%
Phạm vi kiểm địnhToàn bộ dữ liệu backtest, không chia huấn luyện / kiểm định

Giá trị p càng nhỏ thì chuyện «xáo trộn bừa cũng thắng được bạn» càng hiếm. Lấy mặc định 2000 lần làm ví dụ, p = 0.05 nghĩa là trong 2000 lần đó có 100 lần kết quả ngẫu nhiên không thua bạn; p = 0.005 nghĩa là chỉ có 10 lần.

Nó xáo trộn lợi nhuận, không phải các lệnh vào ra của bạn

Đây là điểm mấu chốt nhất trong toàn bộ thiết kế, và cũng là điểm dễ hiểu nhầm nhất: thứ bị xáo trộn là chuỗi lợi nhuận tương lai, còn chuỗi vị thế của bạn thì cố định từ đầu đến cuối.

Mỗi lần hoán vị làm những gì:

position(vị thế của bạn) ← cố định, chỉ tính một lần ngoài vòng lặp
× vol_scalar(co giãn vị thế) ← cố định, chỉ tính một lần ngoài vòng lặp
− fee_cost(phí giao dịch) ← cố định, chỉ tính một lần ngoài vòng lặp
× chuỗi lợi nhuận sau khi xáo trộn ← chỉ mục này mới được rút lại mỗi lần
= Sharpe của lần hoán vị này

Vì phí và co giãn vị thế đều đã tính xong ngoài vòng lặp, mỗi lần hoán vị đều chịu cùng một mức chi phí giao dịch và cùng một mức đòn bẩy. Biến số duy nhất còn lại là «thứ tự lợi nhuận xuất hiện» — tức là những thời điểm bạn chọn có tốt hơn những thời điểm chọn ngẫu nhiên không.

Sao không xáo trộn luôn vị thế cho xong?

Vì như thế là cho điểm không. Xáo trộn một mảng vị thế nhị phân sẽ tạo ra số lần đảo chiều vào ra nhiều hơn hẳn bản thân chiến lược; với fee = 0.0005, mỗi lần hoán vị phải gánh khoảng 30 đến 40 lần mức phí, và mọi Sharpe sau khi xáo trộn đều bị ép xuống âm sâu. Dù chiến lược của bạn có lợi thế thật hay không, giá trị p vẫn sẽ đẹp đến mức vô nghĩa.

Mẹo: Xáo trộn vị thế là đang đo «một chiến lược vào ra điên loạn có thắng nổi bạn không»; xáo trộn lợi nhuận là đang đo «những thời điểm bạn chọn có tốt hơn chọn ngẫu nhiên không». Vế sau mới chính là câu hỏi.

Giá trị p này được tạo ra như thế nào

Nó là tự động, không phải tùy chọn. Mỗi lần chiến lược Type A chạy backtest là nó chạy một lần, kết quả ghi thẳng vào thống kê backtest và hiển thị trên tab Backtest của workspace.

Thiết lậpMặc địnhDiễn giải
Số lần hoán vị2000Có thể ghi đè bằng MCPT_N trong chiến lược, nhưng vẫn chịu ràng buộc của công thức ngân sách bên dưới
Hạt giống ngẫu nhiên42Bộ sinh số riêng, không đụng tới hạt giống toàn cục — cùng một backtest luôn cho ra cùng một giá trị p
Phí giao dịchFEE của chính chiến lượcBacktest dùng gì thì truyền vào cái đó
Tắt điMCPT = FalseBỏ qua toàn bộ, không ghi bất kỳ trường MCPT nào

Hai hành vi đáng nhớ: khi quét tham số thì không chạy MCPT (một lượt quét là vài chục tới vài trăm backtest, thêm nó vào sẽ chậm tới mức không dùng được), nên giá trị p bạn thấy luôn đến từ «lần backtest chạy sau khi đã chọn tham số»; và sau khi chiến lược lên chạy thật, mỗi lần kích hoạt live hay theo lịch đều mang nguyên bộ trường MCPT đó đi theo — cùng mã, cùng tham số, nên giá trị p vẫn còn hiệu lực, vì thế chiến lược đang chạy sẽ luôn thấy nó.

Giá trị p bao nhiêu thì đạt? Phán quyết không nằm trên máy của bạn

Tab Backtest của workspace không phán đạt hay không đạt, nó chỉ hiện con số kèm một dòng diễn giải. Khi p < 0.05 nó viết:

Đã xáo trộn {n} lần; chỉ {pct} số lần xáo trộn vượt được nó. Dưới 0.05 là ngưỡng thông thường.

Khi không đạt thì nó viết:

Đã xáo trộn {n} lần; {pct} số lần xáo trộn vượt được nó — cao hơn ngưỡng thông thường 0.05, nên kết quả này không phân biệt được với may mắn.

Chuyện «đạt hay không đạt» thật sự xuất hiện ở ba tiêu chí chất lượng của Thư viện chiến lược. Tiêu chí 2 tên là «Có ý nghĩa thống kê», phần mô tả viết «Kiểm định hoán vị MCPT p < 0.05», còn phán quyết thực tế đòi hai điều kiện đồng thời thành lập:

Điều kiệnNgưỡng
Giá trị p< 0.05
Số lần hoán vị≥ 1000
Chiến lược danh mục (Type C)Hiện «Không áp dụng MCPT (chiến lược danh mục)», không tính là trượt

Phải qua hoặc không áp dụng cả ba tiêu chí thì chiến lược mới được gắn huy hiệu «Đã kiểm định». Nói thẳng về phân công: máy của bạn chỉ lo tính ra giá trị p và số lần hoán vị, còn «thế có tính là đạt không» là phán quyết của phía lên sàn.

MCPT của một chiến lược thật: lần chạy ngày 2026-09-17

Lấy chiến lược mẫu hướng dẫn btc_sma_cross đi kèm workspace chạy một lần (BTCUSDT 1h, từ 2022-01-01, SMA_FAST = 45 / SMA_SLOW = 100, không sửa một chữ tham số nào, 41,287 cây nến). Toàn bộ các con số dưới đây đều đến từ lần chạy ngày 2026-09-17 đó.

BacktestGiá trị
Tổng lợi nhuận+123.95%
Chuẩn tham chiếu (mua và giữ)+64.36%
Sụt giảm tối đa−44.93%
Sharpe Ratio0.6751
Số giao dịch478
Phí đã trả (so với vốn gốc)23.90%
MCPTGiá trị
Giá trị p0.0160
Số lần hoán vị2000
Đường đỏ trên biểu đồ tần suất «Sharpe thực tế»0.8985
Khoảng phân phối−0.8565 ~ 1.4218

Dòng mà chính backtest in ra stdout là:

MCPT p-value: 0.0160  (n=2000, significant edge at 95%)

Cách đọc: p = 0.0160 nghĩa là trong 2000 lần xáo trộn ngẫu nhiên có 32 lần cho Sharpe không thua nó — nói cách khác, bộ thời điểm vào ra này trên đoạn lịch sử đó trông không giống được chọn bừa. Số lần hoán vị 2000 cũng cao hơn yêu cầu 1000 của cổng, nên hạng mục này là đạt.

Mẹo: Đây là backtest END = None, chạy tới đúng ngày thực thi, nên bản thân nó cũng là một ảnh chụp. Cùng chiến lược đó nhưng trong Cách đọc kết quả backtest là một lần chạy khác, và cả ba con số tổng lợi nhuận, Sharpe, chuẩn tham chiếu đều khác ở đây. Trích con số backtest nào cũng phải ghi kèm ngày chạy, kể cả bộ số bạn đem kể với bạn bè.

Cùng một chiến lược: MCPT đạt, ngoài mẫu không đạt

Đây là phần quan trọng nhất của bài. Cũng chiến lược đó, cùng ngày, cùng một bản mã, còn được chạy thêm một lượt kiểm định ngoài mẫu walk-forward:

Kiểm định Câu hỏi nó đặt ra Lần chạy 2026-09-17 Kết quả
MCPTTrên chuỗi vị thế cố định này, kết quả đó có phân biệt được thực lực với may mắn không?p = 0.0160(n = 2000)Đạt
Walk-forwardBản thân việc chọn tham số theo lịch sử còn dùng được ở đoạn tiếp theo không?Hiệu suất ngoài mẫu −0.334(Sharpe ngoài mẫu −0.45Thấp hơn ngưỡng rất nhiều

Một cái đạt, một cái không, mà cả hai đều không tính sai — bởi chúng vốn không trả lời cùng một câu hỏi. MCPT kiểm định chuyện «bộ vào ra đã chốt này, đặt trên đoạn lịch sử đó, trông không giống đoán mò»; walk-forward kiểm định chuyện «lấy lịch sử ra chọn tham số, đổi sang đoạn kế tiếp chưa từng thấy thì còn đứng vững không». Vế trước đạt không hàm ý vế sau.

Đây không phải là nói chiến lược này hỏng — nó là mẫu hướng dẫn đi kèm workspace, trọng tâm nằm ở khác biệt về ý nghĩa giữa hai kiểm định. Xem đầy đủ các con số walk-forward và cách đọc chúng tại Cách kiểm định ngoài mẫu hoạt động.

Dòng «Actual OOS Sharpe» trên biểu đồ bị ghi nhãn sai

Nếu bạn nhờ agent vẽ biểu đồ tần suất MCPT rồi gửi vào khung chat, phần chú giải của biểu đồ đó sẽ in một dòng:

Actual OOS Sharpe = ⟨con số của bạn⟩

OOS là viết tắt của out-of-sample (ngoài mẫu). Dòng chữ đó là nhãn còn sót lại từ một ghi chú cũ, đừng hiểu theo nghĩa đen. MCPT chạy trên toàn bộ dữ liệu backtest từ đầu đến cuối, không có bất kỳ cửa sổ huấn luyện, cửa sổ kiểm định hay tỷ lệ phân chia nào — nó không thể nào là thứ ngoài mẫu được, và bảng đối chiếu ở trên chính là ví dụ.

Biểu đồ trên tab Backtest của workspace thì không gặp vấn đề này, đường đỏ ở đó ghi là «Sharpe thực tế». Ngoài ra, biểu đồ trong khung chat được tạo ra từ một lần chạy lại thủ công, mà lần chạy lại thủ công không nhất thiết dùng đúng tham số như lần tự động, nên các con số trên biểu đồ cũng không nhất thiết bằng dòng trên tab.

Bẫy 1: số lần hoán vị bản thân nó cũng là một ngưỡng, và nó bị cắt tự động

MCPT tự động thật sự chạy bao nhiêu lần thì không hoàn toàn do bạn quyết. Nó có một công thức ngân sách thực thi:

số lần chạy thật = min( MCPT_N, 20000, max( 200, 4e8 ÷ số nến ) )

Càng nhiều nến thì 4e8 ÷ số nến càng nhỏ. Lần chạy ở trên có 41,287 cây nến, còn cách trần rất xa, nên 2000 lần không bị cắt lần nào. Nhưng bảng đối chiếu ghi trong tài liệu chính thức là: 40,000 nến → 2000 lần; 200,000 nến (5 phút trong hai năm) → 2000 lần; 1,000,000 nến (1 phút trong hai năm) → 400 lần.

Vấn đề là cổng lên sàn chặn ở số lần hoán vị ≥ 1000. Suy ra từ công thức, khi số nến vượt khoảng 400,000 thì số lần chạy thật sẽ xuống dưới 1000 — dù p = 0.001 thì tiêu chí vẫn phán là không qua. Mà màn hình hoàn toàn không gợi ý gì: thông báo bị cắt chỉ được ghi vào log của máy chủ, dòng diễn giải trên workspace vẫn thản nhiên điền con số đã bị cắt, và không hề có kiểu cảnh báo nào.

Mẹo: Ngưỡng 400,000 nến đó là phép tính suy ra từ công thức (4e8 ÷ số nến < 1000), lần chạy trong bài này chưa kích hoạt nó nên chưa được kiểm chứng thực tế; còn «1 phút trong hai năm → 400 lần» là bảng đối chiếu ghi trong tài liệu chính thức. Ngoài ra, chỉnh MCPT_N lớn lên không cứu được chuyện này — MCPT_N chỉ nâng trần lên thôi, thứ thật sự ghìm số lần xuống là số hạng max(200, 4e8 ÷ số nến). Khi nến quá nhiều thì chỉ có hai đường: rút ngắn giai đoạn backtest, hoặc đổi sang khung nến dài hơn.

Bẫy 2: Sharpe ở đường đỏ và Sharpe trên thẻ không phải cùng một con số

Nhìn lại hai bảng ở trên: cùng một backtest mà thẻ Sharpe Ratio ghi 0.6751, còn «Sharpe thực tế» của biểu đồ tần suất ghi 0.8985, lệch 0.22, mắt thường cũng thấy. Đây không phải lỗi, mà là hai cách tính khác nhau:

Mục Sharpe bên trong MCPT(0.8985 Sharpe Ratio trên tab Backtest(0.6751
Lợi nhuận tính thế nàoLợi nhuận đơn giản, không tách đoạn mở cửa với đoạn đóng cửaĐoạn qua đêm và đoạn trong phiên định giá riêng
Co giãn vị thếLuôn được áp:30% biến động mục tiêu、2 lần trần đòn bẩyChỉ có khi chính chiến lược tự viết vào

Mấu chốt nằm ở dòng thứ hai: MCPT tự động không truyền thiết lập biến động mục tiêu của chính chiến lược vào, nó luôn dùng giá trị mặc định của hàm. btc_sma_cross không hề nhắm mục tiêu biến động, nhưng Sharpe MCPT của nó vẫn là con số «sau khi đã áp 30% biến động mục tiêu và trần 2 lần» (biến động mục tiêu làm gì, xem Nhắm mục tiêu biến động hoạt động ra sao). Hai con số khác nhau là bình thường, hiện giao diện chưa giải thích chuyện này.

Tab Backtest không có dòng MCPT?

Khi thất bại thì nó im lặng: không có thông báo lỗi nào, backtest vẫn thành công như thường, chỉ là giao diện thiếu mất dòng đó. Có ít nhất bốn nguyên nhân, mà màn hình thì không phân biệt:

Nguyên nhânChuyện gì đã xảy ra
Chiến lược có viết MCPT = FalseBỏ qua thẳng
Lần backtest này có 0 giao dịchKhông có vị thế nào để kiểm định nên bỏ qua
lib trên máy chủ là bản cũNạp thất bại nên bỏ qua
Dữ liệu quá ngắn, Sharpe ra NaN, hoặc bất kỳ ngoại lệ nàoBỏ qua, thống kê backtest vẫn ghi như thường

Lý do thà không ghi còn hơn ghi sai rất thực tế: bất kỳ NaN hay inf nào cũng làm việc tải chiến lược của cả máy bị trả về, nên hễ con số chưa sạch là cả nhóm trường này không được ghi.

Chiến lược danh mục không có MCPT, và cũng không có thứ thay thế

Backtest của Type C (chiến lược danh mục, N mã kèm một bộ tỷ trọng) không chạy MCPT. Lý do là cấu trúc của kiểm định này là «một chuỗi giá đối một chuỗi vị thế», mà chiến lược danh mục không có cấu trúc đó. Điều đáng nói thẳng là: không có kiểm định thay thế.

Việc bạn muốn làmThực tế ra sao
Nén cả danh mục thành một chuỗi lợi nhuận rồi rút mẫu lạiĐó là bootstrap trên lợi nhuận đã thực hiện, nó trả lời một câu hỏi khác, mà lại không sinh ra được giá trị p — giá trị p mới chính là toàn bộ lý do MCPT tồn tại. Con đường này bị cấm bằng văn bản, và agent cũng không được tự tay nặn ra thứ gì để thế chỗ
Điều bạn mong thẻ tiêu chí hiển thị«Không áp dụng MCPT (chiến lược danh mục)», chứ không phải «Chưa đạt» — nó không tính là trượt và không ảnh hưởng tới huy hiệu «Đã kiểm định»

Có giá trị p rồi thì kiểm năm điều này trước

1

Số lần hoán vị là bao nhiêu? Nếu con số trong dòng diễn giải thấp hơn 1000 thì giá trị p này không qua nổi cổng lên sàn, nhỏ tới đâu cũng vậy. Xem số lần trước, xem giá trị p sau.

2

Đây là giá trị p của lần backtest nào? Quá trình quét tham số không chạy MCPT. Giá trị p trong tay bạn chỉ thuộc về «lần backtest chạy sau khi đã chọn tham số»; sửa mã ở giữa chừng thì phải chạy lại mới tính.

3

Đường đỏ và thẻ không khớp nhau? Bình thường. Lần chạy ở trên là 0.8985 so với 0.6751, chỉ là khác cách tính thôi, không cần đi debug.

4

Bạn có đang đọc «có ý nghĩa» thành «sẽ có lãi» không? Cùng một chiến lược có thể đạt ở p = 0.016 rồi thua đậm với hiệu suất ngoài mẫu −0.334. Giá trị p không phát biểu gì về tương lai cả.

5

Dòng đó biến mất hẳn? Trước hết xác định nó là cái nào trong bốn nguyên nhân (chiến lược tắt đi / 0 giao dịch / lib bản cũ / không đủ dữ liệu), đừng coi là «kiểm định không qua».

Nhắc thật lòng: MCPT kiểm định xem bộ điểm vào ra cố định này đặt trên đoạn lịch sử đó thì tốt hay không. Nó không biết bộ tham số này được chọn ra từ hàng trăm ứng viên — độ lệch lạc quan do chính việc chọn lựa tạo ra thì nó không đo được. Đó là lý do chọn tham số phải chọn cao nguyên chứ không chọn đỉnh nhọn (xem Cách tránh overfitting chiến lược), và cũng là lý do chiến lược trong bài này qua được giá trị p mà ngoài mẫu vẫn rơi. Ngay cả diễn giải của chính workspace cũng chỉ viết «0.05 là ngưỡng thông thường» — đó là thông lệ, không phải bảo đảm.

Tiếp theo

Hiểu giá trị p rồi thì câu hỏi kế tiếp chính là nửa bên phải của bảng đối chiếu ở trên: đổi sang một đoạn dữ liệu chưa từng thấy thì còn đứng vững không. Con đường đó là kiểm định ngoài mẫu, và workspace có tab riêng để làm. Những chiến lược chính thức mang huy hiệu «Đã kiểm định» trong Thư viện chiến lược, thứ mà chúng qua ở tiêu chí 2 chính là kiểm định nói trong bài này — mỗi chiến lược đều kèm backtest thật, con số xem được ngay: Thư viện chiến lược.