Sai lệch sống sót và sai lệch nhìn trước

Bộ lọc loại sạch mã đã dừng giao dịch ngay cả khi hạ sàn thanh khoản về 0, còn công cụ backtest thì nhận chúng rồi khớp lệnh trên những cây nến chưa từng có giao dịch nào.

FinLens48 phút đọc

Bài trước của chuỗi này, backtest là gì và đọc kết quả thế nào, bàn về những gì xảy ra sau khi phép tính bắt đầu: chỉ tiêu nào đo cái gì, giả định khớp lệnh, phí và trượt giá. Bài này lùi lại một bước, tới chỗ mà mọi chuyện đã hỏng từ trước khi dòng lệnh đầu tiên chạy.

Có hai cách một tập dữ liệu tự làm đẹp kết quả cho bạn. Cách thứ nhất là giấu bớt những mã đã chết, nên bộ quy tắc chỉ được thi trên những thí sinh đã biết là sống. Cách thứ hai là rò rỉ vào quá khứ một thông tin mà quá khứ chưa có, nên bộ quy tắc ra quyết định với những con số mà người thật ngồi ở thời điểm ấy không thể nhìn thấy. Cả hai đều không báo lỗi. Cả hai đều làm kết quả đẹp lên chứ hiếm khi xấu đi. Và cả hai đều nằm ở tầng dữ liệu, tức bên dưới mọi thứ mà bài trước đã nói.

Bài này không đi từ định nghĩa xuống ví dụ mà đi ngược lại: mở bằng phép kiểm mà bất kỳ ai cũng chạy được trên chính công cụ mình đang dùng, rồi mới dựng lại cơ chế. Toàn bộ con số dưới đây đo trong ngày 04/09/2026 trên bộ dữ liệu và bộ công cụ mà FinLens đang dùng, và mọi phép đo đều nêu đủ tham số để lặp lại được.

Bài không đưa ra tín hiệu mua bán, không dự báo giá, và không có dòng nào đọc được thành ưu điểm của một chiến lược.

Hai tập hợp mã cổ phiếu đặt chồng lên nhau, tập nhỏ bên trong gồm các mã còn giao dịch tới hôm nay được tô đậm, phần vành ngoài gồm các mã đã dừng giao dịch thì để mờ và gạch chéo

Tập mã mà một backtest chạy trên đó, và tập mã thật sự tồn tại trong quá khứ. Phần chênh lệch là phần không ai nhìn thấy. Nguồn: FinLens tổng hợp.

Hai sai lệch, một điểm chung

Sai lệch sống sót là sai lệch phát sinh khi tập quan sát được lọc theo một tiêu chí chỉ biết được ở tương lai. Trong backtest, tiêu chí ấy là "mã còn tồn tại tới hôm nay". Người ngồi ở tháng 09/2016 không biết mã nào sẽ còn giao dịch vào tháng 09/2026, nên một phép mô phỏng chỉ chạy trên các mã sống sót đang cho bộ quy tắc một lợi thế mà nó không được phép có.

Sai lệch nhìn trước là sai lệch phát sinh khi phép tính tại thời điểm t dùng một dữ kiện chỉ tồn tại sau thời điểm t. Nó không cần một dòng mã sai nào để xảy ra: chỉ cần một cột dữ liệu được gắn nhầm ngày.

Điểm chung của hai thứ này là điều khiến chúng khó chịu hơn mọi lỗi khác trong danh sách của bài trước. Chúng không sinh ra lỗi, chúng sinh ra một con số. Một backtest dính cả hai vẫn chạy hết, vẫn in đủ mọi chỉ tiêu, và bảng kết quả của nó trông giống hệt bảng kết quả của một phép chạy sạch. Không có cảnh báo, không có ô trống, không có dòng nào đỏ.

Và cả hai đều lệch theo một chiều biết trước. Không phải vì có quy luật nào bắt buộc, mà vì cơ chế sinh ra chúng bất đối xứng: mã bị loại khỏi tập là mã đã đi qua một kết cục xấu, còn dữ liệu bị rò từ tương lai về là dữ liệu mà người thật không có. Chiều nào có lợi cho kết quả thì chiều đó được giữ lại.

Câu chốt: hai sai lệch này không làm hỏng phép tính, chúng làm hỏng dữ liệu đầu vào rồi để phép tính chạy đúng trên dữ liệu sai.

Phép kiểm đầu tiên, và nó phải chạy trước mọi phép kiểm khác

Trước khi đọc bất kỳ con số nào từ một công cụ backtest, có đúng một câu hỏi phải trả lời: tập mã của công cụ này có gồm những mã đã dừng giao dịch không?

Câu hỏi đó không trả lời được bằng tài liệu, vì tài liệu hầu như không bao giờ nói. Nó trả lời được bằng một phép thử mất chưa tới một phút: lấy mã của một doanh nghiệp mà bạn biết chắc đã dừng giao dịch, đưa thẳng vào công cụ, xem nó trả về gì.

Kết quả trên bộ công cụ mà bài này đo là một kết quả không ai đoán trước được: hai công cụ chạy trên cùng một bộ dữ liệu cho hai câu trả lời ngược nhau.

Bộ lọc tín hiệu: mã đã dừng giao dịch không tồn tại

Phép thử được đặt sao cho không còn cửa cho lời giải thích nào khác. Sáu mã được đưa vào bộ lọc theo một danh sách chỉ định sẵn, quy tắc quét là điều kiện đơn giản nhất có thể (giá đóng cửa nằm trên đường trung bình 20 phiên), cửa sổ tìm tín hiệu 10 phiên, và sàn thanh khoản đặt bằng 0, tức không có ngưỡng lọc nào cả.

Ba trong sáu mã đang giao dịch bình thường. Ba mã còn lại có chuỗi giá đã dừng.

Công cụ trả về universe_size: 3scanned: 3. Ba mã đã dừng giao dịch không hề bị quét rồi loại, chúng không có mặt trong tập quét ngay từ đầu. Không có thông báo nào nói rằng ba mã đã bị bỏ; con số 3 nằm lặng lẽ trong một trường mà phần lớn người dùng không đọc.

Phép thử được lặp lại với một danh sách khác, trong đó hai mã còn giao dịch nằm trên UPCoM để loại trừ khả năng công cụ đang lọc theo sàn. Kết quả y hệt: universe_size: 3, và hai mã UPCoM ấy đều lọt vào bảng kết quả bình thường.

Hệ quả trực tiếp, và nó rộng hơn vẻ ngoài

Nếu tập quét chỉ gồm mã còn giao dịch tới hôm nay, thì mọi thứ dựng bằng bộ lọc đó đều dính sai lệch sống sót 100%, không phải một phần. Không có tham số nào bật lên để sửa, vì vấn đề nằm ở tập dữ liệu chứ không nằm ở tham số.

Điều này áp cho mọi câu hỏi dạng "trong lịch sử, quy tắc này bắt được bao nhiêu mã", "tỷ lệ mã thoả điều kiện rồi tăng giá là bao nhiêu", hay bất kỳ thống kê cắt ngang thị trường nào chạy qua bộ lọc.

Còn một tầng lọc thứ hai cùng họ, và tầng này thì có ghi trong tài liệu của công cụ: bộ lọc sức mạnh giá tương đối loại các mã chưa đủ 252 phiên lịch sử. Đó là lọc ở đầu ngược lại, loại các mã mới lên sàn. Hai đầu cộng lại, tập quét là tập mã đã sống đủ lâu và vẫn còn sống.

Công cụ backtest: nhận, nhưng theo một cách tệ hơn

Cũng ba mã ấy, đưa vào công cụ backtest theo tên, thì công cụ chạy bình thường. Trường dropped_tickers trả về rỗng. Không có cảnh báo nào.

