Cấu hình streaming mẫu
Mục lục
Sau khi đã tìm hiểu các thành phần nền tảng trong hướng dẫn Khung streaming dữ liệu, bạn có thể lựa chọn phương thức thiết lập nhanh và tự động hóa cao hơn: cấu hình streaming mẫu khai báo bằng định dạng TOML. Thay vì phải tự viết mã Python tùy biến lớp phân tích cú pháp (BaseDataParser) hay tự xử lý handshake/heartbeat thủ công, bạn chỉ cần mô tả cấu hình nguồn WebSocket trong một tệp TOML duy nhất. Thư viện sẽ tự động quản lý toàn bộ quy trình: từ kết nối mạng, gửi thông điệp đăng ký kênh, duy trì heartbeat, tự động reconnect khi gián đoạn, cho tới việc bóc tách từng bản ghi thành các sự kiện dữ liệu chuẩn hóa.
Theo định hướng xây dựng công cụ dạng bộ khung và tiêu chuẩn hoá của Vnstock, vnstock_pipeline cung cấp trình đọc cấu hình và bộ máy vận hành chuẩn, còn bạn chủ động đưa tệp cấu hình của mình vào và sử dụng trong phạm vi được phép của nguồn dữ liệu. Hướng dẫn dưới đây dùng luồng dữ liệu thị trường công khai của Binance Spot (đơn vị có công bố tài liệu WebSocket mở) làm ví dụ minh hoạ kỹ thuật.
Ba kiểu giao thức đóng khung (Framing)
Khi nhận dữ liệu trong phiên qua WebSocket, mỗi máy chủ nguồn có thể đóng gói thông điệp theo một chuẩn khác nhau. vnstock_pipeline hỗ trợ 3 kiểu đóng khung thông điệp (framing) phổ biến:
Kiểu framing | Định dạng thông điệp nhận về | Cơ chế Handshake & Heartbeat |
|---|---|---|
json | Mỗi thông điệp là một tài liệu JSON hoàn chỉnh | Không cần handshake riêng; kết nối trực tiếp qua WebSocket chuẩn |
socketio | Gói tin dạng 42[tên_sự_kiện, dữ_liệu] (chuẩn Socket.IO trên Engine.IO v4) | Thư viện tự động gửi mã 40 và đợi máy chủ xác nhận |
engineio | Gói tin dạng 4{...} (Engine.IO v4 thuần, không có lớp Socket.IO) | Không gửi mã 40, thư viện tự động phản hồi heartbeat (2 → 3) |
Cách nhận biết giao thức của nguồn
Để xác định nguồn dữ liệu của bạn đang sử dụng giao thức nào, hãy kiểm tra gói tin đầu tiên máy chủ trả về ngay sau khi thiết lập kết nối:
- Engine.IO: Gói tin đầu tiên có dạng chuỗi
0{...}. Với giao thức này, thư viện sẽ tự động duy trì heartbeat bằng cách phản hồi mã3(pong) mỗi khi nhận được mã2(ping) từ máy chủ. - Socket.IO: Máy chủ phản hồi mã
40sau khi client gửi gói tin40. - JSON thuần: Máy chủ gửi trực tiếp đối tượng JSON hợp lệ (ví dụ:
{"stream": ..., "data": ...}).
Cấu trúc cú pháp của tệp cấu hình TOML
Tệp cấu hình TOML được tổ chức thành 3 khối thiết lập chính: nguồn kết nối ([source]), thông điệp đăng ký ([[subscribe]]) và quy tắc định tuyến dữ liệu ([[routes]]).
1. Khối nguồn kết nối [source]
Thiết lập thông số kết nối cơ bản tới máy chủ WebSocket:
| Khóa | Ý nghĩa và cách sử dụng |
|---|---|
name | Tên định danh cho nguồn dữ liệu (ví dụ: "binance_spot"). |
uri | Địa chỉ endpoint WebSocket của nguồn. Có thể sử dụng cú pháp ${TEN_BIEN} để lấy giá trị động từ biến môi trường (ví dụ: wss://example.com/ws?token=${WS_TOKEN}). |
framing | Kiểu đóng khung thông điệp: "json", "socketio" hoặc "engineio". |
namespace | (Tùy chọn) Định danh namespace dành riêng cho nguồn Socket.IO có phân chia namespace (mặc định là root /). |
2. Khối đăng ký luồng dữ liệu [[subscribe]]
Mô tả cấu trúc thông điệp gửi lên máy chủ để đăng ký nhận luồng dữ liệu cho danh sách mã. Bạn có thể khai báo nhiều khối [[subscribe]] cho các loại dữ liệu khác nhau (như trades, nến kline, sổ lệnh):
| Khóa | Ý nghĩa và cách sử dụng |
|---|---|
name | Tên định danh nhóm đăng ký (ví dụ: "trades", "klines"). |
template | Mẫu thông điệp đăng ký dạng JSON gửi đi. Lưu ý: dấu ngoặc nhọn trong JSON phải được viết đôi {{ }} để phân biệt với biến thay thế; {symbols} đại diện cho danh sách mã và {seq} là số thứ tự tăng dần tự động. |
unsubscribe_template | Mẫu thông điệp hủy đăng ký gửi lên máy chủ khi bạn chủ động loại bớt mã trong lúc tiến trình đang chạy. |
item | Mẫu định dạng chuỗi cho từng mã riêng lẻ trước khi ghép vào mảng, ví dụ: "{symbol}@trade". |
case | Chuẩn hóa chữ hoa/chữ thường cho mã: "lower" (viết thường) hoặc "upper" (viết hoa). |
chunk_size | Số lượng mã tối đa trong một gói tin đăng ký, giúp tránh vượt quá độ dài cho phép của máy chủ (ví dụ: 50). |
symbols | (Tùy chọn) Danh sách mã cố định (như các mã chỉ số thị trường). Nếu không khai báo, thư viện sẽ sử dụng danh sách mã bạn truyền vào khi khởi chạy. |
separator | Ký tự phân cách khi ghép danh sách mã thành chuỗi (ví dụ: "," hoặc "/"). |
symbols_format | Định dạng xuất danh sách mã: "array" (mảng JSON) hoặc "string" (chuỗi nối bằng dấu phân cách). |
3. Khối định tuyến và bóc tách dữ liệu [[routes]]
Xác định cách nhận diện thông điệp nhận về và chuyển đổi các trường dữ liệu thô thành sự kiện chuẩn hóa. Một cấu hình có thể chứa nhiều khối [[routes]]:
| Khóa | Ý nghĩa và cách sử dụng |
|---|---|
event_type | Tên định danh loại sự kiện đầu ra (ví dụ: "trade", "kline"), được gắn cố định vào từng bản ghi sự kiện sau xử lý. |
match | Điều kiện để khớp thông điệp nhận về vào route này. Hỗ trợ lọc theo tên sự kiện Socket.IO (event hoặc event_prefix), hoặc theo giá trị trường trong gói JSON (path kết hợp với equals hoặc prefix). |
records | Đường dẫn (path) tới bản ghi hoặc mảng bản ghi bên trong thông điệp (ví dụ: "data"). Thư viện sẽ tự động duyệt qua từng phần tử nếu đây là một danh sách. |
symbol | Tên trường chứa mã chứng khoán/tài sản trong bản ghi (ví dụ: "s"). |
fields | Bảng ánh xạ từ tên trường trong dữ liệu nguồn sang tên cột mong muốn ở đầu ra. Hỗ trợ truy cập trường lồng nhau bằng dấu chấm (ví dụ: open = "k.o", close = "k.c"). |
numbers | Danh sách các cột cần tự động ép kiểu từ chuỗi sang số thực (float), xử lý trường hợp sàn giao dịch trả về giá trị số học ở dạng chuỗi ký tự (string). |
time | Cấu hình trích xuất mốc thời gian sự kiện, gồm field (tên trường thời gian) và unit ("ms" cho mili-giây, "s" cho giây). |
filter | (Tùy chọn) Bộ lọc điều kiện cho từng bản ghi riêng lẻ, hữu ích khi một gói tin chứa nhiều phân loại dữ liệu hỗn hợp. |
decode | (Tùy chọn) Giải mã chuỗi JSON lồng nhau bên trong một trường chuỗi nếu nguồn đóng gói hai lần. |
split / columns | (Tùy chọn) Bóc tách một chuỗi phân cách theo danh sách cột định sẵn. |
Cơ chế định tuyến: Mỗi thông điệp nhận về từ WebSocket sẽ được kiểm tra với toàn bộ các route. Nếu một thông điệp khớp với nhiều route, tất cả các route đó đều được xử lý độc lập. Các thông điệp không khớp với bất kỳ route nào sẽ được ghi nhận vào bộ đếm thống kê và có thể ghi ra tệp riêng khi bạn thiết lập tham số
capture_path.
Ví dụ thực tế: Tệp cấu hình Binance Spot
Dưới đây là tệp cấu hình mẫu hoàn chỉnh binance_spot.toml để tiếp nhận dữ liệu khớp lệnh (trades) và nến 1 phút (klines) từ luồng WebSocket công khai của Binance Spot:
[source]
name = "binance_spot"
uri = "wss://stream.binance.com:9443/stream"
framing = "json"
# Mỗi mã được định dạng thành "<mã>@trade" và "<mã>@kline_1m"; gộp tối đa 50 stream mỗi gói tin
[[subscribe]]
name = "trades"
template = '{{"method":"SUBSCRIBE","params":{symbols},"id":{seq}}}'
unsubscribe_template = '{{"method":"UNSUBSCRIBE","params":{symbols},"id":{seq}}}'
item = "{symbol}@trade"
case = "lower"
chunk_size = 50
[[subscribe]]
name = "klines"
template = '{{"method":"SUBSCRIBE","params":{symbols},"id":{seq}}}'
unsubscribe_template = '{{"method":"UNSUBSCRIBE","params":{symbols},"id":{seq}}}'
item = "{symbol}@kline_1m"
case = "lower"
chunk_size = 50
[[routes]]
event_type = "trade"
match = { path = "data.e", equals = "trade" }
records = "data"
symbol = "s"
fields = { trade_id = "t", price = "p", quantity = "q", buyer_is_maker = "m" }
numbers = ["price", "quantity"]
time = { field = "T", unit = "ms" }
[[routes]]
event_type = "kline"
match = { path = "data.e", equals = "kline" }
records = "data"
symbol = "s"
fields = { interval = "k.i", open_time = "k.t", open = "k.o", high = "k.h", low = "k.l", close = "k.c", volume = "k.v", quote_volume = "k.q", trades = "k.n", is_closed = "k.x" }
numbers = ["open", "high", "low", "close", "volume", "quote_volume"]
time = { field = "E", unit = "ms" }Giải thích chi tiết các khối cấu hình:
- Khối
[source]: Binance gửi thông điệp dạng JSON thuần, do đó ta khai báoframing = "json". - Khối
[[subscribe]]:template: Định dạng JSON yêu cầu của Binance có dạng{"method": "SUBSCRIBE", "params": [...], "id": 1}. Dấu ngoặc nhọn JSON được nhân đôi{{ }}để phân biệt với{symbols}và{seq}.item = "{symbol}@trade"vàcase = "lower": Tự động chuyển đổi mã tài sản bạn truyền vào (ví dụBTCUSDT) thành định dạng stream viết thường của Binance (btcusdt@trade).chunk_size = 50: Gộp tối đa 50 kênh trong một thông điệp gửi đi, giúp giảm thiểu số lượng gói tin đăng ký.
- Khối
[[routes]]:match = { path = "data.e", equals = "trade" }: Kiểm tra trườnge(event type) trong đối tượngdata; nếu có giá trị"trade"thì xử lý theo route này.fields: Ánh xạ các trường viết tắt của Binance (t,p,q,m) sang tên cột rõ nghĩa (trade_id,price,quantity,buyer_is_maker). Đối với nến kline, ta dùng cú pháp dấu chấm nhưopen = "k.o"để lấy trườngonằm trong đối tượngk.numbers: Binance trả về giá trị số học (như giá và khối lượng) dưới dạng chuỗi string trong JSON nhằm tránh hiện tượng làm tròn số của JavaScript; khai báonumbersgiúp thư viện tự động chuyển đổi các trường này thành số thực để sẵn sàng tính toán.
Khởi chạy luồng stream từ tệp cấu hình
Thư viện cung cấp hai phương thức vận hành: chạy nhanh bằng hàm tiện ích hoặc quản lý linh hoạt qua lớp client. Cả hai đều tích hợp trực tiếp với bộ ghi dữ liệu CSVProcessor (được giới thiệu trong hướng dẫn Khung streaming dữ liệu) hoặc các bộ xử lý tùy biến kế thừa từ BaseProcessor.
Cách 1: Khởi chạy nhanh với run_profile
Thích hợp cho các tiến trình tiếp nhận dữ liệu chạy nền với danh sách mã cố định:
import asyncio
from pathlib import Path
from vnstock_pipeline.stream import CSVProcessor
from vnstock_pipeline.stream.profiles import load_profile, run_profile
# Thư mục lưu trữ tệp đầu ra trên máy bạn
OUT = Path.home() / "vnstock_stream"
OUT.mkdir(exist_ok=True)
# Nạp tệp cấu hình TOML
profile = load_profile("binance_spot.toml")
# Chạy luồng tiếp nhận dữ liệu
asyncio.run(run_profile(
profile,
["BTCUSDT", "ETHUSDT"],
[CSVProcessor(str(OUT / "binance_{event_type}_%Y-%m-%d.csv"))],
rotate_seconds=23 * 3600, # Binance chủ động ngắt mỗi kết nối sau 24 giờ; xoay vòng sau 23 giờ để duy trì ổn định
))Mỗi bản ghi sự kiện sau khi qua xử lý sẽ bao gồm các trường chuẩn: event_type, symbol, ts_ms (mốc thời gian từ nguồn, mili-giây), received_ms (mốc thời gian máy bạn nhận được gói tin) cùng các trường dữ liệu nghiệp vụ đã ánh xạ trong fields.
Cách 2: Điều khiển nâng cao với ProfileClient
Khi cần thay đổi danh sách mã động trong phiên giao dịch (thêm hoặc bớt mã theo dõi) mà không muốn làm ngắt quãng toàn bộ kết nối, bạn sử dụng ProfileClient:
import asyncio
from pathlib import Path
from vnstock_pipeline.stream import CSVProcessor
from vnstock_pipeline.stream.profiles import load_profile, ProfileClient
OUT = Path.home() / "vnstock_stream"
OUT.mkdir(exist_ok=True)
profile = load_profile("binance_spot.toml")
async def main():
# Khởi tạo client với danh sách theo dõi ban đầu
client = ProfileClient(profile, ["BTCUSDT"])
client.add_processor(CSVProcessor(str(OUT / "binance_{event_type}_%Y-%m-%d.csv")))
# Kết nối tới máy chủ nguồn
await client.connect()
# Đăng ký thêm mã mới ngay trong phiên
await client.subscribe(["SOLUSDT"])
# Hủy đăng ký mã không còn theo dõi
await client.unsubscribe(["BTCUSDT"])
# Giữ phiên chạy cho đến khi mất kết nối
await client.wait_until_disconnected()
# asyncio.run(main())Phân tích dữ liệu tick ghi nhận được với vnstock_quant
Sau khi lưu trữ luồng dữ liệu khớp lệnh (tick) ra tệp CSV, bạn có thể kết hợp với mô-đun micro của thư viện vnstock_quant để dựng nến và phân tích cấu trúc vi mô thị trường:
from datetime import date
from pathlib import Path
import pandas as pd
from vnstock_quant import micro
OUT = Path.home() / "vnstock_stream"
# Đọc dữ liệu tick ghi nhận trong ngày
ticks = pd.read_csv(OUT / f"binance_trade_{date.today():%Y-%m-%d}.csv")
ticks["time"] = pd.to_datetime(ticks["ts_ms"], unit="ms", utc=True)
btc = ticks[ticks["symbol"] == "BTCUSDT"]
# Dựng nến 1 phút theo thời gian truyền thống (time bars)
m1 = micro.bars(btc, by="time", size="1min")
# Dựng nến theo ngưỡng giá trị giao dịch 500.000 USDT (value bars)
vb = micro.bars(btc, by="value", size=500_000)Ghi chú về phân loại giao dịch chủ động: Trong dữ liệu của Binance, cờ
buyer_is_maker = Truecó nghĩa là bên mua đóng vai trò cung cấp thanh khoản (maker trong sổ lệnh), do đó giao dịch phát sinh từ phía người bán chủ động (taker). Hàmmicro.classifycủavnstock_quantnhận diện trực tiếp cột này để phân loại dòng tiền mua/bán chủ động.
Quy trình 4 bước tự tạo tệp cấu hình cho nguồn mới
Khi cần kết nối tới một nguồn dữ liệu WebSocket mới, bạn có thể thực hiện theo quy trình 4 bước chuẩn sau (kết hợp với cơ chế handshake và quản lý phiên đã giới thiệu trong hướng dẫn Khung streaming dữ liệu):
- Nghiên cứu tài liệu và điều khoản nguồn: Tìm hiểu cấu trúc gói tin, quy trình handshake và kiểm tra kỹ điều khoản sử dụng của nhà cung cấp trước khi tích hợp.
- Bảo mật thông tin xác thực qua biến môi trường: Tuyệt đối không ghi cứng (hardcode) các thông tin nhạy cảm (như token, mã phiên hoặc định danh thiết bị) vào tệp TOML. Hãy lưu chúng vào biến môi trường trên máy bạn và tham chiếu trong tệp cấu hình bằng cú pháp
${TEN_BIEN}tại trườnguri. - Ghi nhận gói tin mẫu với
capture_path: Tạo một tệp cấu hình cơ bản chứa khối[source],[[subscribe]]và một route chưa thiết lập điều kiện lọc, sau đó khởi chạy với tham sốcapture_pathđể ghi lại các gói tin thô thực tế:Pythonclient = ProfileClient( load_profile("nguon_cua_ban.toml"), ["AAA"], capture_path="du_lieu_mau.jsonl", capture_limit=500, ) - Phân tích dữ liệu mẫu và hoàn thiện các khối
[[routes]]: Mở tệp.jsonlvừa ghi nhận, quan sát cấu trúc thực tế của các thông điệp để xác định chính xác đường dẫn trường (records,symbol,fields) và quy tắc lọc (match).
Một số lưu ý kỹ thuật và giới hạn vận hành
- Giới hạn số lượng stream và tần suất gửi tin: Mỗi nhà cung cấp đều có quy định riêng về giới hạn kết nối (ví dụ Binance cho phép tối đa 1024 stream trên một kết nối và 5 thông điệp gửi lên mỗi giây). Thư viện
vnstock_pipelineđã tích hợp sẵn cơ chế giãn cách tự động ít nhất 0,2 giây giữa các gói tin đăng ký để tránh bị ngắt kết nối đột ngột. - Xử lý lỗi HTTP khi handshake: Nếu quá trình handshake WebSocket bị từ chối với các mã lỗi HTTP
401(Unauthorized),403(Forbidden) hoặc451(Unavailable For Legal Reasons),run_profilesẽ chủ động dừng hoàn toàn tiến trình và không thử lại nhằm tránh việc gửi yêu cầu dồn dập khiến IP của bạn bị đưa vào danh sách chặn. Mã HTTP429sẽ được ghi nhận là trạng thái chạm trần tần suất (rate limited). - Nguyên tắc an toàn cho tệp cấu hình: Tệp cấu hình TOML được thiết kế có chủ đích không cho phép cấu hình trực tiếp các header tùy biến, cookie,
OriginhayReferer. Bất kỳ thông số phiên cần thiết nào đều phải được truyền qua chuỗi truy vấn (query string) trongurithông qua biến môi trường${TEN_BIEN}. - Mục đích sử dụng: Toàn bộ dữ liệu tiếp nhận được lưu trữ trực tiếp trên máy của bạn nhằm phục vụ cho mục đích nghiên cứu và phân tích định lượng cá nhân. Hãy lưu ý các quy định của nhà cung cấp về việc chia sẻ hoặc phân phối lại dữ liệu.