Có một câu hỏi mà mọi backtest đều né tránh: liệu cùng đoạn code đó, khi được nạp chính thị trường đó theo thời gian thực, có hành xử y hệt không? Bạn không thể trả lời bằng cách backtest thêm. Chỉ có một cách: chạy chiến lược trên dữ liệu trực tiếp với ngữ nghĩa khớp lệnh thật, rồi kiểm tra — từng giao dịch một — rằng hai thế giới ấy khớp nhau. Chúng tôi chạy phép kiểm tra này mỗi ngày, hoàn toàn tự động, và nó là công cụ săn lỗi hiệu quả nhất trong toàn bộ nền tảng.
Ngày đầu tiên: dựng tấm gương soi
Mỗi chiến lược được thăng hạng chạy trên một node trực tiếp riêng biệt, tiêu thụ luồng dữ liệu thật từ sàn: báo giá trực tiếp để khớp lệnh, spread thật, giờ phiên thật. Không có state dùng chung giữa các chiến lược (chúng tôi đã học bài này theo cách đau đớn — một node dùng chung khiến truy vấn vị thế của chiến lược này rò rỉ sang chiến lược khác). Mỗi lệnh khớp đều được ghi lại kèm giá trị tín hiệu đã tạo ra nó.
Mỗi sáng: replay và đối chiếu
Một tác vụ chạy hằng ngày lấy khung thời gian paper của từng chiến lược, chạy đúng đoạn code đó qua engine backtest trên cùng giai đoạn với dữ liệu catalog, rồi ghép các lệnh khớp một-đối-một: cùng chiều, cùng dung sai thời gian, cùng khối lượng. Kết quả không phải là một hệ số tương quan hay một cảm giác mơ hồ. Đó là một bảng: khớp, chỉ có ở paper, chỉ có ở replay. Một chiến lược khỏe mạnh đạt 54 trên 55 khớp. Một chiến lược có vấn đề sẽ chỉ thẳng cho bạn giao dịch nào lệch và lệch lúc nào.
Những gì đã hỏng, theo đúng thứ tự chúng hỏng
- Lệnh khớp trong giai đoạn warmup. Node trực tiếp nạp dữ liệu lịch sử để làm nóng các chỉ báo — và những chiến lược có bộ xử lý bar tùy chỉnh vô tư giao dịch trên đó. Backtest chẳng bao giờ thấy các lệnh vào ma đó. Bản vá nằm ở tầng gửi lệnh, chứ không phải ở từng chiến lược, vì nếu không thì mọi chiến lược đều thừa hưởng con bug này.
- Warmup tuần tự cho chiến lược nhiều chân. Một chiến lược cross-sectional làm nóng từng công cụ một; mỗi chân đều thấy các chân anh em rỗng và mốc neo không bao giờ được khởi tạo. Chạy trực tiếp thì nó không giao dịch gì; replay thì chạy ngon lành. Không một giao dịch nào khớp — nhưng chỉ có phép đối chiếu mới nói thẳng điều đó ra.
- Vòng lặp sập vì độ chính xác của tick. Tick aggTrade trực tiếp về với độ chính xác khác so với công cụ trong sandbox mong đợi. Node sập đi sập lại vì một assertion mà backtest không đời nào kích hoạt được, bởi bar trong catalog đã được chuẩn hóa sẵn.
- Chứng mất trí nhớ khi khởi động lại. Việc khởi động lại tiến trình kích hoạt lại logic vào lệnh trên những vị thế vốn đã tồn tại — với một chiến lược tái cân bằng, một lần restart lặng lẽ biến thành một lần rebalance. Trong giao dịch thật, đó không phải là một chú thích nhỏ về tính tương đương; đó là cờ đỏ có tiền đi kèm.
Hãy để ý quy luật: không có cái nào trong số này là lỗi tín hiệu. Tất cả đều là lỗi ở ranh giới khớp lệnh — warmup, state, độ chính xác, khởi động lại. Đó chính là lớp lỗi mà backtest mù về mặt cấu trúc, bởi trong một backtest thì engine backtest chính là ranh giới khớp lệnh.
Những bất đối xứng chúng tôi cố ý giữ lại
Hai thế giới này được chủ ý làm cho không giống hệt nhau. Backtest tính một nửa spread theo mô hình cộng với tác động thị trường theo quy luật căn bậc hai ngay trong PnL; paper trả spread thật ngay trong giá khớp và không có mô hình tác động nào. Cộng thêm spread mô hình vào paper (hoặc spread thật vào backtest) sẽ là tính hai lần. Tương đương nghĩa là cùng quyết định, cùng lệnh khớp trong phạm vi dung sai — chứ không phải PnL giống nhau đến từng bit. Biết được khác biệt nào là có nguyên tắc và khác biệt nào là bug — đó mới là phần lớn của kỷ luật.
Backtest là một giả thuyết. Paper trading là thí nghiệm. Đối chiếu replay là cuốn sổ tay phòng lab bắt quả tang bạn đang tự lừa dối mình.
Nếu bạn đang chạy chiến lược mà không định kỳ đối chiếu hành vi trực tiếp với hành vi replay, thì có cả một lớp lỗi bạn chưa từng nhìn thấy. Chúng tôi tìm ra một lỗi như vậy khoảng vài tuần một lần, và mỗi lỗi đều vô hình khi nhìn từ bên trong backtest.
← Tất cả bài viết
