Chào bạn đọc! Mình là Minh, chuyên gia về dữ liệu thị trường crypto và đã làm việc với API của hơn 10 sàn giao dịch lớn. Hôm nay mình sẽ chia sẻ chi tiết cách kết nối Bybit Futures API để lấy dữ liệu đối soát giao dịch (tick-by-tick trades) và khôi phục sổ lệnh (order book reconstruction) — hai nguồn dữ liệu quan trọng nhất cho việc phân tích kỹ thuật và xây dựng bot giao dịch.

Lưu ý quan trọng: Bài viết này tập trung vào public endpoints — không cần API key cũng có thể thử nghiệm được. Nếu bạn cần xử lý dữ liệu AI nhanh hơn (phân tích pattern, dự đoán xu hướng), mình khuyên dùng HolySheep AI với độ trễ dưới 50ms và chi phí chỉ từ $0.42/1M tokens.

Mục Lục

1. Giới Thiệu Về Dữ Liệu Bybit Futures

Bybit là một trong những sàn perpetual futures lớn nhất thế giới với khối lượng giao dịch hàng tỷ USD mỗi ngày. API của họ cung cấp hai loại dữ liệu chính:

🤔 Gợi ý ảnh chụp màn hình: Chụp giao diện Bybit Futures, highlight vùng "Recent Trades" và "Order Book" để người đọc hình dung.

2. Thiết Lập Môi Trường Làm Việc

Trước khi bắt đầu, bạn cần cài đặt Python và thư viện cần thiết. Đây là bước quan trọng nhất cho người mới — đừng bỏ qua nhé!

2.1 Cài Đặt Python

Nếu bạn chưa có Python, hãy tải tại python.org. Mình khuyên dùng Python 3.9 trở lên.

2.2 Tạo Virtual Environment (Khuyến nghị)

# Tạo thư mục dự án
mkdir bybit_api_tutorial
cd bybit_api_tutorial

Tạo môi trường ảo (tránh xung đột thư viện)

python -m venv venv

Kích hoạt môi trường ảo

Windows:

venv\Scripts\activate

Mac/Linux:

source venv/bin/activate

Cài đặt các thư viện cần thiết

pip install requests pandas websocket-client asyncio aiohttp

2.3 Cấu Trúc Thư Mục Dự Án

bybit_api_tutorial/
├── config.py          # Cấu hình API
├── public_api.py      # Các hàm lấy dữ liệu public
├── websocket_api.py   # Kết nối real-time
├── orderbook.py       # Xử lý order book
├── main.py            # Chương trình chính
├── requirements.txt   # Danh sách thư viện
└── data/              # Thư mục lưu dữ liệu
    ├── trades/
    └── orderbook/

3. Lấy Dữ Liệu Lịch Sử Giao Dịch (Trade History)

Đây là phần quan trọng nhất — mình sẽ hướng dẫn từng dòng code để bạn hiểu rõ.

3.1 Tạo File Cấu Hình

# config.py
import os

Cấu hình Bybit API (Public - không cần key)

BYBIT_BASE_URL = "https://api.bybit.com" SYMBOL = "BTCUSDT" # Cặp giao dịch CATEGORY = "linear" # linear = USDT perpetual futures

Cấu hình HolySheep AI (nếu cần xử lý AI)

Đăng ký tại: https://www.holysheep.ai/register

HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1" HOLYSHEEP_API_KEY = os.environ.get("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")

Thư mục lưu dữ liệu

DATA_DIR = "data" os.makedirs(f"{DATA_DIR}/trades", exist_ok=True) os.makedirs(f"{DATA_DIR}/orderbook", exist_ok=True)

3.2 Hàm Lấy Dữ Liệu Trade History

# public_api.py
import requests
import pandas as pd
import time
from datetime import datetime
import config

