Mình đã dành 3 tháng làm việc với API của các sàn giao dịch tiền mã hóa, và trải nghiệm đau đớn nhất là khi nhận được lỗi "ConnectionError: timeout after 30000ms" vào lúc 2 giờ sáng khi bot giao dịch của mình ngừng hoạt động. Hôm nay mình sẽ chia sẻ cách subscribe dữ liệu order book từ OKX một cách chính xác, tránh những cạm bẫy thường gặp, và xây dựng một hệ thống real-time data subscription ổn định.

Order Book là gì và tại sao nó quan trọng?

Order Book là danh sách các lệnh mua/bán đang chờ được khớp trên sàn giao dịch. Với trader giao dịch khối lượng lớn hoặc market maker, độ sâu order book (depth of market) cho biết:

OKX cung cấp 3 loại channel order book:

Kết nối WebSocket và Subscribe Order Book

2.1. Cài đặt thư viện và thiết lập project


Tạo virtual environment và cài đặt dependencies

python3 -m venv okx_env source okx_env/bin/activate

Cài đặt websocket-client cho WebSocket connection

pip install websocket-client==0.59.0

Hoặc sử dụng library khác theo preference

pip install websockets==12.0 # Alternative async library

Kiểm tra phiên bản

python3 -c "import websocket; print(websocket.__version__)"

2.2. Subscribe Order Book với WebSocket


import websocket
import json
import time
from datetime import datetime

class OKXOrderBookClient:
    """
    Client subscribe dữ liệu order book từ OKX Exchange
    Documentation: https://www.okx.com/docs-v5/en/#websocket-api
    """
    
    def __init__(self):
        # OKX WebSocket endpoint - Production
        self.wss_url = "wss://ws.okx.com:8443/ws/v5/public"
        # OKX WebSocket endpoint - Demo trading
        # self.wss_url = "wss://wspap.okx.com:8443/ws/v5/public"
        
        self.ws = None
        self.order_book_data = {}
        self.is_connected = False
        self.reconnect_delay = 1
        self.max_reconnect_delay = 30
        
    def connect(self):
        """Thiết lập kết nối WebSocket đến OKX"""
        print(f"[{datetime.now()}] Đang kết nối đến OKX...")
        
        self.ws = websocket.WebSocketApp(
            self.wss_url,
            on_message=self._on_message,
            on_error=self._on_error,
            on_close=self._on_close,
            on_open=self._on_open
        )
        
        # run_forever() blocking - chạy trong thread riêng nếu cần
        self.ws.run_forever(ping_interval=25, ping_timeout=10)
        
    def _on_open(self, ws):
        """Callback khi kết nối thành công"""
        self.is_connected = True
        self.reconnect_delay = 1
        print(f"[{datetime.now()}] ✓ Kết nối WebSocket thành công!")
        
        # Subscribe order book BTC-USDT với depth 400 levels
        subscribe_message = {
            "op": "subscribe",
            "args": [
                {
                    "channel": "books",
                    "instId": "BTC-USDT",
                    "instType": "SPOT"
                }
            ]
        }
        ws.send(json.dumps(subscribe_message))
        print(f"[{datetime.now()}] Đã gửi subscribe request cho BTC-USDT")
        
    def _on_message(self, ws, message):
        """Xử lý tin nhắn từ OKX server"""
        try:
            data = json.loads(message)
            
            # OKX gửi heartbeat định kỳ
            if message == "pong":
                print(f"[{datetime.now()}] Heartbeat received")
                return
                
            # Xử lý event subscription
            if "event" in data:
                if data["event"] == "subscribe":
                    print(f"[{datetime.now()}] ✓ Subscription thành công: {data}")
                elif data["event"] == "error":
                    print(f"[{datetime.now()}] ✗ Subscription lỗi: {data}")
                return
                
            # Xử lý dữ liệu order book
            if "data" in data and len(data["data"]) > 0:
                ob = data["data"][0]
                
                # Trích xất thông tin order book
                self.order_book_data = {
                    "timestamp": ob["ts"],
                    "asks": ob["asks"],  # Format: [[price, size, decimal], ...]
                    "bids": ob["bids"],
                    "msg_id": ob.get("id", "N/A")
                }
                
                # Tính toán thông tin quan trọng
                best_ask = float(ob["asks"][0][0]) if ob["asks"] else 0
                best_bid = float(ob["bids"][0][0]) if ob["bids"] else 0
                spread = best_ask - best_bid
                spread_pct = (spread / best_bid * 100) if best_bid > 0 else 0
                
                print(f"[{datetime.now()}] Order Book: Best Ask={best_ask:.2f}, "
                      f"Best Bid={best_bid:.2f}, Spread={spread:.2f} ({spread_pct:.4f}%)")
                
        except json.JSONDecodeError as e:
            print(f"Lỗi parse JSON: {e}")
        except Exception as e:
            print(f"Lỗi xử lý message: {e}")
            
    def _on_error(self, ws, error):
        """Xử lý lỗi WebSocket"""
        print(f"[{datetime.now()}] ✗ WebSocket Error: {error}")
        
    def _on_close(self, ws, close_status_code, close_msg):
        """Callback khi kết nối đóng"""
        self.is_connected = False
        print(f"[{datetime.now()}] Kết nối đóng: {close_status_code} - {close_msg}")
        self._attempt_reconnect()
        
    def _attempt_reconnect(self):
        """Tự động reconnect với exponential backoff"""
        print(f"[{datetime.now()}] Đang chuẩn bị reconnect sau {self.reconnect_delay}s...")
        time.sleep(self.reconnect_delay)
        self.reconnect_delay = min(self.reconnect_delay * 2, self.max_reconnect_delay)
        self.connect()

