Vi cấu trúc thị trường (market microstructure) là "lớp vật lý" của một sàn giao dịch: tốc độ khớp lệnh, độ sâu order book, độ giãn spread khi tin tức xuất hiện, hay cách dòng lệnh lớn đẩy giá đi bao xa. Nếu bạn đang xây mô hình execution, nghiên cứu thanh khoản hay đơn giản là muốn hiểu vì sao một lệnh market trên Binance bị fill tệ hơn kỳ vọng, bạn cần dữ liệu tick-by-tick chất lượng cao. Trong bài này, tôi sẽ hướng dẫn cách tải dữ liệu L2/L3 từ Tardis và tính các chỉ số microstructure kinh điển ngay trong Python.

1. Vì sao chọn Tardis thay vì tự thu thập từ Binance WebSocket

Tự capture WebSocket Binance trong production rẻ nhưng đau: gap khi reconnect, schema thay đổi không báo trước, khó replay lại đúng phiên backtest. Tardis cung cấp kho dữ liệu tick chuẩn hóa (đã đối chiếu sequence, gộp incremental update thành snapshot) và cho phép replay bất kỳ khoảng thời gian nào trong quá khứ qua WebSocket. Đó là lý do nhiều quỹ prop trading và team nghiên cứu crypto chọn Tardis làm "ground truth".

Bạn cần một API key Tardis (đăng ký tại tardis.dev, có tier miễn phí giới hạn). Toàn bộ code dưới đây chạy được trên Python 3.10+ với pandas, numpy, websockets.

2. Tải snapshot order book 25 cấp theo ngày

Tardis lưu trữ snapshot L2 dạng CSV gzip trên S3. Cách truy cập nhanh nhất là dùng tardis-client hoặc smart_open trực tiếp.

import pandas as pd
import dask.dataframe as dd

Snapshot top-25 levels của Binance spot, ngày 2024-03-15

base = "s3://tardis-internal-data/binance/book_snapshot_25/2024-03-15" df = dd.read_csv( f"{base}/*.csv.gz", storage_options={"anon": True}, dtype={"bid_price_0": "float64", "ask_price_0": "float64"} )

Lọc cặp BTCUSDT, chuyển sang pandas để xử lý

btc = df[df["symbol"] == "BTCUSDT"].compute() print(f"Số snapshot: {len(btc):,}") print(btc[["timestamp", "bid_price_0", "ask_price_0"]].head())

Mỗi snapshot là một hàng, cột timestamp là epoch microsecond, các cột bid_price_i/bid_amount_iask_price_i/ask_amount_i mô tả 25 cấp mỗi bên. Từ đây bạn tính mid, spread, imbalance trong vài dòng.

3. Tính các chỉ số microstructure cốt lõi

Ba chỉ số tôi dùng nhiều nhất khi phân tích execution: quoted spread, book imbalance (top-N), và depth ratio. Công thức đơn giản nhưng cực kỳ nhiều thông tin cho mô hình ngắn hạn.

import numpy as np

def micro_features(snap: pd.DataFrame, levels: int = 5) -> pd.DataFrame:
    mid = (snap["bid_price_0"] + snap["ask_price_0"]) / 2
    spread = snap["ask_price_0"] - snap["bid_price_0"]
    rel_spread = spread / mid  # spread tương đối, đơn vị %

    bid_vol = sum(snap[f"bid_amount_{i}"] for i in range(levels))
    ask_vol = sum(snap[f"ask_amount_{i}"] for i in range(levels))
    imbalance = (bid_vol - ask_vol) / (bid_vol + ask_vol)  # [-1, +1]

    # Depth ratio: lệnh đặt càng xa mid thì tác động giá càng yếu
    bid_far = sum(snap[f"bid_amount_{i}"] * (snap[f"bid_price_{i}"] - mid).abs()
                  for i in range(levels))
    ask_far = sum(snap[f"ask_amount_{i}"] * (snap[f"ask_price_{i}"] - mid).abs()
                  for i in range(levels))
    depth_ratio = bid_far / (bid_far + ask_far + 1e-12)

    out = pd.DataFrame({
        "mid": mid,
        "rel_spread_bps": rel_spread * 1e4,
        "imbalance_5": imbalance,
        "depth_ratio_5": depth_ratio,
    })
    return out

features = micro_features(btc)
print(features.describe().round(4))

Một thực nghiệm nhanh trên BTCUSDT ngày 15/03/2024: rel_spread_bps trung vị khoảng 0.8–1.2 bps khi thanh khoản sâu và phình lên 5–15 bps khi có tin FOMC hoặc liquidation cascade. imbalance_5 lệch rõ về phía bid (+0.15 đến +0.30) thường đi trước một đợt pump ngắn 1–3 phút; ngược lại imbalance âm thường đi trước một cú dump. Đây là tín hiệu cổ điển của order flow toxicity.