def get_recent_trades(symbol: str = config.SYMBOL, limit: int = 100):
    """
    Lấy dữ liệu giao dịch gần đây từ Bybit
    
    Parameters:
        symbol: Cặp giao dịch (VD: BTCUSDT)
        limit: Số lượng giao dịch cần lấy (tối đa 1000)
    
    Returns:
        DataFrame chứa dữ liệu giao dịch
    """
    endpoint = f"{config.BYBIT_BASE_URL}/v5/market/recent-trade"
    
    params = {
        "category": config.CATEGORY,
        "symbol": symbol,
        "limit": limit
    }
    
    try:
        response = requests.get(endpoint, params=params, timeout=10)
        response.raise_for_status()
        
        data = response.json()
        
        if data["retCode"] == 0:
            trades = data["result"]["list"]
            
            # Chuyển đổi sang DataFrame để dễ xử lý
            df = pd.DataFrame(trades)
            
            # Đổi tên cột cho dễ hiểu
            df.columns = [
                "trade_id",      # ID giao dịch
                "trade_time_ms", # Thời gian (milliseconds)
                "symbol",        # Cặp giao dịch
                "price",         # Giá giao dịch
                "size",          # Khối lượng
                "side",          # Hướng: Buy hoặc Sell
            ]
            
            # Chuyển đổi thời gian
            df["trade_time"] = pd.to_datetime(
                df["trade_time_ms"].astype(float), unit="ms"
            )
            
            # Sắp xếp theo thời gian tăng dần
            df = df.sort_values("trade_time").reset_index(drop=True)
            
            # Chuyển đổi kiểu dữ liệu
            df["price"] = df["price"].astype(float)
            df["size"] = df["size"].astype(float)
            
            print(f"✅ Đã lấy {len(df)} giao dịch gần nhất của {symbol}")
            return df
            
        else:
            print(f"❌ Lỗi API: {data['retMsg']}")
            return None
            
    except requests.exceptions.RequestException as e:
        print(f"❌ Lỗi kết nối: {e}")
        return None

def get_historical_trades(symbol: str, start_time: int, end_time: int):
    """
    Lấy dữ liệu giao dịch theo khoảng thời gian
    
    Parameters:
        symbol: Cặp giao dịch
        start_time: Thời gian bắt đầu (milliseconds)
        end_time: Thời gian kết thúc (milliseconds)
    """
    endpoint = f"{config.BYBIT_BASE_URL}/v5/market/history-trade"
    
    all_trades = []
    current_time = start_time
    
    while current_time < end_time:
        params = {
            "category": config.CATEGORY,
            "symbol": symbol,
            "startTime": current_time,
            "limit": 1000
        }
        
        response = requests.get(endpoint, params=params, timeout=10)
        data = response.json()
        
        if data["retCode"] == 0:
            trades = data["result"]["list"]
            all_trades.extend(trades)
            
            if len(trades) < 1000:
                break
            
            # Lấy thời gian của giao dịch cuối cùng + 1ms
            current_time = int(trades[-1]["tradeTime"]) + 1
            
        else:
            print(f"❌ Lỗi: {data['retMsg']}")
            break
    
    print(f"✅ Đã lấy tổng cộng {len(all_trades)} giao dịch")
    return all_trades

Test nhanh

if __name__ == "__main__": df = get_recent_trades(limit=10) if df is not None: print(df.head())

🤔 Gợi ý ảnh chụp màn hình: Chụp output khi chạy script, highlight các cột dữ liệu quan trọng.

4. Reconstruction Order Book (重建订单簿)

Đây là kỹ thuật nâng cao — ghép các giao dịch riêng lẻ thành snapshot của sổ lệnh tại bất kỳ thời điểm nào. Rất hữu ích cho backtesting!

4.1 Lấy Order Book Snapshot

# orderbook.py
import requests
import pandas as pd
from typing import Dict, List, Tuple
from dataclasses import dataclass
from datetime import datetime

@dataclass
class OrderBookLevel:
    """Một mức giá trong sổ lệnh"""
    price: float
    size: float