Thoạt nghe đây là tin tốt: ít nhất tập dữ liệu có chứa mã đã chết, nên về nguyên tắc dựng được một phép mô phỏng không lọc sẵn. Nhưng mở kết quả ra thì hoá ra vấn đề chỉ chuyển sang một hình dạng khó thấy hơn, và ba mục tiếp theo dành cho chuyện đó.

Câu chốt: trên cùng một bộ dữ liệu, bộ lọc nói mã đã dừng giao dịch không tồn tại còn công cụ backtest nói nó tồn tại, và bạn phải tự đi tìm câu trả lời này chứ không ai đưa cho bạn.

Đuôi của một chuỗi giá đã chết được viết tiếp bằng số bịa

Ở Việt Nam, một mã rời khỏi tập quan sát theo vài đường khác nhau, và mỗi đường để lại một hình dạng khác nhau ở đuôi chuỗi giá.

Cách rời khỏi tậpChuyện xảy ra với giao dịchChuyện xảy ra với chuỗi giá
Huỷ niêm yết bắt buộcCổ phiếu rời sàn niêm yết, thường được đăng ký giao dịch trên UPCoM sau đóChuỗi có thể tiếp tục ở nơi mới, với biên độ và lưới giá khác
Đình chỉ giao dịchKhông phiên nào phát sinh giao dịch cho tới khi được gỡChuỗi hoặc thủng, hoặc bị lấp bằng phiên phẳng
Huỷ niêm yết tự nguyện, sáp nhập, giải thểMã biến mất khỏi mọi nơi giao dịchChuỗi dừng hẳn
Đổi sàn theo lộ trình hợp nhấtVẫn giao dịch, nhưng cỗ máy sinh giá đổiChuỗi liền mạch về hình thức, gãy về bản chất

Ba đường đầu là nguồn của sai lệch sống sót. Đường thứ tư không loại mã nào khỏi tập nhưng làm hai đoạn của cùng một chuỗi không so được với nhau, và mục riêng ở dưới dành cho nó. Cơ chế của diện đình chỉ và các diện xử lý khác nằm ở diện cảnh báo, kiểm soát và hạn chế giao dịch; bài này chỉ đo hậu quả của chúng lên một phép mô phỏng.

Ba dạng đuôi chuỗi, đo trên ba mã

Ba mã dưới đây được chọn theo đúng một tiêu chí và không theo tiêu chí nào khác: chuỗi giá của chúng đã dừng phát sinh khối lượng. Tiêu chí ấy không nói gì về doanh nghiệp đứng sau, và đổi sang bất kỳ mã nào khác có chuỗi đã dừng thì bảng vẫn cho đúng hình dạng này. Bài không nêu nhận định nào về ba doanh nghiệp và không dùng chúng để so sánh với nhau.

Ngành ICBPhiên cuối có khối lượngPhiên cuối trong dữ liệuĐuôi chuỗi
ROS2357 Xây dựng11/08/202231/08/202214 phiên phẳng rồi dừng hẳn
FLC8633 Bất động sản08/09/202230/12/2025Hơn ba năm phiên phẳng rồi dừng
ITA8633 Bất động sản25/09/202404/09/2026Phiên phẳng kéo tới đúng ngày chốt số liệu

"Phiên phẳng" ở đây có nghĩa rất cụ thể: giá mở cửa, cao nhất, thấp nhất và đóng cửa bằng nhau tuyệt đối, khối lượng bằng 0, giá trị giao dịch bằng 0. Đây chính là loại cây nến mà bài xu hướng và cấu trúc giá gọi là cây nến do phần mềm lấp vào, thứ chưa từng tồn tại. Ở đó nó là một vấn đề của phép đếm đỉnh đáy. Ở đây nó là một vấn đề nặng hơn, vì backtest không chỉ đếm chúng mà còn giao dịch trên chúng.

Dạng thứ ba là dạng nguy hiểm nhất và cũng là dạng ít ai ngờ. Chuỗi của mã thứ ba chạy tới đúng ngày chốt số liệu, nên nhìn từ bên ngoài nó giống hệt chuỗi của một mã đang giao dịch bình thường. Không có ô trống, không có ngày cuối bất thường. Thứ duy nhất tố cáo là cột khối lượng, mà cột khối lượng thì gần như không ai nhìn khi chạy backtest.

Giá đổi mà không có ai giao dịch

Còn một chi tiết nữa, đo được trên cả hai mã có đuôi chuỗi dài, và nó nói lên bản chất của những con số ấy.

Chuỗi thứ hai đứng yên ở mức 3.57 nghìn đ suốt từ tháng 09/2022. Phiên 17/02/2023 vẫn là 3.57. Phiên kế tiếp trong dữ liệu là 03/03/2023, và mức giá đã thành 3.50. Khối lượng của mọi phiên trong quãng đó, kể cả hai phiên vừa nêu, đều bằng 0.

Chuỗi thứ ba cũng vậy: đứng ở 2.35 nghìn đ từ 26/09/2024, và tới ngày chốt số liệu 04/09/2026 thì đang ở 2.30 nghìn đ. Không phiên nào trong quãng ấy có khối lượng khác 0.

Một mức giá đã thay đổi trong khi không một cổ phiếu nào đổi chủ. Con số ấy đến từ đâu thì bài này không biết, và cũng không cần biết: điều đủ để kết luận là nó không phải kết quả của một giao dịch, nên nó không phải giá theo nghĩa mà mọi chỉ báo kỹ thuật giả định.

Câu chốt: một chuỗi giá không dừng lại khi cổ phiếu dừng giao dịch, và phần viết tiếp sau đó không mang thông tin về cung cầu nào cả.

Ba đường giá trừu tượng xếp chồng theo chiều dọc, cả ba đều biến động ở phần đầu rồi chuyển thành một đoạn thẳng nằm ngang có độ dài khác nhau, đường trên cùng dừng ngay sau đoạn thẳng ngắn, đường giữa kéo đoạn thẳng dài hơn rồi dừng, đường dưới cùng kéo đoạn thẳng tới tận mép phải của khung

Ba dạng đuôi chuỗi của một mã đã dừng giao dịch. Chỉ cột khối lượng phân biệt được đoạn thẳng nằm ngang với một giai đoạn giá đi ngang thật.

Lệnh khớp trên những phiên không có giao dịch nào

Đây là chỗ hậu quả trở nên cụ thể.

Chạy một quy tắc giao cắt hai đường trung bình (mua khi đường 20 phiên cắt lên đường 50 phiên, bán khi cắt xuống) trên mã có đuôi chuỗi dài hơn ba năm, cửa sổ 10 năm, mô phỏng chu kỳ T+2, phí 0.15% mỗi chiều, trượt giá 0.23% mỗi lệnh. Công cụ ghi nhận 18 lệnh.

Lệnh thứ 18, tức lệnh cuối cùng của chuỗi, có ngày vào 11/11/2022 và ngày thoát 16/11/2022. Cả hai đều nằm sau phiên cuối cùng có khối lượng, tức 08/09/2022. Giá vào 3.578 và giá thoát 3.562. Giá trị hai đường trung bình tại phiên vào là 3.5700 và 3.5662.

Cơ chế thì hiển nhiên khi đã nhìn thấy con số. Trên một chuỗi hằng số, mọi đường trung bình động đều hội tụ về đúng giá trị hằng số ấy. Khi hai đường tiến sát nhau tới mức chỉ còn khác nhau ở phần đuôi của phép làm trơn, một điểm giao cắt là chuyện phải xảy ra chứ không phải chuyện có thể xảy ra. Bản chất của phép giao cắt, và lý do nó không chứa thông tin mới về mức giá, nằm ở đường trung bình động.

