3 tháng Chín, 2026 · kỹ thuật dữ liệu

Những phút không tồn tại: khoảng trống, gián đoạn và nến khối lượng bằng 0 trong dữ liệu OHLCV

Những phút không tồn tại: khoảng trống, gián đoạn và nến khối lượng bằng 0 trong dữ liệu OHLCV

Dữ liệu nến 1-minute trong 1 năm dương lịch lẽ ra phải có 525,600 dòng. Lần tải hợp đồng vĩnh cửu BTCUSDT năm 2025 của chúng tôi có 525,557 dòng — thiếu 43 phút, tỷ lệ đầy đủ 99.992%. Một hợp đồng vĩnh cửu altcoin vốn hóa trung bình trên cùng sàn, cùng lần tải, cùng luồng mã, lại thiếu 1,247 dòng: 99.76%. Còn chuỗi dữ liệu theo phút của một cổ phiếu Mỹ trong cùng năm có ít hơn khoảng 427,000 dòng so với dữ liệu crypto, nhưng đó hoàn toàn không phải khoảng trống, chỉ đơn giản là thị trường đóng cửa.

Ba con số trong số đó chẳng có gì đáng nói. Con số đáng giá cả ngàn lời là 43.

525,600nến 1-minute trong năm không nhuận
0.008%bị thiếu ở BTCUSDT, năm 2025
61%số phút đó rơi vào các giờ biến động thuộc nhóm 10% cao nhất

Bốn tình huống khác nhau nhưng đều trông như một lỗ hổng trong dataframe

Trước khi bàn về con số 43, cần phân loại cho rõ, vì phần lớn mã xử lý khoảng trống đã sai ngay ở tầng này. Khi thiếu một dòng trong tệp parquet cục bộ, bạn không thể biết tình huống nào dưới đây đã gây ra việc đó; và cách xử lý đúng sẽ khác nhau tùy từng trường hợp.

LoạiThực tế đã xảy raBiểu hiện trong dữ liệuCách xử lý đúng
Không có giao dịchThị trường mở cửa; không ai khớp qua chênh lệch giá trong phút đóNến khối lượng bằng 0 (OHLC bằng nhau, trades=0) trên một số endpoint, không có dòng trên endpoint khácGiữ lại. Đây là thông tin có thật: không ai muốn giao dịch.
Sàn ngừng hoạt độngHệ thống khớp lệnh ngừng chạy, dù có nằm trong lịch bảo trì hay khôngKhông có dòng, đôi khi là cả một loạt dòng liên tiếpĐánh dấu dữ liệu cũ. Đừng giao dịch xuyên qua khoảng này.
Tạm ngừng giao dịch mã tài sảnVi phạm biên LULD, chờ tin tức, thông báo hủy niêm yếtKhông có dòng, sau đó là một giao dịch trong phiên đấu giá mở cửa lạiĐánh dấu dữ liệu cũ và xem lần mở cửa lại là một điểm gián đoạn.
Lỗi phía bạnPhân trang bị giới hạn tốc độ, lần thử lại làm rơi mất một trang, lỗi ranh giới múi giờ trong vòng lặpKhông có dòng, không thể phân biệt với các trường hợp trênPhát hiện và tải lại. Đây là trường hợp duy nhất bạn có thể khắc phục.

Các sàn không thống nhất cách thể hiện tình huống đầu tiên trong bảng, và đây chính là điểm dễ gây rắc rối. Endpoint nến của Coinbase bỏ hẳn các khoảng thời gian không có giao dịch, nên lịch sử của một cặp thanh khoản thấp đầy những lỗ hổng thực sự. Klines của Binance thường trả về một nến tổng hợp có khối lượng 0, với open=high=low=close neo ở mức giá giao dịch trước đó. Cùng một thực tế, hai dạng dữ liệu khác nhau; rồi bộ nạp dữ liệu căn chỉnh lại theo lưới phút đầy đủ sẽ biến dạng này thành dạng kia mà không báo cho bạn biết. Hãy kiểm tra cách sàn của bạn xử lý trước khi viết bộ lấp khoảng trống, đừng đợi đến sau.

1,247 phút bị thiếu ở altcoin gần như đều thuộc loại thứ nhất: sổ lệnh mỏng lúc 04:00 UTC vào Chủ nhật, không ai giao dịch. Phiền phức, dễ lý giải và phần lớn vô hại vì chiến lược vốn cũng chẳng giao dịch vào lúc đó. Chính vì vậy tôi ngừng để ý đến nó và quay lại xem xét con số 43.

43 phút đó không hề rải rác

