11 tháng Chín, 2026 · khả năng tái lập

Cùng mã nguồn, cùng dữ liệu, hai Sharpe khác nhau: kiểm toán tính không xác định mà backtest của bạn cần

Cùng mã nguồn, cùng dữ liệu, hai Sharpe khác nhau: kiểm toán tính không xác định mà backtest của bạn cần

Cùng commit, cùng các tệp parquet, cùng máy tính, hai lần chạy cách nhau một giờ. Sharpe 1.34 và Sharpe 1.19. Tổng lợi nhuận 41.2% và 37.8%. Số giao dịch 1,418 và 1,421. Hai đường vốn khớp nhau đến từng chữ số thập phân được in ra trong 11,205 nến liên tiếp, rồi mới tách ra.

0.15chênh lệch Sharpe, đầu vào giống hệt nhau
3 / 1,418giao dịch khác nhau
11,205nến giống hệt nhau trước khi tách nhánh

Ba con số đầu thật khó chịu. Con số cuối mới đáng chú ý, vì nó cho thấy vấn đề không phải là sai số dấu phẩy động lấm tấm suốt cả lần chạy. Có một sự kiện rời rạc xảy ra tại một nến cụ thể, rồi hiệu ứng cộng dồn kéo theo sau đó. Bài viết này nói về cách tìm ra nến ấy, và về tác động của mức chênh lệch 0.15 đó lên mọi lần quét tham số bạn từng chạy.

Chiến lược: động lượng cắt ngang trên 30 hợp đồng perpetual USDⓈ-M có khối lượng cao nhất, tái cân bằng mỗi 4 giờ, mua 5 mã có lợi nhuận 12 giờ cao nhất, bán khống 5 mã thấp nhất, dữ liệu lịch sử 18 tháng, tính phí và funding cho từng vị thế.

Tìm nến nơi hai lần chạy bắt đầu khác nhau

Nếu ghi log vốn theo từng nến với độ chính xác đầy đủ, việc này mất khoảng bốn phút. Xuất cả hai lần chạy thành CSV gồm (bar_index, equity, open_positions_hash), nạp cả hai tệp rồi tìm chỉ số đầu tiên chúng khác nhau. Chúng tôi đã viết sẵn tập lệnh đó để xử lý một lỗi khác; nếu không, tôi đã mất cả buổi sáng cho việc này.

Nến 11,206, thời điểm 2025-03-14T08:00 UTC. Vốn ở nến trước đó giống nhau đến 13 chữ số có nghĩa. Tại nến 11,206, mã băm vị thế khác nhau: lần chạy A mua SOL, lần chạy B mua AVAX. Cùng hướng, cùng giá trị danh nghĩa, khác mã. Mọi thứ sau đó đều là hệ quả.

Vì vậy, tôi in các đầu vào xếp hạng tại nến đó của cả hai lần chạy. Chúng giống hệt nhau. Từng byte một, cùng 30 mã, cùng 30 điểm số. Hai điểm số bằng 0.0. Chính xác là 0 ở cả hai, vì hai mã đều có nến 4 giờ với 0 giao dịch trong cửa sổ nhìn lại, nên tỷ lệ giá đóng cửa trên giá đóng cửa ra bằng 1, còn log của nó bằng 0. Không phải do làm tròn thành 0. Mà là 0.

Hai mã đồng hạng ở vị trí thứ năm. Bộ chọn lấy 5 mã đứng đầu. Mã nào trong hai mã được chọn phụ thuộc vào thứ tự chúng còn lại sau khi sắp xếp, nhưng phép sắp xếp không hoạt động như tôi tưởng.

Một trường hợp hòa điểm, phép sắp xếp không ổn định và điều tôi đã bỏ qua suốt hai năm

Việc xếp hạng chạy qua một phép tổng hợp theo nhóm, còn khung dữ liệu đầu vào được tạo từ một dict comprehension duyệt qua tập hợp mã được dựng lại ở mỗi nến từ một lần tải dữ liệu bất đồng bộ. Thứ tự duyệt tập hợp thay đổi theo seed băm, và Python ngẫu nhiên hóa seed băm chuỗi ở mỗi tiến trình trừ khi bạn cố định PYTHONHASHSEED. Vì thế, thứ tự hàng trước khi sắp xếp khác nhau giữa các lần chạy; khi dùng phép sắp xếp không ổn định với khóa bị hòa, thứ tự phá hòa cũng khác nhau.