if __name__ == "__main__":
    client = OKXOrderBookClient()
    try:
        client.connect()
    except KeyboardInterrupt:
        print("\nCtrl+C detected - Đang dừng client...")
        if client.ws:
            client.ws.close()

2.3. Chạy và kiểm tra


Chạy script

python3 okx_orderbook_basic.py

Output mong đợi:

[2026-01-15 10:30:00.123] Đang kết nối đến OKX...

[2026-01-15 10:30:00.456] ✓ Kết nối WebSocket thành công!

[2026-01-15 10:30:00.789] Đã gửi subscribe request cho BTC-USDT

[2026-01-15 10:30:01.012] ✓ Subscription thành công: {'event': 'subscribe', 'arg': {...}}

[2026-01-15 10:30:01.234] Order Book: Best Ask=42350.50, Best Bid=42348.20, Spread=2.30 (0.0054%)

[2026-01-15 10:30:01.456] Order Book: Best Ask=42351.00, Best Bid=42348.50, Spread=2.50 (0.0059%)

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

3.1. Lỗi "401 Unauthorized" - Subscription Fail

Mô tả lỗi: Khi subscribe, nhận được response lỗi 401 Unauthorized


Response lỗi từ OKX

{ "event": "error", "msg": "Illegal request: verify timestamp failed", "code": "20004" }

Nguyên nhân:

1. Timestamp request không chính xác

2. Request signature không đúng (nếu dùng private channel)

3. Parameter format không đúng

Giải pháp:

def validate_subscription_params(inst_id, channel): """Validate tham số trước khi subscribe""" # Danh sách các cặp tiền hợp lệ trên OKX SPOT valid_spot_pairs = [ "BTC-USDT", "ETH-USDT", "SOL-USDT", "XRP-USDT", "DOGE-USDT", "ADA-USDT", "AVAX-USDT", "DOT-USDT" ] # Danh sách perpetual futures pairs valid_perp_pairs = [ "BTC-USDT-SWAP", "ETH-USDT-SWAP", "SOL-USDT-SWAP" ] all_valid = valid_spot_pairs + valid_perp_pairs if inst_id not in all_valid: raise ValueError( f"InstID '{inst_id}'