Nếu dữ liệu thiếu đồng đều, 43 phút trong cả năm sẽ xuất hiện thành 43 phút lẻ loi, cứ mỗi tám ngày rưỡi lại có một phút, mỗi lần chỉ tạo ra sai số làm tròn. Nhưng thực tế không phải vậy. Chúng xuất hiện thành sáu đợt: một đợt kéo dài 19 phút liên tiếp, một đợt 11 phút, hai đợt 4 phút và vài đợt gồm 2 phút. Sáu sự kiện, chứ không phải 43 sự cố riêng lẻ.

Các sự kiện này còn phụ thuộc vào chính điều bạn quan tâm. Sàn ngừng hoạt động khi bị quá tải, mà sàn thường bị quá tải lúc giá biến động. Tôi chia từng giờ trong năm theo độ biến động thực tế rồi xem các phút bị thiếu tập trung ở đâu: 61% nằm trong nhóm 10% giờ biến động cao nhất. Xác suất không điều kiện để một phút bất kỳ bị thiếu là 0.008%. Nhưng nếu phút đó thuộc một giờ trong nhóm 10% biến động cao nhất, xác suất vào khoảng 0.05% — cao gấp sáu lần, lại còn tập trung thành cụm.

Vậy nên chỉ số đầy đủ trên bảng theo dõi chất lượng dữ liệu đang đo sai thứ cần đo. 99.992% nghe có vẻ như một bộ dữ liệu mà bạn không cần bận tâm thêm. Nhưng thực chất, con số đó mô tả một chuỗi dữ liệu đầy đủ vào những giờ chiến lược không làm gì, nhưng lại có lỗ hổng đúng vào những giờ nó hoạt động hết công suất. Một hệ thống động lượng kích hoạt khi biến động mở rộng có xác suất gặp khoảng trống cao hơn đáng kể so với con số tổng quan, và khoảng trống xuất hiện ngay giữa giao dịch.

Việc điền giá về trước gây ra chuyện gì chỉ sau vài dòng

Đây là lỗi khiến tôi viết bài này. Lấy đợt kéo dài 19 phút. Cách làm sạch dữ liệu thông thường: căn chỉnh lại theo lưới phút đầy đủ, điền tiếp giá OHLC từ giá đóng cửa gần nhất, đặt khối lượng bằng 0. Chuỗi dữ liệu giờ đã liên tục và các chỉ báo chạy mà không thấy NaN nào.

19 cây nến đó có high == low == close. True range bằng 0 ở từng cây. ATR(14) tính trên cửa sổ này, bắt đầu từ mức khoảng 240 USDT trước khi sàn ngừng hoạt động, giảm dần về khoảng 34 khi sàn hoạt động trở lại — 5 cây nến thật còn lại trong cửa sổ phải gánh toàn bộ giá trị trung bình. Đưa con số đó vào bộ định cỡ vị thế theo biến động, loại size = risk_budget / ATR thông thường. Quy mô vị thế tăng gấp bảy lần.

Cây nến thật tiếp theo là giao dịch khi mở cửa trở lại, và nó chẳng hề yên ắng. Trong trường hợp của chúng tôi, giá mở cửa chênh 1.8% so với giá đóng cửa cuối cùng trước khi sàn ngừng hoạt động. Backtest vui vẻ vào vị thế lớn gấp 7 lần ngay trước một khoảng nhảy giá 1.8%, với một mức khớp lệnh không thể nào tồn tại, tại mức giá chẳng ai chào mua bán. Riêng giao dịch tổng hợp đó có giá trị lớn hơn cả tháng P&L hợp lệ trên đường cong vốn, theo chiều ngược lại — và nó hoàn toàn bắt nguồn từ một dòng làm sạch dữ liệu viết ra chỉ để dataframe trông gọn gàng.

Xóa các dòng thay vì điền cũng không phải cách khắc phục; đó là cùng một lỗi nhưng đội một chiếc mũ khác. Xóa chúng đi, các cửa sổ nhìn lui theo chỉ số nguyên sẽ đánh lừa bạn: “EMA 20 nến” giờ trải dài 39 phút theo đồng hồ thực qua thời gian sàn ngừng hoạt động, lợi suất giữa hai cây nến ở ranh giới biến thành toàn bộ cú nhảy 1.8% được tính như biến động trong 1 phút, còn mọi ước tính biến động theo mỗi nến đều coi đó là sự kiện 60-sigma. Không có cảnh báo nào xuất hiện. Chỉ mục vẫn tăng đơn điệu.

Lấy mẫu lại khiến vấn đề biến mất khỏi tầm mắt