Những trường hợp hòa như vậy không phải chuyện hiếm bất thường. Chúng là hệ quả cấu trúc. Bất cứ nơi nào một đặc trưng bão hòa hoặc bị chặn biên, bạn sẽ tạo ra giá trị bằng nhau tuyệt đối: nến không có khối lượng tạo lợi nhuận chính xác bằng 0, z-score bị chặn ghim tại ±3.0, phép biến đổi thứ hạng với ít giá trị phân biệt tạo ra hàng chục trường hợp hòa, bộ lọc boolean cho mọi trường hợp đạt điều kiện điểm 1.0. Trong 18 tháng với nến 4 giờ, lần chạy này có 47 nến hòa điểm tại ranh giới chọn. Ba trường hợp khiến rổ được chọn thay đổi. Các trường hợp còn lại là hòa giữa hai mã vốn đều đã được chọn hoặc đều bị loại.

Trong khoảng một ngày, tôi tin chắc bộ tải dữ liệu không có tính xác định, vì đó là câu trả lời ly kỳ hơn. Không phải vậy. Chưa bao giờ là vậy. Chỉ là một tập hợp, một trường hợp hòa điểm và một giả định về tính ổn định của phép sắp xếp mà chẳng ai ghi lại.

Vì sao ba giao dịch lại đáng giá 0.15 Sharpe

Đây là phần nhiều người phản đối, và câu trả lời là backtest dùng quy mô vị thế theo tỷ lệ vốn là một hệ thống phụ thuộc đường đi. Đặt quy mô mỗi vị thế bằng 8% vốn hiện tại nghĩa là chênh lệch vốn tại nến n sẽ tạo ra chênh lệch ở mọi giá trị danh nghĩa từ nến n trở đi.

Bản thân giao dịch tạo ra sai khác đầu tiên gần như không gây thiệt hại. Vị thế AVAX của lần chạy B lỗ 2.1% trong chín giờ; vị thế SOL của lần chạy A lãi 0.4%. Chênh lệch vốn sau giao dịch đó: 0.21%. Không đáng kể. Nhưng từ đó trở đi, hai lần chạy không còn là cùng một chiến lược. Quy mô vị thế nắm giữ hơi khác nhau, nên mức funding tích lũy cũng hơi khác; hai trường hợp hòa ở ranh giới sau đó lại được phá khác nhau vì điểm số đầu vào giờ được tính từ các vị thế nắm giữ khác đôi chút. Một trường hợp xảy ra vào 2025-03-27, một ngày trước xu hướng kéo dài sáu ngày tạo ra khoảng một phần ba tổng PnL của lần chạy. Lần chạy A nắm vị thế suốt cả nhịp tăng; lần chạy B vào lệnh trễ một kỳ tái cân bằng.

Chênh lệch lợi nhuận: 3.4 điểm phần trăm. Chênh lệch Sharpe lớn hơn mức chênh lệch lợi nhuận gợi ý, vì các giao dịch bị đổi thứ tự ở lần chạy B trùng với một giai đoạn biến động mạnh hơn, khiến mẫu số tăng trong khi tử số giảm. Nguyên nhân nhỏ, hai yếu tố khuếch đại.

Nếu bạn dùng quy mô danh nghĩa cố định và lệnh vào không phụ thuộc vào các vị thế đang nắm giữ, bạn sẽ ít bị ảnh hưởng hơn nhiều. Phần lớn chiến lược đáng quan tâm đều không thuộc hai trường hợp đó.

Năm nguồn gây ra vấn đề trên thực tế

