Vì sao một backtest đẹp có thể chẳng nói lên điều gì — và cách phân biệt giữa lợi thế thật và ảo giác do khớp đường cong.
Bạn quét xong tham số, chọn tổ hợp có Sharpe cao nhất, và đường cong backtest đẹp tới mức khó tin. Đến khi chạy thật, hiệu suất của nó không khác gì ngẫu nhiên. Thứ đã xảy ra ở giữa có một cái tên.
Một chiến lược bị overfit khi các tham số của nó được tinh chỉnh khớp với dữ liệu lịch sử chính xác đến mức chiến lược thực chất đang học thuộc quá khứ thay vì học một quy tắc có thể tổng quát hóa. Nó trông xuất sắc trong backtest và chạy kém — hoặc ngẫu nhiên — khi giao dịch thật.
Overfitting không phải lúc nào cũng là cố ý. Nó xảy ra một cách tự nhiên bất cứ khi nào bạn:
Giả sử bạn quét 100 tổ hợp tham số. Ngay cả khi chiến lược của bạn không có lợi thế nào, vẫn có một số tổ hợp cho ra Sharpe dương thuần túy do tình cờ — khoảng 5 trên 100 ở mức ý nghĩa 5%. Nếu bạn chọn tổ hợp tốt nhất rồi báo cáo nó như hiệu suất của chiến lược, bạn đang báo cáo nhiễu như thể đó là tín hiệu.
Vấn đề này càng tệ hơn khi bạn quét càng nhiều tham số. Một chiến lược có 2 tham số độc lập trên lưới 20×20 có 400 tổ hợp. Một chiến lược có 4 tham số trên lưới 10×10×10×10 có 10,000. Mỗi chiều thêm vào lại nhân lên khả năng vô tình vấp phải một đỉnh may mắn.
Kỹ thuật thực hành quan trọng nhất để tránh overfitting là đừng chọn cái đỉnh.
Hãy nhìn một heatmap Sharpe từ một lần quét tham số. Ô tốt nhất tuyệt đối có thể cho Sharpe 1.8. Dịch một bước sang trái: 0.4. Dịch một bước lên trên: 0.3. Cái đỉnh đó rất mong manh — nó tồn tại vì đúng tổ hợp tham số ấy tình cờ khớp với vài sự kiện thị trường cụ thể trong dữ liệu huấn luyện của bạn.
Thay vào đó, hãy tìm cao nguyên: vùng mà Sharpe luôn cao trên nhiều tổ hợp tham số lân cận. Nếu Sharpe là 1.2 trên cả một lân cận 3×3 các giá trị tham số, đó là tín hiệu bền vững — lợi thế tồn tại trên cả một dải tham số, chứ không chỉ tại một điểm cụ thể.
# find_plateau của Blave Agent: chọn lân cận có Sharpe # trung bình cao nhất trong cửa sổ 3×3 quanh mỗi ô — # không phải ô đơn lẻ có giá trị lớn nhất. best_idx = argmax(neighborhood_average_sharpe)
Đây là lý do script quét tham số — chính là scan.py nằm trong thư mục của từng chiến lược — dùng find_plateau thay vì chỉ lấy Sharpe tốt nhất. Một bộ tham số nằm trên một cao nguyên phẳng có khả năng tổng quát hóa cao hơn nhiều so với bộ nằm trên một đỉnh nhọn bị bao quanh bởi hiệu suất kém.
Cách hợp lệ duy nhất để đo hiệu suất thật của một chiến lược là kiểm tra nó trên dữ liệu nó chưa từng thấy. Nếu bạn tối ưu tham số bằng dữ liệu 2020–2023 rồi đánh giá trên 2024, hiệu suất 2024 là ước lượng ngoài mẫu thực sự.
Nếu bạn tối ưu trên 2020–2024 rồi báo cáo hiệu suất 2020–2024: đó là trong mẫu. Ngay cả khi bạn không có ý overfit, quá trình tối ưu đã ngầm dùng kết quả của 2020–2024 để chọn tham số. Sharpe được báo cáo đã bao gồm lợi thế của việc biết trước.
| Phương pháp | Hợp lệ? | Ghi chú |
|---|---|---|
| Tối ưu trên toàn bộ lịch sử, báo cáo hiệu suất toàn bộ lịch sử | ✗ | Trong mẫu — chắc chắn overfit |
| Tối ưu trên 70% đầu, báo cáo hiệu suất 30% cuối | ✓ | Chia train/test đơn giản — hợp lệ nhưng hạn chế |
| Walk-forward: tối ưu trên cửa sổ trượt, báo cáo kỳ kế tiếp | ✓✓ | Sát thực tế nhất — cùng cách làm như giao dịch thật |
Một cái bẫy overfitting phổ biến khác là quét toàn bộ dải giá trị tham số có thể có, kể cả những giá trị cực đoan tình cờ cho Sharpe cao trên một giai đoạn lịch sử bất thường.
Blave Agent dùng cách khác cho các chiến lược dựa trên ngưỡng: suy ra dải quét từ chính phân bố của chỉ báo. Nó lấy p5 và p95 trong phân bố lịch sử của chỉ báo làm hai đầu, rồi lấy trung điểm số học của hai giá trị đó — không phải trung vị — làm ranh giới; ứng viên vào lệnh nằm ở nửa trên (từ trung điểm tới p95), ứng viên thoát lệnh nằm ở nửa dưới (từ p5 tới trung điểm). Cách này giữ cả hai ngưỡng bám vào dải dữ liệu đã quan sát được, tránh những giá trị cực đoan rõ ràng là phi thực tế.
Đỉnh nhọn trên heatmap Sharpe. Tổ hợp tham số tốt nhất bị bao quanh bởi những giá trị thấp hơn hẳn. Dịch một bước tham số theo bất kỳ hướng nào cũng làm Sharpe giảm 0.5+. Lợi thế đó không bền vững — nó chỉ mang tính cục bộ.
Sharpe của backtest không sống sót ngoài mẫu. Chiến lược cho Sharpe 2.0 trên giai đoạn huấn luyện, 0.2 trên giai đoạn kiểm tra được giữ riêng. Khoảng cách càng lớn thì chiến lược càng bị overfit.
Quá ít giao dịch. Một chiến lược có 12 giao dịch trong 3 năm backtest gần như không có sức mạnh thống kê. Vài giao dịch may mắn có thể tạo ra Sharpe 2.0+ hoàn toàn do tình cờ. Hãy đòi hỏi ít nhất 30–50 giao dịch hoàn tất trước khi coi các chỉ số backtest là có ý nghĩa.
Tham số tròn một cách đáng ngờ. Nếu tham số tốt nhất của bạn là SMA(50) / SMA(200) hoặc RSI(14) hoặc đúng bằng 1.0 / −1.0, đó thường là kết quả của việc quét rồi may mắn rơi vào một giá trị "mặc định" quen thuộc. Chúng có thể chạy được vì lý do sai.
Chiến lược chỉ chạy được trong một chế độ thị trường. Nếu hiệu suất backtest hoàn toàn do một giai đoạn tăng hoặc giảm tạo ra, còn đi ngang hoặc âm ở mọi giai đoạn khác, có thể chiến lược đã học đúng chế độ thị trường đó chứ không phải một quy tắc chung.
Không có ngưỡng phổ quát nào, nhưng có một nguyên tắc hữu ích: mỗi tham số tự do thêm vào đều phải có một lý do tiên nghiệm để tồn tại trong logic chiến lược. Đừng thêm một tham số vì nó giúp bạn khớp dữ liệu tốt hơn — hãy thêm vì nó đại diện cho một chiều thực sự của cơ chế thị trường mà bạn đang khai thác.
Một chiến lược có hai tham số đại diện cho những khái niệm có ý nghĩa (ví dụ một cửa sổ tín hiệu nhanh và một cửa sổ xác nhận chậm) dễ bảo vệ hơn nhiều so với một chiến lược sáu tham số, trong đó vài tham số được thêm vào để "gọt bỏ" những giai đoạn thua lỗ cụ thể nhìn thấy trên biểu đồ.