Phần lớn nghiên cứu không chạy trên nến 1-minute mà chạy trên dữ liệu đã gộp, và bước gộp khiến vấn đề như được tẩy sạch. Lấy mẫu lại theo chu kỳ 5-minute, khoảng trống 19 phút biến thành 4 cây nến, trong đó cây đầu và cây cuối chỉ là nến không trọn vẹn. Pandas tính ra OHLC trông hoàn toàn hợp lý từ 2 phút dữ liệu còn lại và gắn nhãn giống hệt một cây nến được tạo từ 5 phút. Đầu ra không có gì phân biệt được chúng.

Cách khắc phục ít tốn kém nhất mà tôi biết: giữ cột bars_in_window xuyên suốt mọi bước lấy mẫu lại và đừng bao giờ loại bỏ nó. Mỗi dòng một số nguyên, vậy là mọi câu hỏi phía sau về độ tin cậy của một cây nến đều có lời giải. Chúng tôi cũng giữ seconds_since_last_real_print, chứa cùng thông tin nhưng ở định dạng mà tầng thực thi có thể xử lý.

Chính sách hiện tại của chúng tôi

Đây là quy trình các agent của chúng tôi hiện áp dụng, theo thứ tự:

  1. Không bao giờ âm thầm căn chỉnh lại dữ liệu. Bộ nạp dữ liệu xuất ra bản kê khoảng trống — thời điểm bắt đầu, thời điểm kết thúc, độ dài và loại nào trong 4 loại mà nó cho là nguyên nhân. Nếu đợt thiếu dưới 3 cây nến và khối lượng ở các cây xung quanh thấp, đó là phút không có giao dịch. Bất kỳ đợt nào dài hơn trong giờ giao dịch đều được xem là sự cố ngừng hoạt động cho đến khi có bằng chứng ngược lại.
  2. Tải lại trước khi diễn giải. Một nửa số khoảng trống ban đầu của chúng tôi do lỗi phân trang. Lần tải thứ hai từ endpoint khác hoặc nhà cung cấp dữ liệu khác sẽ xử lý loại thứ tư và thu hẹp vấn đề trước khi cần đưa ra nhận định nào.
  3. Dùng bộ chặn dữ liệu cũ, không lấp khoảng trống. Chiến lược nhận đầu vào data_age cùng một quy tắc cứng: không mở vị thế mới nếu lần khớp lệnh thật gần nhất đã cũ hơn N cây nến; các vị thế đang mở chỉ được đóng khi sàn mở cửa lại bằng lệnh thị trường, với mức giá đã trừ hao rủi ro khoảng nhảy giá một cách rõ ràng. Những lệnh khớp không thể nào xảy ra còn tệ hơn cả việc bỏ lỡ giao dịch.
  4. Chỉ báo phải nhận NaN, không phải dữ liệu bịa. Giá được điền về trước tuyệt đối không đi đến tầng đặc trưng. Nếu không tính được ATR, giá trị đó chưa xác định; chưa xác định thì không vào vị thế. Lỗi hiển thị rõ ràng tốt hơn một mức đòn bẩy 7x âm thầm.
  5. Báo cáo hiệu suất có điều kiện theo khoảng trống. Mỗi backtest chúng tôi công bố đều trình bày P&L loại trừ các giao dịch sát thời điểm sàn ngừng hoạt động, bên cạnh con số tổng quan. Nếu những giao dịch đó gánh toàn bộ kết quả, thì kết quả ấy chỉ là sản phẩm của lỗi dữ liệu.

Một kiểm tra nhanh bạn có thể chạy ngay hôm nay: gom các phút bị thiếu thành những đợt liên tiếp, rồi xem bao nhiêu phần trăm giao dịch trong backtest mở hoặc đóng trong vòng 30 phút quanh ranh giới một đợt. Nếu dưới 1% thì có lẽ khoảng trống không ảnh hưởng nhiều. Nếu là 5% trở lên, đường cong vốn của bạn một phần đang kể câu chuyện về thời gian sàn ngừng hoạt động.

Dấu hiệu tôi tin cậy nhất là hình dạng của các khoảng trống, chứ không phải số lượng. Bộ dữ liệu có hàng ngàn lỗ hổng rải rác trong những giờ giao dịch ảm đạm thường không sao. Bộ dữ liệu chỉ có vài cụm sát nhau đang báo cho bạn biết có thứ gì đó trục trặc khi hệ thống chịu tải, và chính lúc hệ thống chịu tải là lúc chiến lược của bạn hoạt động.

khoảng trống OHLCVkỹ thuật dữ liệukiểm thử ngượclấy mẫu lạihợp đồng tương lai crypto
← Tất cả bài viết