Điều đáng nói không phải là một lệnh giả trong 18 lệnh làm lệch bao nhiêu; nó không làm lệch bao nhiêu cả, vì lãi lỗ của nó là −0.76%. Điều đáng nói là phép tính không có cơ chế nào để từ chối nó. Chuỗi hằng số dài hơn hoặc quy tắc nhạy hơn thì số lệnh giả tăng lên, và không có gì chặn lại.

Câu chốt: backtest không hỏi một phiên có giao dịch hay không, nó chỉ hỏi phiên đó có một con số hay không.

Cùng một quy tắc, sáu mã, và hai dòng có mốc so sánh bằng 0

Bảng dưới đây là phép chạy đầy đủ, sáu mã trong cùng một lần gọi, cùng cửa sổ 10 năm yêu cầu, cùng quy tắc và cùng tham số vừa nêu ở mục trên.

Ba mã còn giao dịch lấy từ nhóm mã thanh khoản cao. Ba mã đã dừng giao dịch là ba mã đã nêu ở mục trước. Bảng xếp theo thứ tự bảng chữ cái, không xếp theo kết quả, và cột vượt mua giữ có ở mọi dòng. Không mã nào trong bảng thuộc ngành môi giới chứng khoán.

Ngành ICBTrạng thái chuỗiSố lệnhLợi nhuận tổngMua và giữVượt mua giữ
FLC8633 Bất động sảnDừng 08/09/202218−37.24%0−37.24 đpt
FPT9537 Phần mềmĐang giao dịch21+445.38%+665.27%−219.89 đpt
HPG1757 ThépĐang giao dịch26+274.70%+385.87%−111.17 đpt
ITA8633 Bất động sảnDừng 25/09/202424−12.41%−53.06%+40.65 đpt
ROS2357 Xây dựngDừng 11/08/202210+362.31%0+362.31 đpt
VNM3577 Thực phẩmĐang giao dịch24+15.73%−7.35%+23.08 đpt
Vượt mua và giữ, cùng một quy tắc giao cắt trên sáu mã
-300-200-1000100200300400đptFLC: -37.24 đptFPT: -219.89 đptHPG: -111.17 đptITA: 40.65 đptROS: 362.31 đptVNM: 23.08 đptFLCFPTHPGITAROSVNM
Vượt mua và giữ, cùng một quy tắc giao cắt trên sáu mã. Đơn vị đpt.
MốcVượt mua và giữ (đpt)
FLC-37.24
FPT-219.89
HPG-111.17
ITA40.65
ROS362.31
VNM23.08

Chốt số liệu 04/09/2026. Cửa sổ yêu cầu 10 năm, T+2, phí 0.15% mỗi chiều, trượt giá 0.23% mỗi lệnh. Hai cột dương cao nhất thuộc về hai mã đã dừng giao dịch, và mốc so sánh của một trong hai được công cụ in bằng 0. Nguồn: HOSE, HNX, FinLens tổng hợp.

Hai điều phải nói ngay, và cả hai đều là điều kiện để bảng này được đọc đúng.

Điều thứ nhất: hai ô in đậm là hai ô vô nghĩa. Công cụ báo lợi nhuận của phép mua và giữ bằng đúng 0 cho hai mã có chuỗi kết thúc trước cuối cửa sổ. Con số 0 ấy không phải kết quả của một phép tính về thị trường; nó là thứ rơi ra khi mẫu số so sánh không có dữ liệu để tính. Và vì cột vượt mua giữ là hiệu giữa hai cột trước nó, một mốc so sánh bằng 0 biến toàn bộ lợi nhuận của chiến lược thành phần vượt trội. Dòng có phần vượt lớn nhất bảng, +362.31 đpt, là dòng có mốc so sánh không tồn tại.

Điều thứ hai: nhóm ba mã đã dừng giao dịch không phải một quy tắc chọn mã. Đây là chỗ dễ đọc sai nhất. Hai trong ba dòng dương nhất thuộc về mã đã chết, nhưng điều đó tuyệt đối không đọc được thành "chiến lược này hợp với mã sắp chết". Không ai biết trước mã nào sẽ chết, nên tiêu chí phân nhóm ở đây chỉ tồn tại sau khi mọi chuyện đã xong. Đúng vì vậy mà nó là bằng chứng của sai lệch sống sót chứ không phải một phát hiện về chiến lược: bảng đang cho thấy tập mã tự bẻ cong kết quả, không cho thấy quy tắc nào tốt.

Điều thứ ba, và đây là chỗ bảng tự thừa nhận yếu: tham số trượt giá 0.23% một vòng dùng chung cho cả sáu mã, trong khi sàn trượt giá vật lý suy từ lưới bước giá khác nhau theo từng mã và từng vùng giá. Bảng sàn theo từng mã nằm ở chi phí giao dịch ăn mòn lợi nhuận thế nào. Ba mã đã dừng giao dịch đều ở vùng thị giá vài nghìn đồng vào những phiên cuối của chúng: phiên cuối có khối lượng của một trong ba mã đóng ở 2,350 đ, và với lưới 100 đ đồng nhất thì một bước giá bằng 4.26% thị giá, tức sàn một vòng 4.26%, cao hơn tham số đang dùng hơn 18 lần. Quy ước đơn vị của cả chuỗi: sàn một vòng bằng một bước giá chia thị giá, sàn mỗi chiều bằng một nửa số đó. Nghĩa là ba dòng ấy còn được ưu ái thêm một lần nữa, đúng theo chiều làm chúng đẹp lên.

Và cần nói rõ giới hạn của bằng chứng này. Sáu dòng không đủ để kết luận sai lệch sống sót đẩy kết quả lên bao nhiêu, ở thị trường nào, với chiến lược nào. Nó đủ để kết luận rằng cột vượt mua giữ của những mã có chuỗi đã dừng là con số không đọc được.

Câu chốt: một bảng backtest cắt ngang nhiều mã cần thêm một cột mà không công cụ nào in ra, đó là cột nói chuỗi của mã đó có chạy hết cửa sổ hay không.

Cùng một mã, hai bộ số, khác nhau chỉ vì ai đứng cạnh nó

Phép chạy trên còn để lộ một chuyện nữa, và nó thuộc loại chuyện làm người ta mất lòng tin vào toàn bộ bảng.

Ba mã đã dừng giao dịch được chạy thêm một lần nữa, mỗi mã một mình, với đúng cùng quy tắc, đúng cùng tham số, đúng cùng yêu cầu 10 năm. Chỉ khác một điều: không có mã nào khác trong cùng lần gọi.

Chạy một mìnhTrong rổ sáu mã
ROSCửa sổ công cụ dùng06/09/2016 tới 31/08/202206/09/2016 tới 04/09/2026
Lợi nhuận tổng+362.31%+362.31%
Tăng trưởng kép năm29.38%16.72%
Mua và giữ−76.83%0
Vượt mua giữ+439.14 đpt+362.31 đpt
FLCCửa sổ công cụ dùng06/09/2016 tới 30/12/202506/09/2016 tới 04/09/2026
Lợi nhuận tổng−37.24%−37.24%
Tăng trưởng kép năm−4.93%−4.60%
Mua và giữ−23.17%0
Vượt mua giữ−14.07 đpt−37.24 đpt
ITACửa sổ công cụ dùng06/09/2016 tới 04/09/202606/09/2016 tới 04/09/2026
Lợi nhuận tổng−12.41%−12.41%
Tăng trưởng kép năm−1.33%−1.33%
Mua và giữ−53.06%−53.06%
Vượt mua giữ+40.65 đpt+40.65 đpt

Mã thứ ba là mã đối chứng, và nó chứng minh cơ chế. Chuỗi của nó kéo tới tận ngày chốt số liệu, tức phủ hết cửa sổ, nên không một con số nào của nó thay đổi. Hai mã kia có chuỗi kết thúc trước cuối cửa sổ, và đúng ba chỉ tiêu đổi: cửa sổ, tăng trưởng kép năm, và mốc so sánh mua giữ.