class OrderBook:
    """Class quản lý order book"""
    
    def __init__(self):
        self.bids: Dict[float, float] = {}  # {price: size} cho lệnh mua
        self.asks: Dict[float, float] = {}  # {price: size} cho lệnh bán
    
    def update_bid(self, price: float, size: float):
        """Cập nhật lệnh mua"""
        if size == 0:
            self.bids.pop(price, None)
        else:
            self.bids[price] = size
    
    def update_ask(self, price: float, size: float):
        """Cập nhật lệnh bán"""
        if size == 0:
            self.asks.pop(price, None)
        else:
            self.asks[price] = size
    
    def get_best_bid(self) -> Tuple[float, float]:
        """Lấy giá mua tốt nhất (bid cao nhất)"""
        if not self.bids:
            return 0, 0
        best_price = max(self.bids.keys())
        return best_price, self.bids[best_price]
    
    def get_best_ask(self) -> Tuple[float, float]:
        """Lấy giá bán tốt nhất (ask thấp nhất)"""
        if not self.asks:
            return 0, 0
        best_price = min(self.asks.keys())
        return best_price, self.asks[best_price]
    
    def get_spread(self) -> float:
        """Tính spread (chênh lệch giá mua-bán)"""
        best_bid, _ = self.get_best_bid()
        best_ask, _ = self.get_best_ask()
        if best_bid and best_ask:
            return best_ask - best_bid
        return 0
    
    def get_mid_price(self) -> float:
        """Giá giữa thị trường"""
        best_bid, _ = self.get_best_bid()
        best_ask, _ = self.get_best_ask()
        if best_bid and best_ask:
            return (best_bid + best_ask) / 2
        return 0
    
    def get_depth(self, levels: int = 10) -> pd.DataFrame:
        """Lấy độ sâu thị trường"""
        # Lấy N mức giá tốt nhất
        top_bids = sorted(self.bids.items(), reverse=True)[:levels]
        top_asks = sorted(self.asks.items())[:levels]
        
        # Tạo DataFrame
        bids_df = pd.DataFrame(top_bids, columns=["bid_price", "bid_size"])
        asks_df = pd.DataFrame(top_asks, columns=["ask_price", "ask_size"])
        
        # Thêm cột cumulative
        bids_df["bid_cumulative"] = bids_df["bid_size"].cumsum()
        asks_df["ask_cumulative"] = asks_df["ask_size"].cumsum()
        
        return pd.concat([bids_df, asks_df], axis=1)
    
    def to_dict(self) -> dict:
        """Chuyển sang dictionary để lưu"""
        return {
            "timestamp": datetime.now().isoformat(),
            "bids": dict(sorted(self.bids.items(), reverse=True)),
            "asks": dict(sorted(self.asks.items())),
            "best_bid": self.get_best_bid()[0],
            "best_ask": self.get_best_ask()[0],
            "spread": self.get_spread(),
            "mid_price": self.get_mid_price()
        }

def get_order_book_snapshot(symbol: str, limit: int = 50) -> OrderBook:
    """
    Lấy snapshot order book hiện tại từ Bybit
    
    Parameters:
        symbol: Cặp giao dịch
        limit: Số lượng mức giá (tối đa 200)
    """
    endpoint = f"{config.BYBIT_BASE_URL}/v5/market/orderbook"
    
    params = {
        "category": config.CATEGORY,
        "symbol": symbol,
        "limit": limit
    }
    
    try:
        response = requests.get(endpoint, params=params, timeout=10)
        response.raise_for_status()
        
        data = response.json()
        
        if data["retCode"] == 0:
            result = data["result"]
            
            ob = OrderBook()
            
            # Cập nhật bids
            for price, size in zip(result["b"], result["bs"]):
                ob.update_bid(float(price), float(size))
            
            # Cập nhật asks
            for price, size in zip(result["a"], result["as"]):
                ob.update_ask(float(price), float(size))
            
            print(f"✅ Đã lấy order book: Spread = {ob.get_spread():.2f}")
            return ob
            
        else:
            print(f"❌ Lỗi API: {data['retMsg']}")
            return None
            
    except Exception as e:
        print(f"❌ Lỗi: {e}")
        return None

4.2 Reconstruction Từ Trade Data

Đây là phần "magic" — mình sẽ hướng dẫn cách reconstruct order book từ các giao dịch. Kỹ thuật này dùng khi bạn có dữ liệu trade history và muốn biết trạng thái sổ lệnh tại thời điểm bất kỳ.

def reconstruct_order_book_from_trades(
    trades: List[dict],
    initial_book: OrderBook = None
) -> OrderBook:
    """
    Khôi phục order book từ danh sách giao dịch
    
    Nguyên lý: Mỗi giao dịch sẽ thay đổi order book:
    - Nếu trade có side="Buy" → lệnh market mua → khớp với ask
    - Nếu trade có side="Sell" → lệnh market bán → khớp với bid
    
    Parameters:
        trades: Danh sách giao dịch (đã sắp xếp theo thời gian)
        initial_book: Order book ban đầu (nếu có)
    
    Returns:
        OrderBook đã được cập nhật
    """
    if initial_book is None:
        ob = OrderBook()
    else:
        ob = initial_book
    
    for trade in trades:
        price = float(trade["price"])
        size = float(trade["size"])
        side = trade["side"].lower()
        
        # Xác định order book level bị ảnh hưởng
        # Quan trọng: Đây là DEMO, thực tế cần xử lý phức tạp hơn
        if side == "buy":
            # Buyer taker → khớp với ask (giảm ask size)
            if price in ob.asks:
                new_size = ob.asks[price] - size
                ob.update_ask(price, max(0, new_size))
            else:
                # Tạo bid mới (limit buy order)
                ob.update_bid(price, size)
                
        else:  # side == "sell"
            # Seller taker → khớp với bid (giảm bid size)
            if price in ob.bids:
                new_size = ob.bids[price] - size
                ob.update_bid(price, max(0, new_size))
            else:
                # Tạo ask mới (limit sell order)
                ob.update_ask(price, size)
    
    return ob

