Kiến trúc bot trading: năm khối và vì sao phải tách rời
Script một tệp chạy được vài tuần rồi bắt đầu hỏng theo những cách không hiểu nổi. Tách thành năm khối nghe thừa thãi lúc mới viết, nhưng đó là thứ cứu bạn lúc 13h45 khi có sự cố.
Kiến trúc bot trading là thứ người mới thấy thừa thãi và người đã chạy bot vài tháng thấy là bắt buộc. Một script một tệp chạy được vài tuần, rồi bắt đầu hỏng theo những cách khó hiểu: vị thế lệch so với dự tính, lệnh đặt hai lần, bot tưởng mình đang đứng ngoài trong khi vẫn có hợp đồng mở.
Những lỗi đó không đến từ thuật toán sai. Chúng đến từ việc mọi thứ nằm chung một chỗ.
Bài này đi sâu vào năm khối đã nhắc ở bài tổng quan về bot trading, nói rõ mỗi khối chịu trách nhiệm gì và ranh giới giữa chúng nằm ở đâu.
Khối dữ liệu: nơi lỗi âm thầm nhất
Nhiệm vụ: lấy dữ liệu từ nguồn, chuẩn hoá về một định dạng duy nhất, và từ chối dữ liệu đáng ngờ thay vì chuyền tiếp.
Phần từ chối là phần người mới bỏ qua. Nếu nguồn trả về một nến có giá cao nhất nhỏ hơn giá đóng cửa, hoặc một khoảng trống thời gian bất thường, khối này phải phát hiện và báo động chứ không đưa vào tính toán. Một tín hiệu sinh ra từ dữ liệu hỏng vẫn trông như tín hiệu bình thường — đó là điều nguy hiểm.
Khối này cũng chịu trách nhiệm về ranh giới thời gian: chỉ được cung cấp dữ liệu đã hoàn tất tại thời điểm hỏi. Nến của phút hiện tại chưa đóng thì chưa được tính là nến. Đây chính là cách chặn lỗi nhìn trước tương lai ngay từ tầng dưới cùng.
Khối tín hiệu: chỉ trả lời một câu
Nhiệm vụ: nhận dữ liệu sạch, trả về trạng thái mong muốn — mua, bán, hay đứng ngoài.
Điều quan trọng là những gì khối này không làm. Nó không biết tài khoản còn bao nhiêu tiền. Nó không biết hôm nay đã lỗ bao nhiêu. Nó không quyết định khối lượng.
Tách bạch như vậy mang lại ba lợi ích cụ thể. Kiểm định được độc lập: chạy khối tín hiệu trên dữ liệu lịch sử và xem chuỗi trạng thái nó sinh ra, không cần mô phỏng cả hệ thống. Thay được dễ dàng: đổi chiến lược mà không đụng phần còn lại. Và quan trọng nhất, khi bot lỗ bạn biết ngay vấn đề nằm ở tín hiệu hay ở khâu thực thi.
Khối rủi ro: có quyền phủ quyết
Nhiệm vụ: nhận trạng thái mong muốn từ khối tín hiệu, đối chiếu với trạng thái tài khoản, rồi quyết định có vào không và vào bao nhiêu.
Đây là khối duy nhất có quyền nói không. Tín hiệu bảo mua nhưng đã chạm giới hạn lỗ ngày thì không mua. Tín hiệu bảo mua nhưng biến động thị trường vượt ngưỡng thì mua ít hơn. Tín hiệu bảo mua nhưng đang có vị thế cùng hướng đủ lớn thì bỏ qua.
Cách tính khối lượng và các ngưỡng giới hạn nằm ở bài về quản trị vốn. Điểm cần nhấn ở đây là vị trí của nó trong kiến trúc: nằm giữa tín hiệu và lệnh, không nằm bên trong tín hiệu.
Gộp chung là lỗi phổ biến nhất của người mới, và hậu quả rất cụ thể: khi muốn siết rủi ro, bạn phải sửa vào logic chiến lược, và mỗi lần sửa lại phá vỡ kết quả kiểm định đã có.
Khối quản lý lệnh: theo dõi vòng đời
Nhiệm vụ: biến quyết định "mua 2 hợp đồng" thành lệnh thật, rồi theo dõi nó tới khi kết thúc.
Một lệnh đi qua nhiều trạng thái: chuẩn bị gửi, đã gửi chờ phản hồi, đã vào sổ, khớp một phần, khớp hết, bị huỷ, bị từ chối. Bot chỉ hiểu hai trạng thái — chưa khớp và đã khớp — sẽ sai ở mọi trường hợp còn lại.
Ba tình huống khối này phải xử lý đúng:
Mất phản hồi. Gửi lệnh nhưng không nhận được xác nhận. Lệnh có thể đã vào sổ hoặc chưa. Cách xử lý đúng là truy vấn lại trạng thái từ sàn, không phải gửi lại — gửi lại là cách tạo ra vị thế gấp đôi.
Khớp một phần. Đặt 5 hợp đồng, khớp 2. Vị thế thật là 2, và mọi tính toán phía sau phải dùng con số đó chứ không phải 5.
Lệnh treo quá lâu. Phải có quy tắc hết hạn: không khớp sau khoảng thời gian định trước thì huỷ và đánh giá lại. Lệnh treo mà bot quên là nguồn gốc của những vị thế xuất hiện bất ngờ.
Khối kết nối: gói riêng phần bẩn
Nhiệm vụ: nói chuyện với API của công ty chứng khoán hoặc sàn, và giấu mọi thứ xấu xí của nó khỏi phần còn lại.
Mỗi nơi một chuẩn, một cách xác thực, một định dạng mã lỗi, một giới hạn tần suất gọi. Nếu những chi tiết đó rò rỉ ra các khối khác, đổi nơi giao dịch nghĩa là viết lại cả bot.
Khối này cũng là nơi xử lý thử lại khi lỗi tạm thời, tôn trọng giới hạn số lệnh mỗi giây, và giữ kết nối sống. Và vì nó là ranh giới duy nhất chạm ra ngoài, nó cũng là chỗ để chuyển sang chế độ mô phỏng: thay đúng một khối là bot chạy không đặt lệnh thật, phần còn lại không biết gì.
Vòng phản hồi từ nhật ký
Mũi tên quay ngược trong sơ đồ không phải trang trí. Mỗi lệnh khớp phải được ghi lại kèm giá mong muốn, giá khớp thật, và dấu thời gian từng chặng.
Dữ liệu đó dùng để đo chi phí thực thi thật, rồi đưa con số thật đó ngược vào giả định backtest. Không có vòng này, bạn mãi kiểm định chiến lược bằng giả định lạc quan mà không biết mình đang sai bao nhiêu.
Trạng thái: thứ phải sống sót qua khởi động lại
Có một câu hỏi phân biệt bot nghiệp dư với bot dùng được: khi tiến trình bị tắt rồi bật lại, bot có biết mình đang ở đâu không?
Máy chủ khởi động lại, tiến trình bị hệ điều hành kết thúc, bạn cập nhật mã và restart — những việc này chắc chắn xảy ra. Nếu bot giữ trạng thái chỉ trong bộ nhớ, mọi thứ mất sạch: nó không biết đang có vị thế, không biết lệnh nào đã gửi, không biết hôm nay đã lỗ bao nhiêu.
Hai nguyên tắc xử lý.
Nguồn sự thật là sàn, không phải bộ nhớ bot. Khi khởi động, việc đầu tiên là truy vấn vị thế và lệnh đang mở từ công ty chứng khoán, rồi đồng bộ trạng thái nội bộ theo đó. Đừng tin biến lưu trong bot.
Ghi ra đĩa những gì sàn không biết. Sàn biết bạn có mấy hợp đồng, nhưng không biết hôm nay bot đã lỗ bao nhiêu so với giới hạn ngày, hay lệnh nào thuộc chiến lược nào. Những con số đó phải ghi ra tệp hoặc cơ sở dữ liệu sau mỗi thay đổi.
Bước đối chiếu là bước quan trọng nhất và cũng hay bị bỏ. Nếu trạng thái lưu nói đang có 2 hợp đồng mà sàn nói có 3, đừng đoán — dừng lại và báo động. Một bot tự "sửa" chênh lệch mà nó không hiểu sẽ tạo ra sai lệch lớn hơn.
Nhìn theo chiều dọc thì rõ vì sao tầng rủi ro phải nằm riêng: nó là khối duy nhất có quyền chặn một tín hiệu đã hợp lệ, và quyền đó không được trộn vào logic chiến lược.
Bắt đầu ở mức nào là hợp lý
Không cần dựng cả năm khối ngay ngày đầu, nhưng có hai ranh giới nên tôn trọng từ dòng mã đầu tiên: tín hiệu tách khỏi rủi ro, và kết nối sàn tách khỏi logic.
Hai ranh giới đó tốn thêm chừng một giờ lúc mới viết, và tiết kiệm cho bạn hàng chục giờ gỡ lỗi sau này — cùng với số tiền mà những lỗi đó sẽ lấy đi.
Câu hỏi thường gặp
- Có cần tách khối ngay từ bot đầu tiên không?
- Nên tách từ đầu, vì chi phí tách lúc mới viết gần như bằng không, còn chi phí tách sau khi mã đã rối thì rất lớn. Tối thiểu phải tách tầng rủi ro ra khỏi tầng tín hiệu.
- Tầng rủi ro khác gì lệnh dừng lỗ?
- Dừng lỗ là mức thoát của một lệnh cụ thể. Tầng rủi ro quyết định có vào lệnh không và vào bao nhiêu, dựa trên trạng thái toàn tài khoản — đã lỗ bao nhiêu hôm nay, đang mở mấy vị thế, biến động thị trường ra sao.
- Vì sao khối quản lý lệnh quan trọng?
- Vì lệnh có nhiều trạng thái trung gian: đã gửi nhưng chưa có phản hồi, khớp một phần, bị từ chối. Bot chỉ biết hai trạng thái khớp hoặc chưa khớp sẽ hiểu sai vị thế thật và tính sai mọi thứ phía sau.
- Nên viết bằng ngôn ngữ gì?
- Python đủ nhanh cho mọi chiến lược khung phút trở lên và có hệ sinh thái dữ liệu tốt nhất. Chỉ cần ngôn ngữ nhanh hơn khi làm chiến lược nhạy cảm với độ trễ dưới mili giây, và đó không phải sân chơi của cá nhân.