Hai hệ quả, và cả hai đều đáng nhớ hơn con số cụ thể.

Cửa sổ mô phỏng bị rút ngắn mà không có cảnh báo nào. Yêu cầu 10 năm, nhận về 6 năm, và điều đó chỉ hiện ra ở hai trường ngày tháng trong phần đầu kết quả. Ai đọc bảng chỉ tiêu mà bỏ qua hai trường ấy đang so một chuỗi 6 năm với một chuỗi 10 năm và tưởng chúng cùng kỳ. Chuyện tương tự áp cho mọi phép so sánh giữa các mã trong một bảng nhiều dòng.

Tăng trưởng kép năm đổi 12.66 điểm phần trăm mà không có một lệnh nào đổi. Con số 29.38% và 16.72% đến từ đúng cùng một chuỗi 10 lệnh với đúng cùng lợi nhuận tổng +362.31%. Khác biệt duy nhất là mẫu số: 6 năm hay 10 năm. Đây là ví dụ sạch nhất mà bài này có được về chuyện một chỉ tiêu có thể đổi gần gấp đôi chỉ vì một giả định về thời gian mà người dùng không biết là mình đã đặt.

Câu chốt: nếu một chỉ tiêu của mã A đổi khi bạn thêm mã B vào cùng lần chạy, thì chỉ tiêu ấy đang chứa một giả định mà bạn chưa khai.

Ba dạng sai lệch nhìn trước

Sang nửa thứ hai của bài. Sai lệch nhìn trước không liên quan tới việc mã nào có mặt trong tập; nó liên quan tới việc thông tin nào có mặt tại thời điểm nào.

Dạng một: quyết định bằng giá đóng cửa rồi khớp ngay tại chính giá đó

Bài trước đã dựng bảng ba quy ước khớp lệnh và nêu vì sao quy ước thứ nhất là quy ước sai một cách kín đáo nhất. Ở đây chỉ cần thêm một việc: đo xem công cụ đang dùng quy ước nào.

Phép đo thì đơn giản tới mức không cần giải thích. Chạy backtest với phí bằng 0 và trượt giá bằng 0, rồi so giá vào lệnh với giá đóng cửa của chính ngày vào lệnh.

Kết quả trên VNM, một mã HOSE ngành 3577 thực phẩm, quy tắc giao cắt hai đường trung bình, cửa sổ 2 năm: lệnh đầu tiên có ngày vào 12/03/2025 và giá vào 55.561 nghìn đ. Giá đóng cửa của phiên 12/03/2025 trong chính bộ dữ liệu ấy là 55.56067644406253 nghìn đ. Hai con số là một. Mã ở đây chỉ đóng vai một chuỗi giá đủ dài để chạy phép thử; đổi sang bất kỳ mã HOSE nào khác thì phép thử vẫn cho cùng kết luận về quy ước khớp lệnh.

Vậy công cụ dùng quy ước thứ nhất: tín hiệu tính từ giá đóng cửa, và lệnh khớp tại đúng giá đóng cửa đó. Đường trung bình 20 phiên tại phiên ấy được tính có bao gồm giá đóng cửa của chính phiên ấy, tức phép so sánh sinh ra tín hiệu chỉ hoàn tất sau khi phiên kết thúc, rồi lệnh được coi là đã khớp ở một mức giá mà tại thời điểm quyết định chưa ai biết.

Ở Việt Nam quy ước này còn thêm một tầng khó chịu. Giá đóng cửa trên HOSE và HNX không phải một giao dịch khớp lệnh liên tục mà là kết quả của phiên khớp lệnh định kỳ đóng cửa, tức một mức giá do chính phiên đó xác định ra ở cuối phiên. Bản chất khác nhau của giá đóng cửa ở ba nơi giao dịch nằm ở đọc một cây nến và những gì nó giấu đi. Một backtest khớp tại giá đóng cửa vì thế đang giả định người tham gia đặt được lệnh vào phiên định kỳ và khớp trọn vẹn tại mức giá mà phiên ấy tìm ra sau.

Cần nói cho công bằng: quy ước này không phải một lỗi lập trình mà là một lựa chọn thiết kế, và nó tiện. Vấn đề chỉ nằm ở chỗ nó không được khai, nên người đọc kết quả không biết mình đang xem một phép mô phỏng có rò rỉ.

Dạng hai: số liệu báo cáo tài chính gắn vào ngày kết thúc kỳ

Đây là dạng gây thiệt hại lớn nhất trong các bộ lọc có điều kiện cơ bản, và nó đến từ một chỗ rất tầm thường: bảng dữ liệu chỉ có nhãn kỳ, không có ngày công bố.

Kiểm được trực tiếp. Danh sách các kỳ báo cáo kết quả kinh doanh theo quý của một mã trả về các bản ghi dạng {"year": 2026, "quarter": 2, "period": "Q2 2026"}, kéo dài về tới quý 1 năm 2007. Không bản ghi nào có trường ngày công bố, ngày ký báo cáo, hay bất kỳ mốc thời gian nào khác ngoài nhãn kỳ.

Hệ quả: bất kỳ ai ghép số liệu ấy vào một chuỗi giá đều phải tự quyết định gắn nó vào ngày nào, và lựa chọn tự nhiên nhất, tức ngày kết thúc kỳ, chính là lựa chọn sai.

Khoảng cách giữa hai ngày không nhỏ và không phải chuyện ước lượng, vì nó do quy định ấn định. Bài lịch công bố thông tin của doanh nghiệp niêm yết có bảng đầy đủ; ba dòng đủ để thấy quy mô vấn đề:

Loại báo cáoHạn công bố tính từ ngày kết thúc kỳMốc dài hơn cho công ty mẹ
Báo cáo tài chính quý20 ngày30 ngày
Báo cáo bán niên đã soát xétTối đa 45 ngày60 ngày
Báo cáo năm đã kiểm toánTối đa 90 ngàyKhông có mốc dài hơn

Một bộ lọc gắn số liệu quý 1 vào ngày 31/03 đang cho phép quy tắc của mình biết trước từ 20 tới 30 ngày. Với số liệu năm, khoảng nhìn trước lên tới 90 ngày. Và đây là hạn tối đa, không phải ngày công bố thật; doanh nghiệp công bố sớm thì khoảng rò rỉ ngắn hơn, doanh nghiệp xin gia hạn thì dài hơn. Nghĩa là khoảng rò rỉ khác nhau theo từng doanh nghiệp và từng kỳ, nên không có cách nào sửa bằng một độ trễ cố định áp cho tất cả.

Đây cũng là chỗ hai sai lệch của bài này gặp nhau. Doanh nghiệp chậm công bố là doanh nghiệp dễ bị đưa vào các diện xử lý, và diện xử lý kéo dài thì dẫn tới đình chỉ rồi huỷ niêm yết bắt buộc. Tức nhóm doanh nghiệp có khoảng rò rỉ dài nhất trùng đáng kể với nhóm sẽ biến mất khỏi tập, mà tập thì đã lọc mất họ rồi.

Dạng ba: danh mục chỉ số hôm nay áp cho quá khứ

Dạng thứ ba xảy ra khi một phép mô phỏng lấy danh sách thành phần hiện tại của một rổ chỉ số rồi chạy ngược về quá khứ.

Rổ chỉ số không cố định. Thành phần được xem xét lại theo kỳ, có mã vào và có mã ra, và tiêu chí xét bao gồm những thứ chỉ biết được tại kỳ xét. Cách một rổ được lập và các điều kiện thành phần nằm ở chỉ số VNINDEX và VN30 được tính thế nào.