4. Tính Kyle's lambda và effective spread từ dữ liệu trade

Tardis cũng lưu trữ trades (tick-by-tick). Kết hợp với mid tại thời điểm khớp, bạn suy ra effective spread và ước lượng Kyle's lambda - hệ số price impact cốt lõi trong mô hình của Kyle (1985).

trades = dd.read_csv(
    "s3://tardis-internal-data/binance/trades/2024-03-15/*.csv.gz",
    storage_options={"anon": True},
).compute()
trades = trades[trades["symbol"] == "BTCUSDT"]

Join với mid gần nhất (forward-fill trong khoảng 500ms)

mid = btc[["timestamp", "mid"]].sort_values("timestamp") trades = trades.sort_values("timestamp").merge( mid, on="timestamp", how="left" ).fillna(method="ffill")

Effective spread (giả định mid không đổi trong 1s sau trade)

trades["eff_spread_bps"] = 2 * np.sign( trades["side"].map({"buy": 1, "sell": -1}) ) * (trades["price"] - trades["mid"]).abs() / trades["mid"] * 1e4

Kyle's lambda: hồi quy delta_mid_t -> cumulative signed volume

trades["signed_vol"] = trades["amount"] * trades["side"].map({"buy": 1, "sell": -1}) trades["cum_signed"] = trades["signed_vol"].cumsum() trades["mid_diff"] = trades["mid"].diff()

OLS rolling 1000 trade

window = 1000 slope = [] for i in range(window, len(trades)): x = trades["cum_signed"].iloc[i-window:i].values y = trades["mid_diff"].iloc[i-window:i].values slope.append(np.polyfit(x, y, 1)[0]) trades.loc[trades.index[window:], "kyle_lambda"] = slope print(f"Kyle's lambda trung vị: {trades['kyle_lambda'].median():.2e}") print(f"Effective spread trung vị: {trades['eff_spread_bps'].median():.2f} bps")

Với BTCUSDT, lambda thường vào khoảng 1e-7 đến 1e-6 (đơn vị USD/USDT mỗi BTC giao dịch). Giá trị này càng lớn khi spread giãn - chính là lúc thị trường đang "sợ" dòng tiền lớn, cơ hội cho market maker nhưng bất lợi cho lệnh market của bạn.

5. Replay thời gian thực qua WebSocket

Để backtest chiến lược execution mà vẫn cảm nhận được tốc độ thật, Tardis cung cấp endpoint replay. Tốc độ có thể tùy chỉnh từ 1x đến 400x.

import asyncio, json, websockets, os

API_KEY = os.environ["TARDIS_API_KEY"]

async def replay():
    uri = (
        "wss://replay.tardis.dev/v1/data-feeds/binance-futures"
        "?from=2024-03-15T10:00:00.000Z&to=2024-03-15T10:05:00.000Z"
        "&speed=50"
    )
    headers = {"Authorization": f"Bearer {API_KEY}"}
    async with websockets.connect(uri, extra_headers=headers) as ws:
        # Subscribe các kênh
        await ws.send(json.dumps({
            "op": "subscribe",
            "channel": "incremental_book_L2",
            "symbols": ["btcusdt"]
        }))
        await ws.send(json.dumps({
            "op": "subscribe",
            "channel": "trade",
            "symbols": ["btcusdt"]
        }))
        count = 0
        async for msg in ws:
            data = json.loads(msg)
            if data.get("channel") == "trade":
                count += 1
            if count >= 5:
                break
        print("Replay OK, nhận đủ trade mẫu")

asyncio.run(replay())

Nếu bạn cần sinh nhanh code phân tích (đặc biệt khi thử nghiệm nhiều chỉ số), có thể dùng LLM API làm "pair programmer" - ví dụ gọi qua HolySheep AI với base_urlhttps://api.holysheep.ai/v1 và key YOUR_HOLYSHEEP_API_KEY, model deepseek-v3.2 có giá chỉ $0.42/MTok phù hợp sinh code Python từ prompt tự nhiên mà vẫn giữ chất lượng cao. So với GPT-4.1 ($8/MTok) hay Claude Sonnet 4.5 ($15/MTok), chi phí giảm hơn 90% trong khi độ trễ vẫn dưới 50ms.

Lỗi thường gặp và cách khắc phục

Lỗi 1: botocore.exceptions.NoCredentialsError khi đọc S3

Tardis public bucket cho phép truy cập ẩn danh nhưng nhiều phiên bản smart_open không tự thêm anon=True. Kết quả là AWS SDK cố tìm credentials và báo lỗi.