def analyze_trade_flow(trades_df: pd.DataFrame) -> dict:
    """
    Phân tích dòng tiền từ dữ liệu giao dịch
    
    Returns thông tin quan trọng cho trading:
    - Buy/Sell ratio
    - Volume-weighted average price (VWAP)
    - Tổng khối lượng buy/sell
    """
    trades_df["value"] = trades_df["price"] * trades_df["size"]
    
    buy_trades = trades_df[trades_df["side"] == "Buy"]
    sell_trades = trades_df[trades_df["side"] == "Sell"]
    
    analysis = {
        "total_trades": len(trades_df),
        "buy_count": len(buy_trades),
        "sell_count": len(sell_trades),
        "buy_ratio": len(buy_trades) / len(trades_df) if len(trades_df) > 0 else 0,
        "buy_volume": buy_trades["size"].sum(),
        "sell_volume": sell_trades["size"].sum(),
        "total_volume": trades_df["size"].sum(),
        "volume_buy_ratio": buy_trades["size"].sum() / trades_df["size"].sum() if trades_df["size"].sum() > 0 else 0,
        "vwap": trades_df["value"].sum() / trades_df["size"].sum() if trades_df["size"].sum() > 0 else 0,
        "price_range": {
            "min": trades_df["price"].min(),
            "max": trades_df["price"].max(),
            "open": trades_df["price"].iloc[0],
            "close": trades_df["price"].iloc[-1]
        }
    }
    
    return analysis

⚠️ Lưu ý quan trọng: Việc reconstruction order book từ trade data chỉ cho kết quả ước lượng. Để chính xác 100%, bạn cần có full order book history (dữ liệu mà sàn không public). Tuy nhiên, với mục đích backtesting, độ chính xác này là chấp nhận được.

5. Chương Trình Hoàn Chỉnh

Đây là script main.py kết hợp tất cả các phần trên — bạn có thể chạy ngay!

# main.py
import time
import json
import pandas as pd
from datetime import datetime, timedelta

import config
from public_api import get_recent_trades, get_historical_trades
from orderbook import get_order_book_snapshot, OrderBook, reconstruct_order_book_from_trades, analyze_trade_flow

def main():
    """Chương trình chính - Demo đầy đủ chức năng"""
    
    print("=" * 60)
    print("BYBIT FUTURES API - DEMO")
    print("=" * 60)
    
    # 1. Lấy dữ liệu trade gần đây
    print("\n📊 Bước 1: Lấy dữ liệu giao dịch gần đây...")
    trades_df = get_recent_trades(symbol="BTCUSDT", limit=100)
    
    if trades_df is not None:
        # Phân tích dòng tiền
        analysis = analyze_trade_flow(trades_df)
        print(f"\n📈 Phân tích:")
        print(f"   - Tổng giao dịch: {analysis['total_trades']}")
        print(f"   - Buy/Sell ratio: {analysis['buy_ratio']:.2%}")
        print(f"   - Khối lượng mua: {analysis['buy_volume']:.4f} BTC")
        print(f"   - Khối lượng bán: {analysis['sell_volume']:.4f} BTC")
        print(f"   - VWAP: ${analysis['vwap']:,.2f}")
        print(f"   - Biên độ giá: ${analysis['price_range']['min']:,.2f} - ${analysis['price_range']['max']:,.2f}")
        
        # Lưu vào file
        filename = f"{config.DATA_DIR}/trades/{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
        trades_df.to_csv(filename, index=False)
        print(f"💾 Đã lưu: {filename}")
    
    # 2. Lấy order book snapshot
    print("\n📋 Bước 2: Lấy Order Book...")
    ob = get_order_book_snapshot(symbol="BTCUSDT", limit=20)
    
    if ob is not None:
        best_bid, bid_size = ob.get_best_bid()
        best_ask, ask_size = ob.get_best_ask()
        
        print(f"\n💹 Sổ lệnh BTCUSDT:")
        print(f"   Bid: ${best_bid:,.2f} (Size: {bid_size:.4f})")
        print(f"   Ask: ${best_ask:,.2f} (Size: {ask_size:.4f})")
        print(f"   Spread: ${ob.get_spread():.2f}")
        print(f"   Mid Price: ${ob.get_mid_price():,.2f}")
        
        # Hiển thị depth
        print("\n📊 Độ sâu thị trường (top 5):")
        depth = ob.get_depth(levels=5)
        print(depth.to_string(index=False))
        
        # Lưu order book
        ob_data = ob.to_dict()
        filename = f"{config.DATA_DIR}/orderbook/{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        with open(filename, "w") as f:
            json.dump(ob_data, f, indent=2)
        print(f"💾 Đã lưu: {filename}")
    
    # 3. Demo reconstruction (tùy chọn)
    print("\n🔄 Bước 3: Demo Order Book Reconstruction...")
    print("(Sử dụng 20 giao dịch đầu tiên để demo)")
    
    if trades_df is not None:
        # Lấy 20 giao dịch đầu
        sample_trades = trades_df.head(20).to_dict("records")
        
        # Bắt đầu với order book hiện tại
        reconstructed = reconstruct_order_book_from_trades(sample_trades)
        
        print(f"\n📊 Sau khi reconstruction:")
        print(f"   Số lượng bid levels: {len(reconstructed.bids)}")
        print(f"   Số lượng ask levels: {len(reconstructed.asks)}")
    
    print("\n" + "=" * 60)
    print("✅ Hoàn thành!")
    print("=" * 60)

