Phải Làm Gì Khi Chính Backtest Engine Của Mình Bị Sai?
· #research-method #backtesting #reproducibility #validation #systematic-trading
Mình ngừng để nó làm trọng tài, và mình nói thẳng điều đó ra. Khi engine nghiên cứu của mình và MetaTrader 5 cho hai kết quả trái ngược trên cùng một chiến lược trong cùng một giai đoạn, mình quyết định chỉ dòng khớp lệnh thật của broker mới được tính là kết quả, chạy lại toàn bộ phần việc bên dưới chuẩn đó, và ghi lại rõ ràng những con số cũ đã từng khẳng định điều gì.
Là một quant tự học, mình đã im lặng một quãng, và đó không phải vì mình hết nội dung. Đó là lúc mình kiểm tra lại bài làm của chính mình. Dưới đây là những gì mình tìm thấy, bao gồm cả những phần đi ngược lại chính mình.
Mình phát hiện engine của mình sai bằng cách nào?
Cùng một chiến lược, cùng một giai đoạn, nhưng kết quả trả về ở hai nơi lại trái dấu nhau.
Chuyện đó lặp lại ba lần riêng biệt trước khi mình dám gọi nó là một quy luật thay vì một sự cố ngẫu nhiên. Một cấu hình trailing stop lãi khoảng 24 phần trăm trong engine và lỗ khoảng 24 phần trăm trong MetaTrader 5. Một bộ lọc chặn vào lệnh dựa trên mức hấp thụ tại node khối lượng trông như trung tính trong engine nhưng sang MetaTrader 5 thì thảm khốc, âm khoảng 95 phần trăm net. Một cấu hình Bitcoin báo profit factor gần 1,29 trong engine so với khoảng 1,06 trong MetaTrader 5, tức là khoảng cách giữa một edge nhỏ và không có edge nào cả. Những con số đó nằm trong ghi chú robustness của chính mình trong repo nghiên cứu DQuants, đề ngày 2026-06, và tất cả đều là kết quả backtest cùng các lần chạy xác nhận, không phải kết quả live.
Hai trong ba trường hợp đó là tin xấu mà mình suýt tin theo. Đó không phải một chú thích nhỏ. Nếu cái thước đo bị sai, thì mọi con số từng được đo bằng nó đều phải bị xem lại, kể cả những con số mình thích.
Engine của mình đã sai chính xác ở chỗ nào?
Ở phần thoát lệnh, và sai theo đúng một hướng.
Một engine nghiên cứu chỉ là một mô hình của thị trường, và mô hình thì được phép sai. Mô hình của mình sai theo một cách rất cụ thể và học được. Nó xử lý việc thoát lệnh trong bar theo một chuỗi tick rời rạc và không thể biết giá đã đi thế nào bên trong một tick, nên khi một lệnh có thể đã bị quét stop hoặc bị trail ra giữa chừng, engine có xu hướng chọn phương án dễ chịu hơn trong hai phương án. Cấu trúc feed còn làm chuyện đó tệ hơn. Trên dữ liệu của mình, tỷ lệ giá đi tiếp sau breakout nằm quanh 45 phần trăm, trong khi cùng phép đo đó trên feed của một nhà cung cấp khác lại ra gần 94 phần trăm, nghĩa là hình dạng các nhịp hồi thậm chí không giống nhau.
Kết luận mình ghi lại được viết hẹp một cách có chủ ý: các con số phía vào lệnh của engine là đáng tin, còn các con số phía thoát lệnh thì ghi nhận quá hào phóng cho những lệnh chạy dài một cách có hệ thống. Cách hạch toán thoát lệnh dễ dãi có thể biến một hệ thống trung bình thành một đường equity rất đẹp trên màn hình. Rồi một broker thật khớp lệnh ở giá thật, và vẻ đẹp đó biến mất.
Mình đã thay bằng quy tắc nào?
Chỉ một trọng tài duy nhất, và trọng tài đó không phải code của mình.
Từ giờ chỉ dòng deal thật của broker trên tick thật mới được tính là kết quả. Engine chỉ đề xuất, engine không phân xử. Trên thực tế, điều đó nghĩa là các ý tưởng phía vào lệnh vẫn có thể được quét trong engine, nơi nó nhanh và trung thực, còn mọi ý tưởng phía thoát lệnh phải đi thẳng sang một bài test A và B trong MetaTrader 5 trước khi được phép thay đổi bất cứ thứ gì. Cách này chậm hơn, và đó cũng là phiên bản duy nhất mình có thể bảo vệ về sau.
Bên dưới quy tắc đó là một cổng mà mình muốn giới thiệu với bất kỳ kỹ sư nào đang trade bằng code của chính mình. Trước khi một bản build được deploy, output từng lệnh của nó phải trùng khớp từng byte với lần chạy tham chiếu, tính cả checksum. Cổng đó từng bắt được một lỗi làm cho kết quả báo cáo tốt lên chứ không phải xấu đi. Nhìn bằng mắt vào một đường lợi nhuận thì sẽ cho lỗi kiểu đó đi qua rất vui vẻ. Checksum thì không. Một bản review độc lập theo hướng phản biện gay gắt mà mình đặt hàng vào tháng 8 năm 2026 đã đánh giá thứ tự thẩm quyền này là đúng, theo cách diễn đạt của người review: nền tảng mà mình không kiểm soát được có quyền phủ quyết engine do chính mình viết.
Những giả định tiện lợi nào vẫn còn trong mô hình chi phí của mình?
Có ba, và mình muốn người đọc nghe điều này từ mình.
Tick engine hiện tại của mình có mô hình spread và commission tùy chọn, nhưng slippage, latency và swap đều đang để bằng không. Thêm vào đó, feed vàng mà mình nghiên cứu có spread hẹp hơn khoảng mười lần so với mức mình thật sự thấy trên tài khoản live, khoảng 19 điểm so với 189 điểm. Ghi chú của chính mình xếp đây là khoảng trống có giá trị sự thật cao nhất trong toàn bộ stack và đồng thời cũng là khoảng trống rẻ nhất để lấp, đó là một câu khó chịu khi mình đã viết ra mà vẫn chưa xử lý xong.
Một giả định tiện lợi giống như một khoản vay. Thị trường sẽ đến thu, thường kèm phí. Công bố phần dư nợ đó không phải là khiêm tốn, đó chỉ là phiên bản chính xác.
Vì sao mình không chọn đưa ra đúng cửa sổ dữ liệu đẹp?
Bởi vì gần như thứ gì cũng có một cửa sổ thắng, nếu mình chịu khó săn đủ lâu.
Mình có một ví dụ cụ thể ngay trong hồ sơ của mình. Một cấu hình Bitcoin âm net trên toàn bộ cửa sổ kiểm định, nhưng lại dương net trên phần dữ liệu gần đây hơn. Cả hai mệnh đề đều đúng. Chỉ báo cáo mệnh đề thứ hai là marketing kèm một bảng tính, và đó là kiểu nói dối dễ nhất trong ngành này vì người viết không cần đặt bút viết một câu sai nào. Chuyện tương tự cũng đúng với cấu hình vàng mỏng hơn của mình, nơi profit factor tiêu đề quanh 1,43 gần như được gánh bởi đúng một quý rất thuận, còn riêng trên cửa sổ huấn luyện thì con số đó chỉ quanh 1,15.
Vì vậy giai đoạn out-of-sample gần nhất và chưa bị chạm vào là bài test công bằng duy nhất mà mình cho phép mình báo cáo, và mình báo cáo nó dù kết quả làm mình đẹp lên hay xấu đi. Sự trung thực trong backtest hóa ra phần lớn nằm ở những con số mình từ chối sử dụng.
Con số nào đọc lại thấy đau?
Có hai, và con số thứ hai còn nặng hơn.
Con số thứ nhất là live. Phần tiền thật duy nhất mà logic chủ lực của mình từng chạy là một dòng nhỏ trên tài khoản prop, gồm 62 lệnh với profit factor 0,68, tức là dưới hòa vốn và đồng thời cũng không có ý nghĩa thống kê ở cỡ mẫu đó. Cách nói trung thực ở đây không phải là một tỷ lệ phần trăm. Đó là chưa có bằng chứng live nào cả, theo cả hai chiều.
Con số thứ hai đến từ bản audit độc lập kia. Con số robustness mà mình trích dẫn nhiều nhất, deflated Sharpe ratio bằng 1,000, hóa ra dựa trên một cổng mà với mức phân tán trial mình đã ghi lại thì phải cần tới cỡ mười lũy thừa mười bốn cấu hình ứng viên trước khi nó có thể trượt. Câu của người audit còn thẳng hơn bất cứ câu nào mình tự viết ra: kết luận đó không phải bằng chứng, đó chỉ là số học. Tệ hơn, bài test thật sự có khả năng phân biệt, tức xác suất overfitting của backtest, cho kết quả trên 0,5 ở tám trên chín ô, và mình đã để một lối kể chuyện lấn át nó.
Không điều nào trong đó chứng minh edge là giả. Nó chứng minh rằng phần bằng chứng mình đang dẫn đầu không đủ khả năng cho mình biết nếu edge là giả.
Vì sao lại công khai tất cả những chuyện này?
Bởi vì mình không thể tách việc không nói dối chính mình ra khỏi việc không nói dối người đọc.
Một thương hiệu chỉ đăng ngày thắng thì chỉ là một cái máy đánh bạc có gắn logo. Nhật ký nghiên cứu phía sau các bài viết này hiện có 137 thí nghiệm, trong đó 45 bị bác bỏ hoàn toàn và 72 chỉ dừng ở mức nghiên cứu, so với 11 thí nghiệm đi được tới một cấu hình khóa. Tức là khoảng một trên mười hai, với mọi thất bại đều được giữ lại và tra cứu được, và con số đó mô tả công việc này chính xác hơn bất kỳ đường cong đơn lẻ nào. Trong đó còn có một dự đoán của chính mình đã sai và được ghi lại là sai: mình đoán rằng chưa tới 25 trong khoảng 180 tham số được phơi ra thật sự làm thay đổi lệnh nào được vào, còn kết quả đo được là 49.
Mình thà đi chậm mà đúng một cách công khai còn hơn đi nhanh mà sai trong im lặng. Bản audit làm mình ngượng hôm nay chính là phần uy tín mình còn được phép dùng về sau.
Câu hỏi thường gặp
Khi backtest engine và MetaTrader 5 cho kết quả khác nhau thì điều đó nghĩa là gì? Nghĩa là một trong hai đang mô hình hóa thứ mà bên kia không mô hình hóa, và cho tới khi mình biết bên nào, không con số nào dùng được. Trong trường hợp của mình, sự khác biệt tập trung ở phía thoát lệnh và có hướng rõ ràng chứ không ngẫu nhiên, đó là dấu hiệu của một thiên lệch mô hình chứ không phải nhiễu. Nền tảng nằm ngoài tầm kiểm soát của mình được quyền đúng theo mặc định.
Vì sao backtest engine hay ghi nhận quá hào phóng ở phía thoát lệnh? Bởi vì một bar hay một tick không phải là một đường đi. Nếu stop và target cùng nằm trong một khoảng, engine buộc phải đoán thứ tự chúng bị chạm, và cái đoán dễ chịu đó cộng dồn qua từng lệnh có phần chạy dài. Quyết định vào lệnh không gặp vấn đề này, vì nó được đưa ra tại một mức giá mà engine thật sự nhìn thấy.
Làm sao để một lỗi trong engine không làm đẹp kết quả của mình? Bằng cách để một hệ thống mình không kiểm soát làm trọng tài, và bằng cách gắn điều kiện deploy vào khả năng tái tạo chính xác chứ không vào một đường cong trông giống nhau. Cổng deploy của mình yêu cầu output từng lệnh của bản build phải trùng từng byte với lần chạy tham chiếu. Nó từng bắt được một lỗi làm tăng lợi nhuận báo cáo, đúng loại lỗi mà một lần review bằng mắt sẽ cho đi qua.
Một cửa sổ out-of-sample thua có phải lý do để bỏ chiến lược không? Tự nó thì chưa, nhưng đó là lý do để ngừng trích dẫn cửa sổ đẹp. Một cấu hình của mình âm net trên giai đoạn gần nhất chưa bị chạm vào, trong khi một cửa sổ cũ hơn và dễ chịu hơn vẫn tồn tại. Cửa sổ cũ đó chồng lên đúng phần dữ liệu đã dùng để xây hệ thống, nên đem nó ra dùng là chọn lọc chứ không phải bằng chứng.
Vì sao cherry-picking trong backtest lại khó phát hiện? Bởi vì nó không cần tới một câu sai nào. Mọi con số trong một báo cáo được chọn lọc đều có thể đúng riêng lẻ trong khi toàn bộ báo cáo vẫn gây hiểu nhầm, vì phần không trung thực nằm ở việc chọn cửa sổ. Cách phòng vệ là cố định cửa sổ báo cáo trước khi nhìn kết quả, chứ không phải sau đó.
Phần nào của MasterVP đọc biểu đồ, và phần nào giao dịch? Cả hai phần đều tồn tại, và sự phân chia đó rất quan trọng. MasterVP Profiler là indicator, có trên TradingView và trên MetaTrader 5: nó cho bạn thấy cách đọc thị trường, nơi khối lượng thực sự giao dịch và cấu trúc biểu đồ hình thành ra sao, còn quyết định đặt lệnh vẫn thuộc về bạn. Nó không phải dịch vụ tín hiệu. Phần thực thi nằm ở phía Expert Advisor, và phần đó chỉ giao dịch theo đúng quy tắc mà người dùng tự cấu hình.
Có track record live nào phía sau các con số này không? Chưa có, và mình sẽ không ám chỉ điều ngược lại. Mọi con số phía trên đều là backtest, là một lần chạy xác nhận trong MetaTrader 5 trên tick lịch sử thật, hoặc là phát hiện từ một bản audit độc lập về quy trình của mình. Dòng live duy nhất mình có gồm 62 lệnh và dưới hòa vốn, quá nhỏ để kết luận bất cứ điều gì. Kết quả live nên được kỳ vọng là mỏng hơn kết quả nghiên cứu, vì slippage, latency và swap chưa có trong mô hình chi phí của mình.
Nói đơn giản thì parity gate là gì? Đó là quy tắc buộc một phần mềm phải tái tạo chính xác một lần chạy tham chiếu đã biết trước khi được phép đứng gần tiền thật. Không phải gần đúng, và không phải chỉ khớp ở các chỉ số tổng hợp, mà là khớp từng lệnh một. Đây là thực hành bình thường trong kỹ thuật phần mềm, và cũng là thói quen duy nhất từ công việc chính của mình chuyển sang nghiên cứu trading mà không cần sửa gì cả.
Viết bởi KenKem, một kỹ sư phần mềm và nhà sáng lập với hai mươi năm kinh nghiệm, đang học quantitative trading một cách công khai và chia sẻ lại toàn bộ quá trình, kể cả những lần bị bác bỏ.
Bài viết này được tổng hợp từ các post 221 đến 230 trong chuỗi build-log của KenKem. Chỉ nhằm mục đích giáo dục. Không phải lời khuyên đầu tư. Mọi con số được dẫn ra đều là kết quả backtest, kết quả chạy xác nhận hoặc kết quả audit, không phải track record live. Hiệu suất quá khứ không bảo đảm kết quả tương lai.