Dùng danh sách hôm nay cho năm 2016 là mắc cả hai sai lệch cùng lúc: nhìn trước, vì danh sách ấy chưa tồn tại năm 2016; và sống sót, vì những mã từng ở trong rổ rồi rơi ra đã bị loại khỏi tập. Đây là dạng khó tránh nhất trong ba dạng, bởi vì dữ liệu thành phần rổ theo từng kỳ trong quá khứ thường không có sẵn, và khi không có thì phép mô phỏng ấy đơn giản là không chạy được cho đúng.

Câu chốt: sai lệch nhìn trước hầu như không bao giờ đến từ một dòng mã sai, nó đến từ một cột dữ liệu bị gắn nhầm ngày.

Ba dải thời gian nằm ngang xếp chồng, mỗi dải có một điểm đánh dấu thời điểm ra quyết định và một điểm đánh dấu thời điểm thông tin thật sự tồn tại, với một mũi tên cong đi ngược từ điểm sau về điểm trước để thể hiện thông tin bị rò từ tương lai về quá khứ

Ba dạng sai lệch nhìn trước. Trong cả ba, mũi tên đi ngược chiều thời gian, và đó là toàn bộ vấn đề.

Chuỗi đã điều chỉnh: giá quá khứ không phải giá người ta đã giao dịch

Bài xu hướng và cấu trúc giá đã dựng khái niệm khoảng trống giá giả: vào ngày giao dịch không hưởng quyền, giá tham chiếu bị điều chỉnh theo giá trị quyền, và trên chuỗi chưa điều chỉnh thì bước nhảy hành chính ấy hiện ra giống hệt một khoảng trống do thị trường tạo ra. Bản chất của ngày đó nằm ở ngày giao dịch không hưởng quyền.

Cách chữa phổ biến là dùng chuỗi đã điều chỉnh hồi tố: nhân toàn bộ giá của các phiên trước ngày không hưởng quyền với một hệ số nhỏ hơn 1 để chuỗi liền mạch. Cách chữa ấy giải quyết được vấn đề tính lãi lỗ, và tạo ra một vấn đề khác mà phần lớn người dùng không biết mình đang mang.

Phép đo, và cách lặp lại nó

Bộ dữ liệu mà bài này dùng trả về chuỗi đã điều chỉnh, và điều đó nhìn thấy được ngay từ số chữ số thập phân. Giá đóng cửa phiên 12/03/2025 của VNM, vẫn là mã dùng ở mục trên, được trả về là 55.56067644406253 nghìn đ.

Hệ số điều chỉnh suy ra được từ chính chuỗi, không cần dữ liệu ngoài. Lấy hiệu giữa hai giá trị liền kề khác nhau trong cùng một đoạn:

55.828653983181795 − 55.73932813680871 = 0.089325846373086
55.73932813680871  − 55.650002290435616 = 0.089325846373094

Khoảng cách nhỏ nhất giữa hai mức giá trong đoạn ấy là 0.089325846 nghìn đ, tức 89.33 đ. Mã này giao dịch trên HOSE ở vùng giá từ 50,000 đ trở lên, nơi bước giá là 100 đ, chuyện đã dựng ở hỗ trợ và kháng cự bản chất là gì. Vậy hệ số điều chỉnh của đoạn ấy là 89.33 chia 100, tức 0.8932585. Phép suy này chỉ dùng hai thứ: khoảng cách giữa hai mức giá liền kề trong chính chuỗi, và bước giá theo quy chế của vùng giá tương ứng. Không cần dữ liệu nào bên ngoài, nên ai cũng lặp lại được trên mã của mình.

Chia ngược lại thì ra giá thật, và mọi giá thật suy ra đều là bội số tròn của 100 đ, tức phép suy khớp:

PhiênGiá chuỗi trả vềHệ số của đoạnGiá thật trên bảngChênh
12/03/202555,560.68 đ0.893258562,200 đ−10.67%
05/02/202666,907.29 đ0.968267669,100 đ−3.17%
04/09/202661,900.00 đ1.000000061,900 đ0.00%

Ba hệ quả, và hệ quả thứ ba là hệ quả nặng nhất

Một: mọi ngưỡng tuyệt đối đặt trên chuỗi đó là ngưỡng ảo. Một quy tắc dạng "mua khi giá vượt 60,000 đ" chạy trên chuỗi này sẽ không kích hoạt ở phiên 12/03/2025, vì chuỗi nói giá là 55,560.68 đ. Trong thực tế phiên đó giá là 62,200 đ và quy tắc đã kích hoạt. Cùng một quy tắc, cùng một phiên, hai kết quả ngược nhau.

Hai: giá trên chuỗi còn không đặt lệnh được. 55,560.68 đ không phải bội số của 100 đ, cũng không phải bội số của 50 đ hay 10 đ. Nó không nằm trên lưới bước giá nên hệ thống không nhận một lệnh ở mức ấy. Một phép mô phỏng khớp lệnh tại con số đó đang khớp ở một mức giá mà sổ lệnh không có chỗ để chứa.

Ba: hệ số không cố định theo thời gian, và nó bị viết lại mỗi lần có sự kiện quyền mới. Bảng trên cho thấy ba đoạn có ba hệ số khác nhau, giảm dần về 1.0 khi tới hiện tại. Nghĩa là cùng một phiên quá khứ sẽ có giá trị khác nhau tuỳ vào ngày bạn tải dữ liệu. Một ngưỡng hiệu chỉnh trên chuỗi tải hôm nay không còn tương ứng với cùng một mức giá thật khi tải lại sau kỳ chia cổ tức tiếp theo. Ở Việt Nam, nơi cổ tức bằng cổ phiếu và phát hành thêm cho cổ đông hiện hữu là chuyện phổ biến (cơ chế ở chia tách và phát hành thêm ảnh hưởng giá thế nào), việc viết lại này xảy ra thường xuyên hơn hẳn.

Và đây là chỗ không có lối thoát sạch: chuỗi thô cho lãi lỗ sai qua ngày không hưởng quyền, chuỗi đã điều chỉnh cho ngưỡng tuyệt đối sai ở mọi nơi. Không chuỗi nào đúng cả hai. Cách duy nhất giữ được cả hai là chỉ dùng đại lượng bất biến qua phép nhân, tức tỷ lệ và phần trăm, và tuyệt đối không đặt ngưỡng bằng một mức giá tuyệt đối.

Câu chốt: trên chuỗi đã điều chỉnh hồi tố, con số ở cột giá là một đại lượng kế toán chứ không phải một mức giá đã từng có người đặt lệnh.

Hai trục giá dọc đặt cạnh nhau, trục bên phải có các vạch chia đều nhau đúng bằng một bước giá và một mức giá được đánh dấu nằm đúng trên vạch, trục bên trái là cùng các mức giá đó sau khi nhân với một hệ số nhỏ hơn một nên không mức nào rơi trúng vạch chia nữa

Chuỗi đã điều chỉnh hồi tố dịch mọi mức giá quá khứ xuống, và sau phép dịch đó không mức nào còn nằm trên lưới bước giá.

Chuỗi gãy vì luật, và vì sao nó là họ hàng của hai sai lệch trên

Bài nền đã liệt kê bốn cách chuỗi giá Việt Nam bị gãy vì quy định chứ không vì thị trường: nến bị rút ngắn thời gian sinh ra nó, nến thoái hoá khi chỉ còn khớp lệnh định kỳ, chuỗi có lỗ khi bị gom về một phiên trong tuần hoặc bị đình chỉ, và đổi sàn. Mục này không dựng lại bốn cách đó mà đo hậu quả riêng của chúng lên một phép mô phỏng dài hạn.

Hậu quả thứ nhất: hai đoạn của cùng một chuỗi thuộc hai chế độ, và phép mô phỏng nối chúng lại như một. Khi một mã chuyển từ HNX sang HOSE, biên độ đổi từ 10% xuống 7%, lưới bước giá đổi từ 100 đ phẳng sang ba bậc, phiên định kỳ mở cửa xuất hiện và phiên PLO biến mất. Chuyện này không phải giả định: HNX đã ngừng nhận hồ sơ niêm yết cổ phiếu mới từ 01/07/2025 và toàn bộ cổ phiếu niêm yết trên HNX phải chuyển sang HOSE chậm nhất 31/12/2026. Khác biệt giữa ba nơi giao dịch nằm ở HOSE, HNX và UPCoM khác nhau ở đâu.