if __name__ == "__main__":
    main()

Cách chạy:

# Kích hoạt môi trường ảo (nếu chưa làm)

Windows:

venv\Scripts\activate

Linux/Mac:

source venv/bin/activate

Chạy chương trình

python main.py

🤔 Gợi ý ảnh chụp màn hình: Chụp terminal output khi script chạy thành công, highlight các số liệu quan trọng.

6. Lỗi Thường Gặp Và Cách Khắc Phục

Qua quá trình làm việc với API, mình đã gặp nhiều lỗi phổ biến. Dưới đây là tổng hợp cách xử lý:

Lỗi Nguyên nhân Cách khắc phục
retCode: 10001 - Permission denied Dùng private endpoint mà không có API key hoặc key không có quyền
# Kiểm tra lại cấu hình API key
import os
print("API Key:", os.environ.get("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY"))

Đảm bảo key có quyền: Read Market Data

requests.exceptions.ReadTimeout Server phản hồi chậm hoặc mạng không ổn định
# Tăng timeout hoặc thử lại
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = requests.Session()
retry = Retry(connect=3, backoff_factor=0.5)
adapter = HTTPAdapter(max_retries=retry)
session.mount('https://', adapter)

Sử dụng session thay vì requests trực tiếp

response = session.get(url, timeout=30)
JSON decode error API trả về HTML (thường là trang lỗi 403/429) hoặc rate limit
# Kiểm tra response trước khi decode
response = requests.get(url)
print(f"Status: {response.status_code}")
print(f"Headers: {response.headers}")

if response.status_code == 429:
    print("⏳ Rate limit - chờ 60 giây...")
    time.sleep(60)
    response = requests.get(url)

Chỉ decode nếu là JSON

if 'application/json' in response.headers.get('content-type', ''): data = response.json() else: print(f"⚠️ Response không phải JSON: {response.text[:200]}")
Order book reconstruction không chính xác Thiếu dữ liệu order book ban đầu hoặc trades không đầy đủ
# Luôn bắt đầu với snapshot mới nhất trước khi reconstruct
current_ob = get_order_book_snapshot("BTCUSDT", limit=200)

Chỉ reconstruct các giao dịch SAU snapshot đó

VD: trades từ last_snapshot_time đến now

recent_trades = get_historical_trades( symbol="BTCUSDT", start_time=last_snapshot_time, end_time=current_time )

Reconstruct

final_ob = reconstruct_order_book_from_trades(recent_trades, current_ob)

Mẹo Tối Ưu Hóa Khi Làm Việc Với Dữ Liệu Lớn

# Tip 1: Sử dụng async để lấy nhiều symbol cùng lúc
import asyncio
import aiohttp

async def fetch_multiple_symbols(symbols: list):
    """Lấy dữ liệu nhiều symbol song song"""
    async with aiohttp.ClientSession() as session:
        tasks = []
        for symbol in symbols:
            url = f"https://api.bybit.com/v5/market/recent-trade?category=linear&symbol={symbol}&limit=50"
            tasks.append(fetch_trade(session, url))
        
        results = await asyncio.gather(*tasks)
        return results

Tip 2: Lưu data vào SQLite để truy vấn nhanh

import sqlite3 def save_to_sqlite(trades_df, table_name="trades"): conn = sqlite3.connect("trades.db") trades_df.to_sql(table_name, conn, if_exists="append", index=False) conn.close()

Tip 3: Sử dụng Pandas efficiently

Đọc chunk thay vì toàn bộ

for chunk in pd.read_csv("large_file.csv", chunksize=10000): process(chunk) # Xử lý từng phần

7. So Sánh Giải Pháp - Bảng Giá

Nếu bạn cần xử lý dữ liệu