Đây là một hàng dữ liệu trong tệp cổ phiếu ngày. NVDA, thứ Sáu 7 June 2024, phiên cuối cùng trước khi chia tách 10:1 có hiệu lực.
| Cột | Giá trị |
|---|---|
| ngày | 2024-06-07 |
| giá đóng cửa | 1208.88 |
| khối lượng | 42,800,000 (làm tròn) |
| hệ số chia tách | 1.0 |
| cổ tức | 0.00 |
| giá đóng cửa đã điều chỉnh | 120.8781 |
Tôi cố ý lược bỏ các cột OHLC trong phần này. Chúng sẽ xuất hiện lại gần cuối, và chính chúng âm thầm gây lỗi.
Điều đáng chú ý nhất ở hàng dữ liệu này nằm giữa cột thứ hai và cột thứ sáu. Bảng giá ngày 7 June ghi 1208.88. Sẽ không ai còn giao dịch ở mức giá đó nữa, bởi cổ phiếu mà mức giá ấy đại diện không còn tồn tại dưới dạng một đơn vị như vậy. Mức 120.8781 là giá được tái dựng, và không đơn giản chỉ là 1208.88 chia cho mười. Hãy cùng xem từng cột.
ngày: 2024-06-07, và hai ngày khác còn quan trọng hơn
Việc chia tách được công bố ngày 22 May 2024 cùng với báo cáo lợi nhuận. Ngày
chốt danh sách cổ đông là 6 June. Giao dịch trên cơ sở giá đã điều chỉnh sau
chia tách bắt đầu vào thứ Hai 10 June. Có ba ngày, còn cột
split_factor của nhà cung cấp dữ liệu của bạn chỉ trỏ đến đúng
một ngày trong số đó, thường là phiên đầu tiên sau chia tách. Sai lệch một
ngày ở đây là lỗi hành động doanh nghiệp phổ biến nhất mà tôi gặp, và nó vô
hình khi nhìn tổng thể: đường vốn của bạn có thể xuất hiện một cú giảm 90%
hoặc tăng 900% chỉ trong một ngày. Sharpe tính trên 2,000 ngày gần như không
phản ánh điều đó, nhưng mức sụt giảm tối đa hoặc quy tắc cắt lỗ thì chắc chắn
có.
Ngày của cổ tức cũng có vấn đề riêng, và quy tắc đã thay đổi giữa năm 2024 mà nhiều người không để ý. Cổ tức hàng quý $0.01 của NVDA sau chia tách có ngày chốt danh sách cổ đông là 11 June. Mỹ chuyển sang thanh toán T+1 vào 28 May 2024, nghĩa là ngày giao dịch không hưởng cổ tức nay trùng với ngày chốt danh sách. Mọi bộ nạp dữ liệu tự suy ra ngày giao dịch không hưởng cổ tức bằng cách lấy "ngày chốt danh sách trừ một ngày làm việc" — vốn đúng trong nhiều thập kỷ — bắt đầu âm thầm tạo điều chỉnh cổ tức sớm một phiên từ June 2024. Không có lỗi nào được báo. Hệ số chỉ được ghi vào nhầm hàng.
Lỗi của mã xử lý hành động doanh nghiệp không bao giờ hiện thành ngoại lệ. Đó là một con số lệch một ngày nhưng trông hoàn toàn hợp lý.
giá đóng cửa: 1208.88 — mức giá không có trong tệp
Đây là giá khớp thô, và trong phần lớn quy trình nghiên cứu, nó chỉ tồn tại
như một cột chẳng ai đọc. Tín hiệu được tính trên adj_close. Mọi
quy tắc tham chiếu đến một ngưỡng giá trên mỗi cổ phiếu tính bằng đô la giờ
đều dùng một con số mà không người tham gia thị trường nào từng nhìn thấy.
Đà tăng của NVDA hướng tới $1,000 vào mùa xuân năm 2024 là chuyện có thật, với dòng lệnh thực sự phía sau: các mức giá tròn thu hút giao dịch, giá thực hiện quyền chọn tập trung quanh 950 và 1000, cùng rủi ro tin tức mỗi lần giá tiến gần. Trong dữ liệu đã điều chỉnh, toàn bộ giai đoạn ấy diễn ra ở mức $100. Nếu bạn kiểm thử hành vi quanh các ngưỡng tâm lý, logic lấp khoảng trống giá gần mức tròn hoặc một đặc trưng đo khoảng cách đến giá thực hiện, bạn đang kiểm thử trên chuỗi giá đã được quy đổi bởi một sự kiện còn chưa xảy ra.
Điều tương tự cũng áp dụng cho bộ lọc danh mục cổ phiếu. "Loại cổ phiếu dưới $5" là một đại diện hợp lý cho nhóm vấn đề vi cấu trúc có thật: chênh lệch giá tương đối rộng, quy định về khả năng ký quỹ, chế độ bước giá. Chạy bộ lọc trên dữ liệu lịch sử đã điều chỉnh, bạn sẽ loại các công ty trong những giai đoạn họ giao dịch ở mức $30 rồi sau đó bị chia tách ngược. Dùng bộ lọc "giá trên $500", bạn sẽ âm thầm loại NVDA khỏi mọi năm trước June 2024. Bộ lọc giá phải dùng giá khớp chưa điều chỉnh. Chấm hết.
hệ số chia tách: 1.0 — và 0.1 ở hàng kế tiếp
Hệ số này là tích lũy lùi từ hôm nay. Mọi hàng trước 10 June 2024 mang hệ số 0.1 từ lần chia tách này, nhân với 0.25 từ lần chia 4:1 năm 2021, rồi nhân với các hệ số trước đó. Lịch sử NVDA trước 2021 được quy đổi theo 0.025, nên một thanh giá từng ghi $200 năm 2019 giờ hiện là $5.00.
Có hai hệ quả cần nhớ. Thứ nhất, dữ liệu lịch sử đã điều chỉnh sẽ thay đổi mỗi khi có hành động doanh nghiệp mới. Backtest bạn chạy tháng 5 năm 2024 và backtest chạy hôm nay sẽ cho ra những con số khác nhau dù cùng mã và cùng khoảng thời gian. Nếu bạn lưu bộ nhớ đệm các đặc trưng theo mã và ngày mà không ghi dấu phiên bản dữ liệu, bộ nhớ đệm giờ sẽ trộn lẫn hai thang giá. Vì lý do đó, chúng tôi đưa phiên bản thời kỳ điều chỉnh vào khóa đặc trưng; phải mất một buổi chiều đầy bối rối chúng tôi mới rút ra được bài học này.
Thứ hai, với hệ số 0.025, cổ phiếu từng giao dịch theo bước giá $0.01 nay biến động theo nấc $0.00025. Logic làm tròn bước giá, phép so sánh "có lập đỉnh mới không", các phép kiểm tra bằng nhau trên số thực: tất cả giờ đều hoạt động ở độ phân giải thấp hơn nhiều so với độ phân giải thực tế của thị trường.
cổ tức: 0.01 — con số làm lệch lưới giá của mọi mức giá trước đó
Một cent. Trông như nhiễu, nhưng chính nó khiến adj_close hiển
thị 120.8781 thay vì con số tròn 120.888.
Giá đóng cửa ngày 10 June là 121.79. Hệ số ngày giao dịch không hưởng cổ tức là (121.79 − 0.01) / 121.79 = 0.99991789. Hệ số đó nhân với mọi mức giá trước 11 June, nên mức 120.888 đã điều chỉnh theo chia tách trở thành 120.878074. Mức chênh nhỏ, nhưng tồn tại vĩnh viễn và được áp dụng lại vào mỗi ngày giao dịch không hưởng cổ tức trong hơn sáu chục ngày thuộc lịch sử NVDA. Điều đáng kể là hiệu ứng cộng dồn: khi xem dữ liệu năm 2015, bạn đang nhìn mức giá đã được nhân với hàng chục hệ số gần như vô tỷ. Không mức giá lịch sử nào đã điều chỉnh còn nằm đúng trên lưới giá theo cent.
Điều đó có nghĩa mọi phép so sánh == với một ngưỡng tròn đều sai,
mọi bước kiểm tra "giá có chạm mức giới hạn không" đều cần dung sai, và OHLC
tái dựng không còn thỏa điều kiện low ≤ open ≤ high sau khi làm tròn ngây thơ
đến 4 chữ số thập phân. Điều đó cũng có nghĩa adj_close là chuỗi
tổng lợi nhuận, còn close là chuỗi lợi nhuận theo giá; nếu bạn
tính lợi nhuận từ chuỗi này nhưng tính mức sụt giảm từ chuỗi kia, bạn đã trộn
lẫn hai định nghĩa khác nhau về những gì đã xảy ra.
khối lượng: 42,800,000 — hệ số chia mà các bên áp dụng không nhất quán
Muốn so sánh với các phiên sau chia tách, phải nhân khối lượng cổ phiếu với 10. Một số nhà cung cấp làm vậy, một số thì không. Một số điều chỉnh OHLC nhưng để khối lượng ở dạng thô. Đây là lựa chọn tệ nhất trong ba kiểu, vì tệp trông có vẻ nhất quán cho đến khi bạn nhân giá với khối lượng.
Cách làm hợp lý là ngừng dùng khối lượng cổ phiếu làm đặc trưng và chuyển sang khối lượng giao dịch tính bằng đô la, vốn bất biến trước chia tách ngay từ cách định nghĩa. Bộ lọc thanh khoản theo khối lượng giao dịch đô la trung bình 20 ngày hoàn toàn không cần logic điều chỉnh và không thể bị hành động doanh nghiệp làm hỏng. Đặc trưng bất biến tốt hơn mã điều chỉnh đúng, vì mã điều chỉnh đúng là thứ bạn phải liên tục bảo đảm vẫn đúng.
Ba dạng tệp từ nhà cung cấp, ba kiểu lỗi.
| Dữ liệu cung cấp | Điều gì hỏng |
|---|---|
| Chỉ giá đóng cửa đã điều chỉnh, OHLC thô | Logic khoảng trống giá và trong phiên tính trên hai thang giá khác nhau |
| OHLC đã điều chỉnh, khối lượng thô | Khối lượng giao dịch đô la lệch theo tỷ lệ chia tách; bộ lọc ADV cho kết quả sai |
| OHLCV thô cùng các cột hệ số | Không có gì, nếu áp dụng đúng. Mọi thứ đều hỏng, nếu áp dụng nhầm hàng |
Các cột còn thiếu: adj_open, adj_high, adj_low
Giờ hãy đưa OHLC trở lại. Nếu nguồn dữ liệu chỉ điều chỉnh giá đóng cửa, còn
chiến lược tính khoảng trống giá qua đêm bằng
open / prev_adj_close − 1, thì vào 10 June 2024, phép tính là
120.37 / 120.8781 − 1, một con số hợp lý, khoảng −0.4%. Chạy nguyên mã đó
ngược về 7 June 2024, nó lấy giá mở cửa từ tệp thô (khoảng 1207) rồi so với
giá đóng cửa được điều chỉnh theo hệ số 0.025 từ các lần chia tách trước, tạo
ra khoảng trống giá lên đến vài nghìn phần trăm.
Bất cứ phép tính nào trộn một cột thô với một cột đã điều chỉnh đều là phép tính giữa hai hệ tọa độ. Giờ tôi xem việc đọc dữ liệu trộn lẫn kiểu điều chỉnh là điều kiện khẳng định ở cấp bộ nạp dữ liệu, thay vì một điều mà nhà nghiên cứu phải nhớ, vì chẳng ai nhớ đâu, kể cả tôi.
Điều không cột nào cho biết: thực tế bạn đã mua bao nhiêu cổ phiếu
Đây là vấn đề khiến mô hình chi phí của backtest tốn tiền, và cũng là lý do tôi viết về chia tách cổ phiếu.
Phí hoa hồng cổ phiếu Mỹ thường tính theo mỗi cổ phiếu. Giả sử mức phí là $0.005 với mức tối thiểu $1.00 mỗi lệnh, một biểu phí cố định phổ biến ở các công ty môi giới bán lẻ. Giờ hãy backtest vị thế NVDA trị giá $400,000 vào một năm trước khi chia tách, khi giá thô gần $400 còn giá đã điều chỉnh hiển thị là $40.
| Số cổ phiếu | Phí hoa hồng | |
|---|---|---|
| Thực tế đã xảy ra (giá thô $400) | 1,000 | $5.00 |
| Backtest nhìn thấy gì (giá đã điều chỉnh $40) | 10,000 | $50.00 |
Cùng mức đầu tư, cùng giao dịch, nhưng chi phí mô hình hóa cao gấp 10 lần. Đó là $45 hư cấu cho mỗi lượt, $90 cho mỗi giao dịch khứ hồi, và một chiến lược giao dịch hằng ngày sẽ gánh khoảng $22,000 chi phí mỗi năm chưa từng tồn tại. Với danh mục $400k, mức sinh lời hằng năm giảm 5.6 điểm phần trăm, hoàn toàn do hệ số chia tạo ra. Ở đây nó khiến kết quả trông tệ hơn thực tế, điều đó còn chịu được.
Chia tách ngược tạo ra hiệu ứng theo chiều ngược lại, và đó mới là hướng nguy hiểm. Chia tách ngược 1:10 khiến giá trong dữ liệu đã điều chỉnh cao gấp 10 lần, còn số cổ phiếu thấp hơn 10 lần so với lượng bạn thực sự sẽ giao dịch. Mô hình chi phí âm thầm chia phí trên mỗi cổ phiếu cho mười. Những cổ phiếu thường bị chia tách ngược nằm trong nhóm vốn hóa nhỏ, dưới $5, chênh lệch giá rộng — nơi phí tính trên mỗi cổ phiếu chiếm tỷ trọng lớn nhất trong tổng chi phí giao dịch. Backtest lại ưu ái đúng những mã lẽ ra phải bị phạt nặng nhất.
Vì vậy, cách sửa khá đơn giản về mặt cấu trúc: xác định quy mô vị thế bằng đô la, quy đổi sang số cổ phiếu bằng giá chưa điều chỉnh tại ngày đó, rồi tính phí trên số cổ phiếu ấy. Hãy giữ cột giá thô trong bộ nạp dữ liệu mãi mãi, không phải chỉ để kiểm tra sau này mà còn làm đầu vào trực tiếp cho bộ máy khớp lệnh.
- Tín hiệu và lợi nhuận: chuỗi đã điều chỉnh.
- Số cổ phiếu, phí trên mỗi cổ phiếu, làm tròn bước giá, bộ lọc theo mức giá: chuỗi chưa điều chỉnh.
- Thanh khoản và năng lực giao dịch: khối lượng đô la, không cần điều chỉnh.
Sáu cột, một hàng dữ liệu, một hành động doanh nghiệp khá bình thường được công bố trước ba tuần và chẳng khiến ai bất ngờ. Thị trường xử lý xong trong một buổi sáng. Tái dựng sự kiện đó một cách trung thực trong quy trình nghiên cứu khó hơn nhiều, và dấu hiệu cho thấy quy trình làm chưa đúng hầu như chẳng bao giờ là một thông báo lỗi.
← Tất cả bài viết