Với backtest, hậu quả cụ thể là cái sàn trượt giá đổi giữa chừng cửa sổ mô phỏng, mà tham số trượt giá thì chỉ có một giá trị cho cả kỳ. Bảng sàn trượt giá theo từng mã và cách nó đổi giữa hai lưới nằm ở chi phí giao dịch ăn mòn lợi nhuận thế nào.

Hậu quả thứ hai: một chuỗi có lỗ làm cửa sổ n phiên không còn là n phiên thật. Đường trung bình 50 phiên tính trên một đoạn có 20 phiên do phần mềm lấp vào thực chất chỉ dùng 30 quan sát thật, và 20 quan sát còn lại là cùng một con số lặp lại. Không có cảnh báo nào cho chuyện đó, vì về mặt kiểu dữ liệu thì chúng là những con số hợp lệ.

Hậu quả thứ ba, và là chỗ nối với sai lệch sống sót: những mã bị gãy chuỗi nặng nhất chính là những mã có xác suất rời khỏi tập cao nhất. Diện hạn chế và diện đình chỉ là các bước trên cùng một thang leo dần dẫn tới huỷ niêm yết bắt buộc. Nên nhóm mã có dữ liệu bẩn nhất và nhóm mã bị loại khỏi tập là hai nhóm chồng lên nhau rất nhiều. Người làm sạch dữ liệu bằng cách bỏ các mã có chuỗi bẩn đang vô tình thực hiện đúng phép lọc sống sót mà mình định tránh.

Câu chốt: làm sạch dữ liệu bằng cách loại các chuỗi gãy là một cách tạo ra sai lệch sống sót bằng chính tay mình.

Kỳ nghỉ Tết: cửa sổ n phiên không phải n khoảng thời gian bằng nhau

Biến dạng thứ năm nhỏ nhất về hình thức nhưng đụng thẳng vào cách chia dữ liệu.

Mọi cửa sổ tham số của mọi chỉ báo đều đếm phiên, không đếm thời gian. Chuyện đó bình thường cho tới khi thị trường đóng cửa nhiều ngày liên tiếp.

Đo trên chuỗi giá thật: hai phiên liền nhau trong tháng 02/2026 là 13/02/202623/02/2026, cách nhau 10 ngày lịch, trong khi khoảng cách thông thường là 1 ngày hoặc 3 ngày nếu vắt qua cuối tuần.

Ba hệ quả riêng cho hai sai lệch của bài này.

Một: mốc chia trong mẫu và ngoài mẫu bị lệch. Chia dữ liệu chuỗi thời gian theo tỷ lệ số phiên và chia theo mốc thời gian cho hai kết quả khác nhau, và chênh lệch lớn nhất rơi đúng vào các năm mà kỳ nghỉ dài rơi gần mốc chia. Cách chia đúng cho chuỗi thời gian là nội dung của vì sao tối ưu tham số hay dẫn tới overfit.

Hai: đoạn khởi động của chỉ báo trải trên một khoảng thời gian không cố định. Một đường trung bình 50 phiên cần 50 phiên để có giá trị đầu tiên, và 50 phiên ấy có thể là hơn 10 tuần lịch nếu chúng vắt qua Tết. Với một cửa sổ mô phỏng ngắn thì phần đầu bị mất nhiều hơn dự tính.

Ba: số ngày nắm giữ theo lịch và số phiên nắm giữ tách nhau ra, và công cụ thường chỉ in một trong hai.

Nguyên tắc gốc nằm ở bài khung thời gian và tính nhất quán. Hệ quả riêng cho backtest là chỗ này: một cửa sổ kiểm chứng đếm bằng phiên có thể trải trên hai khoảng thời gian lịch rất khác nhau tuỳ nó rơi vào đâu trong năm, nên hai kỳ "cùng 250 phiên" không phải hai kỳ cùng độ dài.

Hai backtest trên hai tập mã: làm được tới đâu, và chỗ không làm được

Bằng chứng đắt nhất cho sai lệch sống sót là chạy đúng một quy tắc trên hai tập mã, một tập chỉ gồm mã còn sống và một tập gồm cả mã đã chết, rồi đo chênh lệch. Bài này không làm được phép đo đó, và cần nói rõ vì sao chứ không lấp liếm.

Ba trở ngại, và cả ba đều mang tính chặn.

Trở ngại thứ nhất: không dựng được tập rộng bằng chính công cụ. Bộ lọc loại mã đã dừng giao dịch khỏi tập quét ngay cả khi hạ sàn thanh khoản về 0. Nghĩa là không có đường nào để hỏi công cụ "cho tôi tập mã đã tồn tại vào tháng 09/2016". Danh sách ấy phải lấy từ các quyết định huỷ niêm yết và đình chỉ giao dịch mà Sở giao dịch công bố theo từng lần, rồi tra ngược từng mã một.

Trở ngại thứ hai: kể cả có danh sách thì con số chạy ra cũng không dùng được. Ba mã đo được ở trên cho thấy chuỗi của mã đã chết bị viết tiếp bằng phiên phẳng, mốc so sánh bị in bằng 0, cửa sổ bị rút ngắn không cảnh báo, và tham số trượt giá nằm dưới sàn vật lý của chính chúng nhiều lần. Cộng vào một phép so sánh cắt ngang thì phần nhiễu do công cụ tạo ra lớn hơn phần tín hiệu cần đo.

Trở ngại thứ ba: chưa xác minh được tập dữ liệu có chứa những mã đã biến mất hoàn toàn hay không. Ba mã đo được đều còn hồ sơ trên hệ thống, chỉ là chuỗi giá đã dừng. Một doanh nghiệp sáp nhập hoặc giải thể thì mã không còn ở đâu cả, và bài này không kiểm được bộ dữ liệu có giữ lịch sử của những mã như vậy không.

Vậy nên điều bài này chứng minh được là sự tồn tại của cơ chế, kèm ba mã đo trực tiếp và một bảng sáu dòng cho thấy hình dạng của nó. Điều bài này không chứng minh được là độ lớn: sai lệch sống sót đẩy một kết quả lên bao nhiêu điểm phần trăm ở thị trường Việt Nam là câu hỏi chưa có câu trả lời trong bài, và bất kỳ con số nào được nêu ra mà không kèm danh sách mã đã rời thị trường thì đều là con số bịa.

Điều này áp cho chính các con số của chuỗi bài này

Mọi bảng backtest trong chuỗi "Kiểm chứng một phương pháp" đều chạy trên những mã còn giao dịch tới ngày chốt số liệu. Đó là một tập đã được lọc sống sót, và không có ngoại lệ nào.

Với bảng chạy trên một mã, hệ quả nằm ở khâu chọn mã: mã ấy được chọn vì có đủ dữ liệu cho cả cửa sổ, mà đó chính là điều kiện sống sót. Với bảng chạy cắt ngang nhiều mã, hệ quả nặng hơn: tập mã ấy không đại diện cho tập mã đã tồn tại vào đầu kỳ.

Không bảng nào trong chuỗi được đọc như một ước lượng về hiệu quả của một chiến lược, và lý do vừa nêu là một trong những lý do chính. Chúng chỉ được đọc đúng theo cái mà chúng chứng minh: một cách hỏng cụ thể, đo được, lặp lại được.

Câu chốt: nói rõ mình không đo được cái gì là phần bắt buộc của một bài về sai lệch, vì im lặng ở đúng chỗ đó là cách sai lệch sinh ra.

Công cụ này mất tác dụng khi nào