import smart_open

SAI: smart_open.open("s3://tardis-internal-data/...", "rb")

ĐÚNG:

with smart_open.open( "s3://tardis-internal-data/binance/book_snapshot_25/2024-03-15/btcusdt.csv.gz", "rb", transport_params={"client_kwargs": {"region_name": "eu-central-1"}} ) as f: head = f.read(200) print(head)

Lỗi 2: Memory error khi load cả ngày của nhiều symbol

Một ngày BTCUSDT trên Binance Futures có thể lên tới 30–50 GB incremental L2. Đừng compute() toàn bộ Dask graph cùng lúc. Hãy partition theo giờ hoặc symbol, xử lý tuần tự rồi ghi xuống Parquet.

import dask.dataframe as dd
from dask.diagnostics import ProgressBar

hours = [f"{h:02d}" for h in range(24)]
for h in hours:
    df = dd.read_csv(
        f"s3://tardis-internal-data/binance/trades/2024-03-15/{h}/*.csv.gz",
        storage_options={"anon": True},
    )
    with ProgressBar():
        df[df["symbol"] == "BTCUSDT"].to_parquet(
            f"out/trades_btc_15h{h}.parquet", compression="snappy"
        )

Lỗi 3: WebSocket replay bị disconnect sau vài phút

Endpoint replay của Tardis đóng kết nối nếu không nhận được heartbeat hoặc nếu client chậm xử lý. Bạn cần bọc lại với logic reconnect có backoff và buffer xử lý ngoài hot loop.

import asyncio, websockets, json
from collections import deque

async def robust_replay(api_key, channel="trade", symbol="btcusdt"):
    uri = (
        f"wss://replay.tardis.dev/v1/data-feeds/binance-futures"
        f"?from=2024-03-15T10:00:00Z&to=2024-03-15T11:00:00Z&speed=20"
    )
    backoff = 1
    buf = deque(maxlen=10000)
    while True:
        try:
            async with websockets.connect(
                uri, ping_interval=15, ping_timeout=20,
                extra_headers=[("Authorization", f"Bearer {api_key}")]
            ) as ws:
                await ws.send(json.dumps({
                    "op": "subscribe", "channel": channel, "symbols": [symbol]
                }))
                backoff = 1
                async for msg in ws:
                    buf.append(json.loads(msg))
        except Exception as e:
            print(f"Reconnect sau {backoff}s: {e}")
            await asyncio.sleep(backoff)
            backoff = min(backoff * 2, 30)

Lỗi 4: Spread âm do dữ liệu bị cross

Đôi khi incremental update đến trước khi snapshot cũ được clear, tạo ra bid_price_0 > ask_price_0. Đây là hiện tượng "locked/crossed book". Khi tính spread, phải lọc hoặc đánh dấu.

def safe_spread(row):
    if row["bid_price_0"] >= row["ask_price_0"]:
        return np.nan  # crossed book, bỏ qua
    return (row["ask_price_0"] - row["bid_price_0"]) / (
        (row["ask_price_0"] + row["bid_price_0"]) / 2
    )

btc["spread_safe"] = btc.apply(safe_spread, axis=1)
print(f"Tỷ lệ snapshot bị crossed: {btc['spread_safe'].isna().mean():.4%}")

6. Đọc kết quả và áp dụng vào chiến lược

Sau khi có bảng feature gồm spread, imbalance, kyle_lambda, eff_spread, bạn có thể:

Dữ liệu microstructure chất lượng cao là khác biệt giữa một backtest "đẹp trên giấy" và một chiến lược chịu được thực chiến. Đừng tiếc vài giờ đầu tư clean dữ liệu - nó sẽ tiết kiệm hàng tuần debug sau này.

Lời kết

Tardis là công cụ gần như không thể thay thế nếu bạn nghiên cứu execution hay thanh khoản crypto. Kết hợp với các chỉ số microstructure kinh điển (spread, imbalance, Kyle's lambda, VPIN), bạn sẽ có một nền tảng dữ liệu đủ vững để đưa ra quyết định execution thay vì chỉ đoán. Khi cần sinh nhanh code phân tích hoặc refactor pipeline, HolySheep AI với base_url = https://api.holysheep.ai/v1, hỗ trợ thanh toán WeChat/Alipay, tỷ giá 1¥ = $1 (tiết kiệm hơn 85% so với billing USD), độ trễ dưới 50ms và giá DeepSeek V3.2 chỉ $0.42/MTok là một lựa chọn hợp lý cho workflow kỹ thuật hàng ngày.

👉 Đăng ký HolySheep AI — nhận tín dụng miễn phí khi đăng ký