NguồnTriệu chứngCách khắc phục
PYTHONHASHSEED không được cố định, thứ tự duyệt set/dict được dùng làm đầu vào cho phép sắp xếpThứ tự phá hòa đảo giữa các lần chạy; sai khác đầu tiên xuất hiện tại một nến cụ thểCố định seed; sắp xếp theo khóa phụ rõ ràng (mã) để kết quả khi hòa luôn xác định
Phép sắp xếp không ổn định khi khóa bị hòa (mặc định quicksort trong NumPy/pandas)Như trên, vẫn xảy ra dù đã cố định seedkind="stable", hoặc tạo khóa có thứ tự toàn phần
RNG không có seed trong bootstrap, xáo trộn tập huấn luyện/kiểm thử hoặc nhiễu ngẫu nhiên khi khớp lệnh mô phỏngSai lệch trên toàn bộ lần chạy, không có điểm bắt đầu khác biệt rõ ràngDùng một seed tường minh cho mỗi thành phần và ghi lại trong manifest của lần chạy
Phép rút gọn số thực song song (thứ tự cộng phụ thuộc số luồng)Sai khác ở vài bit cuối, thường vô hại cho đến khi vượt qua ngưỡng so sánhCố định số luồng cho các lần chạy nghiên cứu; không bao giờ so sánh số thực bằng == tại ranh giới quyết định
Phiên bản thư viện không được cố địnhCó thể tái lập hôm nay, nhưng không phải vào tháng 11Ghi hash của lockfile trong manifest cùng với hash của ảnh chụp dữ liệu

Hàng thứ tư ít khi quan trọng như mọi người lo ngại; hàng đầu tiên mới là thứ liên tục gây rắc rối.

Tái lập từng bit là một công cụ, không phải phẩm chất tự thân

Bạn cần tính xác định để khi thay đổi một dòng, phần chênh lệch trên đường vốn có thể quy về đúng dòng đó. Chỉ vậy thôi. Giờ đây, mỗi lần chạy agent trên Stratmill đều ghi manifest gồm hash ảnh chụp dữ liệu, hash lockfile và mọi seed; một lần chạy lại không tái tạo được đường vốn cũ từng bit được xem là bản dựng thất bại, chứ không phải chuyện đáng tò mò.

Nhưng khi đã tái lập được kết quả, hãy chủ động làm nó biến thiên. Chạy 64 lần với 64 seed và xem độ phân tán:

Biên độ dao động. Cùng chiến lược, cùng dữ liệu, 64 hoán vị có seed cho thứ tự phá hòa và thứ tự khớp lệnh. Sharpe p5 là 1.12, trung vị 1.27, p95 1.41. Độ rộng biên độ là 0.29.

Giờ hãy quay lại lần quét tham số. Cấu hình tốt nhất đạt 1.46. Cấu hình xếp thứ 40 trong số 96 đạt 1.31. Chênh lệch giữa chúng là 0.15, bằng một nửa biên độ. Lần quét không xếp hạng hai cấu hình đó. Nó lấy một mẫu từ phân phối của mỗi cấu hình rồi sắp xếp các mẫu ấy.

Cách nhìn đó đã thay đổi cách chúng tôi chọn chiến lược. Kết quả quét chỉ phản ánh thứ hạng nếu khoảng cách giữa các cấu hình lớn hơn mức dao động của một cấu hình đơn lẻ; với chiến lược phụ thuộc đường đi có 1,400 giao dịch, mức dao động thường đủ lớn để biến một phần ba đầu bảng xếp hạng thành một nhóm đồng hạng. Khi vậy, hãy chọn theo tiêu chí mà biên độ dao động không thể che lấp: tỷ lệ luân chuyển thấp hơn, ít tham số hơn, giả định chi phí mà bạn có thể bảo vệ trước một người hoài nghi, hoặc hành vi tốt hơn trong fold kiểm định cuốn chiếu mà bạn ít tin tưởng nhất. Đó mới là những tiêu chí phá hòa thực sự. Lợi thế Sharpe 0.15 thì không.

Còn một việc nên làm trước khi tin vào bất kỳ kết quả nào. Chạy backtest của bạn hai lần ngay bây giờ, so sánh vốn theo từng nến và tìm xem bạn thuộc nhóm kết quả giống hệt từng bit hay nhóm chênh lệch 0.15. Thử nghiệm này mất mười lăm phút và cho bạn biết bao nhiêu phần trong lịch sử nghiên cứu của mình đã đo lường chiến lược, còn bao nhiêu phần chỉ đo lường seed băm.

tính xác địnhbacktestkỹ thuật dữ liệuquá khớppython
← Tất cả bài viết