Ở đây "công cụ" là bất kỳ kết luận nào rút ra từ một phép mô phỏng chạy trên dữ liệu quá khứ. Dưới đây là những đầu vào mà kết luận ấy không mang thông tin, kể cả khi bảng kết quả trông hoàn chỉnh.

Tình huốngVì sao kết luận không đọc được
Tập mã lấy từ một bộ lọc chạy trên dữ liệu hôm nayTập ấy chỉ gồm mã còn sống, và điều kiện đó chỉ biết được ở tương lai
Chuỗi của một mã kết thúc trước cuối cửa sổ mô phỏngCửa sổ bị rút ngắn âm thầm, và mốc so sánh có thể bị in bằng 0
Đoạn cuối chuỗi có các phiên bốn giá bằng nhau, khối lượng 0Đó là phiên do phần mềm lấp, chỉ báo vẫn tính và lệnh vẫn khớp trên chúng
Chuỗi kéo dài tới ngày hiện tại nhưng cột khối lượng bằng 0Nhìn từ bên ngoài giống hệt một mã đang giao dịch, chỉ cột khối lượng tố cáo
Chỉ tiêu của một mã đổi khi thêm mã khác vào cùng lần chạyChỉ tiêu ấy chứa một giả định về cửa sổ mà người dùng chưa khai
Quy tắc dùng giá đóng cửa rồi khớp ngay tại chính giá đóDùng một con số chỉ tồn tại sau khi phiên đã kết thúc
Điều kiện cơ bản gắn vào ngày kết thúc kỳ báo cáoNhìn trước từ 20 tới 90 ngày tuỳ loại báo cáo, và khoảng ấy khác nhau theo từng doanh nghiệp
Danh mục chỉ số lấy theo thành phần hiện tạiMắc cả nhìn trước lẫn sống sót cùng lúc
Ngưỡng đặt bằng một mức giá tuyệt đối trên chuỗi đã điều chỉnhGiá quá khứ đã bị nhân hệ số, và mức đó còn không nằm trên lưới bước giá
Chuỗi thô, lệnh vắt qua ngày giao dịch không hưởng quyềnLãi lỗ tính trên hai gốc toạ độ khác nhau
Dữ liệu tải lại ở hai thời điểm khác nhau cho hai kết quảHệ số điều chỉnh bị viết lại sau mỗi sự kiện quyền mới
Cửa sổ chứa mốc đổi sàn của mãBiên độ và lưới bước giá đổi giữa chừng, sàn trượt giá đổi theo
Cửa sổ chứa quãng mã ở diện hạn chế hoặc đình chỉCửa sổ n phiên không còn là n quan sát thật
Mốc chia trong mẫu và ngoài mẫu rơi gần một kỳ nghỉ dàiChia theo số phiên và chia theo thời gian cho hai mốc khác nhau
Dữ liệu đã được làm sạch bằng cách loại các chuỗi gãyPhép làm sạch ấy trùng với phép lọc sống sót

Mười lăm dòng trên có một điểm chung đáng nhấn: không dòng nào làm phép tính báo lỗi. Tất cả đều để lại một bảng kết quả đầy đủ, và cách duy nhất phát hiện là chủ động đi tìm.

Câu chốt: hai sai lệch này không được phát hiện, chúng chỉ được đi tìm.

Những chỗ bài này cố ý không khẳng định

Không khẳng định sai lệch sống sót làm kết quả đẹp lên bao nhiêu. Bài chỉ có sáu dòng đo được, trong đó hai dòng có mốc so sánh do công cụ in ra bằng 0. Sáu dòng chứng minh cơ chế tồn tại, không chứng minh độ lớn của nó, và không cho phép suy ra một con số hiệu chỉnh cho các bảng khác.

Không khẳng định chiều của sai lệch là luôn hướng lên. Cơ chế thì bất đối xứng, nhưng trong sáu dòng đo được, một mã đã dừng giao dịch cho phần vượt âm còn một mã cho phần vượt dương lớn nhất bảng. Kết luận đúng là các con số ấy không đọc được, không phải là chúng đẹp lên.

Không khẳng định bộ dữ liệu này thiếu hay đủ mã đã rời thị trường. Điều kiểm được là ba mã đã dừng giao dịch vẫn còn trong tập dữ liệu của công cụ backtest và không còn trong tập quét của bộ lọc. Điều chưa kiểm được là những doanh nghiệp đã biến mất hoàn toàn có được giữ lại hay không, và phép kiểm ấy đòi một danh sách mà bài chưa dựng.

Không nêu ngày công bố thật của bất kỳ báo cáo tài chính nào. Bảng ở trên là các mốc hạn theo quy định, không phải ngày công bố thực tế. Ngày thực tế nằm trên công bố của từng doanh nghiệp và bài này không tra từng cái một.

Không khẳng định quy ước khớp lệnh nào là đúng. Bài chỉ đo được rằng công cụ đang dùng khớp tại giá đóng cửa của chính phiên ra tín hiệu, và chỉ ra rằng quy ước ấy chứa một khoản rò rỉ. Khớp ở giá mở cửa phiên kế tiếp cũng có giả định của nó, và bài không đo được giả định nào gần thực tế hơn.

Không suy rộng từ ba mã sang thị trường. Ba mã có đuôi chuỗi phẳng được chọn vì chuỗi của chúng đã dừng, và ba dạng đuôi chuỗi mà chúng thể hiện có thể không phải ba dạng duy nhất. Bài không đếm được có bao nhiêu mã trong tập dữ liệu đang ở tình trạng tương tự.

Không nêu tỷ lệ mã rời thị trường mỗi năm ở Việt Nam. Con số đó cần thống kê đầy đủ các quyết định huỷ niêm yết và đình chỉ, việc nằm ngoài phạm vi bài, và một con số ước lượng ở đây sẽ đúng vào loại sai mà bài đang cảnh báo.

Không xếp hạng chỉ báo hay chiến lược, và không có dòng nào nói bộ quy tắc nào tốt hơn. Quy tắc giao cắt hai đường trung bình được dùng trong mọi phép đo của bài chỉ vì nó đơn giản nhất và dễ lặp lại nhất, không vì bất kỳ ưu điểm nào khác. Thứ hạng giữa các chỉ báo còn không ổn định giữa các mã, chuyện đã đo ở chỉ báo trễ và chỉ báo dẫn.

Câu chốt: một bài về sai lệch mà không liệt kê chỗ chính nó chưa đo được thì đang mắc đúng lỗi mà nó đi cảnh báo.

Bảy câu hỏi trước khi tin một bảng kết quả chạy trên dữ liệu quá khứ

Gộp toàn bộ bài lại thành một danh sách chạy được ngay, dù bảng do bạn chạy hay do người khác đưa. Danh sách này đứng trước bảy câu hỏi của bài trước, vì nếu một câu ở đây không có câu trả lời thì mọi chỉ tiêu trong bảng đều chưa đọc được.

  1. Tập mã này gồm những mã nào, và nó được lập ra khi nào? Nếu tập được sinh bởi một bộ lọc chạy trên dữ liệu hôm nay thì nó chỉ gồm mã còn sống, và không tham số nào sửa được điều đó. Thử đưa một mã đã dừng giao dịch vào rồi xem công cụ trả về gì.
  2. Chuỗi của từng mã có chạy hết cửa sổ không? So ngày cuối của cửa sổ mà công cụ báo với ngày cuối bạn yêu cầu. Lệch thì cửa sổ đã bị rút ngắn, và mọi chỉ tiêu tính theo năm đã đổi mẫu số.
  3. Cột khối lượng ở đoạn cuối chuỗi bằng bao nhiêu? Đây là phép kiểm rẻ nhất trong bảy phép kiểm và cũng là phép kiểm bắt được nhiều nhất. Một chuỗi phiên có bốn giá bằng nhau và khối lượng bằng 0 là chuỗi phiên do phần mềm lấp.
  4. Mốc so sánh mua và giữ bằng bao nhiêu, và nó có phải một con số thật không? Mốc bằng đúng 0 gần như luôn là dấu hiệu của một phép tính không có dữ liệu, không phải một kết quả thị trường.
  5. Lệnh khớp ở giá nào, và giá đó có tồn tại tại thời điểm ra quyết định không? Chạy lại với phí và trượt giá bằng 0 rồi so giá vào lệnh với bốn giá của phiên đó. Trùng giá đóng cửa nghĩa là có rò rỉ.
  6. Mọi cột dữ liệu ngoài giá được gắn vào ngày nào? Với số liệu báo cáo tài chính, hỏi thẳng: gắn vào ngày kết thúc kỳ hay ngày công bố. Không có trường ngày công bố trong dữ liệu thì câu trả lời gần như chắc chắn là vế thứ nhất.
  7. Chuỗi giá đã điều chỉnh quyền chưa, và có ngưỡng tuyệt đối nào trong quy tắc không? Hai câu này phải hỏi cùng lúc, vì mỗi chuỗi hỏng một kiểu và không chuỗi nào đúng cả hai. Có ngưỡng tuyệt đối thì đổi nó thành ngưỡng tỷ lệ trước khi chạy.

Bảy câu này không giúp tìm ra một quy tắc tốt. Chúng chỉ giúp biết bảng trước mặt có đang mô tả một điều gì đó hay không, và trong lĩnh vực này thì đó thường là toàn bộ khác biệt giữa một phép đo và một ảo giác.

Đọc tiếp gì

Đây là phần thứ hai của chuỗi "Kiểm chứng một phương pháp". Phần trước, backtest là gì và đọc kết quả thế nào, xử lý những gì xảy ra sau khi phép tính bắt đầu. Hai phần sau đi tiếp: chi phí giao dịch ăn mòn lợi nhuận thế nào đo cái sàn trượt giá của từng mã và cho thấy một tham số dùng chung sai theo một chiều biết trước; kỳ vọng toán học của một hệ thống ghép tỷ lệ thắng với tỷ lệ lãi trên lỗ thành một con số duy nhất và chỉ ra một phép suy đang bị dùng sai.

Ba bài nền của chủ đề này là chỗ mọi biến dạng trong bài được dựng lần đầu: xu hướng và cấu trúc giá cho khoảng trống giá giả và các cách chuỗi bị gãy vì luật; hỗ trợ và kháng cự bản chất là gì cho lưới bước giá; khối lượng giao dịch nói lên điều gì cho phần khối lượng không đi qua sổ lệnh. Định tự chạy lại các phép đo thì đọc kèm vì sao tối ưu tham số hay dẫn tới overfit, vì cách chia dữ liệu chuỗi thời gian nằm ở đó.

Nội dung trong bài mang tính chia sẻ kiến thức, không phải khuyến nghị mua bán chứng khoán và không phải tư vấn đầu tư. Các mã cổ phiếu xuất hiện trong bài được dùng làm điểm dữ liệu để minh hoạ tình trạng của một chuỗi giá và hành vi của một công cụ, không phải để so sánh doanh nghiệp, và bài không chứa nhận định nào về doanh nghiệp đứng sau bất kỳ mã nào. Toàn bộ kết quả backtest trong bài là mô phỏng quá khứ với phí và trượt giá giả định, không phải kết quả giao dịch thật, và không dòng nào trong đó được đọc thành ưu điểm của một chiến lược. Số liệu chốt ngày 04/09/2026, do FinLens tổng hợp từ dữ liệu giao dịch của HOSE, HNX và UPCoM; các mốc quy định dẫn theo văn bản hiện hành và nên đối chiếu bản đang có hiệu lực khi cần con số chính xác. Kết quả quá khứ không bảo đảm cho kết quả tương lai.

Câu hỏi thường gặp

Sai lệch sống sót trong backtest là gì?

Là sai lệch phát sinh khi tập mã dùng để mô phỏng chỉ gồm những mã còn tồn tại tại thời điểm chạy, trong khi tập mã có thật tại thời điểm quá khứ còn gồm cả những mã sau đó bị huỷ niêm yết, bị đình chỉ giao dịch hoặc biến mất vì lý do khác. Người ra quyết định trong quá khứ không thể biết mã nào sẽ sống, nên một phép mô phỏng chạy trên tập đã lọc sẵn đang cho bộ quy tắc một thông tin mà nó không được phép có.

Làm sao biết công cụ backtest của mình có gồm mã đã rời sàn hay không?

Cách kiểm nhanh nhất là lấy mã của một doanh nghiệp đã dừng giao dịch mà bạn biết chắc, đưa thẳng vào công cụ, rồi xem nó trả về gì. Có ba kết cục và cả ba đều đáng biết: công cụ báo không tìm thấy mã, tức tập dữ liệu đã bị lọc; công cụ chạy và trả kết quả bình thường, khi đó phải kiểm tiếp cột khối lượng ở đoạn cuối chuỗi; hoặc công cụ chạy nhưng âm thầm rút ngắn cửa sổ mô phỏng. Trên bộ công cụ mà bài này đo, bộ lọc tín hiệu cho kết cục thứ nhất còn công cụ backtest cho cả kết cục thứ hai lẫn thứ ba.

Sai lệch nhìn trước có mấy dạng?

Bài này tách ba dạng hay gặp nhất. Một, dùng giá đóng cửa để phát tín hiệu rồi cho khớp lệnh ngay tại chính mức giá đó, tức dùng một con số chỉ tồn tại sau khi phiên đã kết thúc. Hai, gắn số liệu báo cáo tài chính vào ngày kết thúc kỳ thay vì ngày báo cáo được công bố, trong khi khoảng cách giữa hai ngày ấy là hàng chục ngày theo quy định. Ba, dùng danh mục thành phần hiện tại của một chỉ số cho các đoạn quá khứ, tức giả định rằng rổ chỉ số của hôm nay đã tồn tại từ nhiều năm trước.

Chuỗi giá đã điều chỉnh quyền có dùng được để đặt ngưỡng giá không?

Không, và đây là chỗ hay bị bỏ qua nhất. Trên chuỗi đã điều chỉnh hồi tố, giá của một phiên quá khứ đã bị nhân với một hệ số nhỏ hơn 1, nên nó không còn là mức giá mà người ta thật sự đã đặt lệnh. Đo trên một mã HOSE, phiên 12/03/2025 hiện ra ở mức 55,560.68 đ trong khi mức giá thật của phiên đó là 62,200 đ, tức thấp hơn 10.67%. Con số 55,560.68 đ còn không nằm trên lưới bước giá nên không đặt lệnh được ở mức đó. Mọi ngưỡng tuyệt đối đặt trên chuỗi ấy vì thế là ngưỡng ảo.

Vì sao một cây nến có bốn giá bằng nhau và khối lượng bằng 0 lại nguy hiểm cho backtest?

Vì phép tính không phân biệt được nó với một phiên có thật. Đo được trên một mã đã dừng giao dịch từ 08/09/2022: công cụ vẫn ghi nhận một lệnh vào ngày 11/11/2022 và thoát ngày 16/11/2022, tức hai phiên không hề có giao dịch nào. Trên một chuỗi hằng số, hai đường trung bình động hội tụ về đúng cùng một giá trị, nên chỉ một sai số làm tròn rất nhỏ cũng đủ sinh ra một điểm giao cắt. Tín hiệu có thật trong phép tính, giao dịch thì không có thật.

Cập nhật lần cuối:

Đọc tiếp

Nội dung trong bài mang tính chia sẻ kiến thức, không phải khuyến nghị mua bán. Mọi quyết định đầu tư và rủi ro đi kèm thuộc về người đọc.