บทนำ

สำหรับนักพัฒนาที่ต้องการรับข้อมูลความลึกของตลาด (Order Book Depth) ของ Bitcoin แบบ Real-time การใช้งาน WebSocket ของ OKX เป็นอีกทางเลือกที่น่าสนใจ เพราะให้ข้อมูลที่รวดเร็วและครอบคลุม ในบทความนี้เราจะมาดูวิธีการเชื่อมต่อ รับข้อมูล Depth และ Parse ข้อมูลอย่างถูกต้องด้วย Python พร้อมทั้งเปรียบเทียบกับบริการ API อื่นๆ ก่อนเริ่ม หากคุณกำลังมองหาทางเลือกที่มีประสิทธิภาพสูงและประหยัดกว่า ลองพิจารณา สมัครที่นี่ เพื่อรับเครดิตฟรีเมื่อลงทะเบียน

WebSocket vs REST API สำหรับข้อมูลตลาด Crypto

เกณฑ์WebSocketREST APIHolySheep AI
ความเร็วในการตอบสนอง<50ms100-500ms<50ms
ความถี่ในการอัปเดตReal-time (ทุก Tick)Polling แบบ IntervalReal-time ผ่าน Webhook
การใช้ Bandwidthต่ำ (Persistent Connection)สูง (Request/Response ทุกครั้ง)ต่ำ
ความซับซ้อนในการตั้งค่าปานกลางต่ำต่ำ
ค่าบริการฟรี (OKX)ฟรี (บางเว็บ)เริ่มต้น $2.50/MTok
ปริมาณข้อมูลที่รองรับไม่จำกัดRate Limit ต่ำปรับขนาดได้

วิธีการเชื่อมต่อ OKX WebSocket สำหรับ BTC Depth Data

1. ติดตั้ง Library ที่จำเป็น

pip install websocket-client pandas numpy

2. โค้ด Python สำหรับเชื่อมต่อ OKX WebSocket

import json
import websocket
import pandas as pd
from datetime import datetime

class OKXDepthReader:
    def __init__(self, symbol="BTC-USDT"):
        self.symbol = symbol
        self.bids = []
        self.asks = []
        self.ws = None
        
    def on_message(self, ws, message):
        """รับข้อความ WebSocket และ Parse ข้อมูล Depth"""
        data = json.loads(message)
        
        if data.get("arg", {}).get("channel") == "books5":
            # ข้อมูล Order Book 5 ระดับ
            if "data" in data and data["data"]:
                depth_data = data["data"][0]
                
                # Parse Bids (คำสั่งซื้อ)
                self.bids = []
                for price, size, _ in depth_data.get("bids", []):
                    self.bids.append({
                        "price": float(price),
                        "size": float(size),
                        "total_value": float(price) * float(size)
                    })
                
                # Parse Asks (คำสั่งขาย)
                self.asks = []
                for price, size, _ in depth_data.get("asks", []):
                    self.asks.append({
                        "price": float(price),
                        "size": float(size),
                        "total_value": float(price) * float(size)
                    })
                
                # แสดงผล Order Book
                self.print_depth()
    
    def print_depth(self):
        """แสดงผล Order Book แบบจัดรูปแบบ"""
        print(f"\n{'='*60}")
        print(f"OKX BTC-USDT Depth - {datetime.now().strftime('%H:%M:%S.%f')}")
        print(f"{'='*60}")
        
        # รวมยอดคำสั่งซื้อ
        total_bid_value = sum(b["total_value"] for b in self.bids)
        total_ask_value = sum(a["total_value"] for a in self.asks)
        
        print(f"รวมคำสั่งซื้อ (Bids): ${total_bid_value:,.2f}")
        print(f"รวมคำสั่งขาย (Asks): ${total_ask_value:,.2f}")
        print(f"Spread: {self.asks[0]['price'] - self.bids[0]['price']:.2f} USDT")
        
        print(f"\n{'Price':>15} | {'Size':>15} | {'Value':>20}")
        print("-" * 55)
        
        for i in range(min(5, len(self.asks))):
            a = self.asks[-(i+1)]
            print(f"{a['price']:>15,.2f} | {a['size']:>15.6f} | ${a['total_value']:>18,.2f}")
        
        print("-" * 55)
        
        for b in self.bids[:5]:
            print(f"{b['price']:>15,.2f} | {b['size']:>15.6f} | ${b['total_value']:>18,.2f}")
    
    def on_error(self, ws, error):
        print(f"เกิดข้อผิดพลาด: {error}")
    
    def on_close(self, ws, close_status_code, close_msg):
        print("การเชื่อมต่อ WebSocket ถูกปิด")
    
    def on_open(self, ws):
        """ส่งคำสั่ง Subscribe เมื่อเปิดการเชื่อมต่อ"""
        subscribe_msg = {
            "op": "subscribe",
            "args": [{
                "channel": "books5",
                "instId": self.symbol
            }]
        }
        ws.send(json.dumps(subscribe_msg))
        print(f"สมัครรับข้อมูล Depth สำหรับ {self.symbol}")
    
    def connect(self):
        """เริ่มการเชื่อมต่อ WebSocket"""
        self.ws = websocket.WebSocketApp(
            "wss://ws.okx.com:8443/ws/v5/public",
            on_message=self.on_message,
            on_error=self.on_error,
            on_close=self.on_close,
            on_open=self.on_open
        )
        self.ws.run_forever()

เริ่มการทำงาน

if __name__ == "__main__": reader = OKXDepthReader("BTC-USDT") reader.connect()

3. ปรับปรุงโค้ดให้รองรับ Order Book หลายระดับ

import json
import websocket
import pandas as pd
from collections import defaultdict
import threading
import time

class BTCDepthAnalyzer:
    """
    เครื่องมือวิเคราะห์ Order Book ของ BTC/USDT จาก OKX WebSocket
    รองรับการติดตามหลายระดับของ Order Book
    """
    
    def __init__(self, symbol="BTC-USDT", levels=25):
        self.symbol = symbol
        self.levels = levels
        self.order_book = {"bids": {}, "asks": {}}
        self.lock = threading.Lock()
        self.ws = None
        self.is_running = False
        
        # สถิติการอัปเดต
        self.update_count = 0
        self.last_update_time = time.time()
        self.update_rate = 0
    
    def parse_depth_snapshot(self, data):
        """Parse ข้อมูล Depth Snapshot จาก OKX"""
        for side in ["bids", "asks"]:
            orders = data.get(side, [])
            with self.lock:
                self.order_book[side] = {}
                for order in orders[:self.levels]:
                    price = float(order[0])
                    size = float(order[1])
                    self.order_book[side][price] = size
        
        self.update_stats()
    
    def update_stats(self):
        """อัปเดตสถิติการรับข้อมูล"""
        self.update_count += 1
        current_time = time.time()
        time_diff = current_time - self.last_update_time
        
        if time_diff >= 1.0:
            self.update_rate = self.update_count / time_diff
            self.update_count = 0
            self.last_update_time = current_time
    
    def get_depth_summary(self):
        """สรุปข้อมูล Order Book"""
        with self.lock:
            bids_df = pd.DataFrame([
                {"price": p, "size": s, "side": "bid", "value": p * s}
                for p, s in self.order_book["bids"].items()
            ])
            
            asks_df = pd.DataFrame([
                {"price": p, "size": s, "side": "ask", "value": p * s}
                for p, s in self.order_book["asks"].items()
            ])
        
        if bids_df.empty or asks_df.empty:
            return None
        
        bids_df = bids_df.sort_values("price", ascending=False)
        asks_df = asks_df.sort_values("price")
        
        total_bid_value = bids_df["value"].sum()
        total_ask_value = asks_df["value"].sum()
        
        best_bid = bids_df["price"].max()
        best_ask = asks_df["price"].min()
        spread = best_ask - best_bid
        spread_pct = (spread / best_bid) * 100
        
        return {
            "best_bid": best_bid,
            "best_ask": best_ask,
            "spread": spread,
            "spread_pct": spread_pct,
            "total_bid_value": total_bid_value,
            "total_ask_value": total_ask_value,
            "bid_ask_ratio": total_bid_value / total_ask_value if total_ask_value > 0 else 0,
            "update_rate": self.update_rate,
            "bids_df": bids_df,
            "asks_df": asks_df
        }
    
    def calculate_wall_positions(self, threshold_pct=0.05):
        """
        คำนวณตำแหน่งของ Order Walls (คำสั่งขนาดใหญ่)
        threshold_pct: เปอร์เซ็นต์ของขนาดเฉลี่ยที่ถือว่าเป็น Wall
        """
        summary = self.get_depth_summary()
        if summary is None:
            return []
        
        all_sizes = pd.concat([summary["bids_df"]["size"], summary["asks_df"]["size"]])
        avg_size = all_sizes.mean()
        threshold = avg_size * (1 + threshold_pct * 10)
        
        walls = []
        
        for _, row in summary["bids_df"].iterrows():
            if row["size"] >= threshold:
                walls.append({
                    "price": row["price"],
                    "size": row["size"],
                    "side": "bid",
                    "strength": row["size"] / avg_size
                })
        
        for _, row in summary["asks_df"].iterrows():
            if row["size"] >= threshold:
                walls.append({
                    "price": row["price"],
                    "size": row["size"],
                    "side": "ask",
                    "strength": row["size"] / avg_size
                })
        
        return sorted(walls, key=lambda x: x["strength"], reverse=True)
    
    def on_message(self, ws, message):
        """รับและประมวลผลข้อความจาก OKX WebSocket"""
        try:
            data = json.loads(message)
            
            if "data" in data and data["data"]:
                for depth_data in data["data"]:
                    if depth_data.get("action") == "snapshot":
                        self.parse_depth_snapshot(depth_data)
                    elif depth_data.get("action") == "update":
                        self.apply_delta_update(depth_data)
        except Exception as e:
            print(f"ข้อผิดพลาดในการ Parse: {e}")
    
    def apply_delta_update(self, data):
        """ประยุกต์ใช้ Delta Update สำหรับ Order Book ที่อัปเดต"""
        with self.lock:
            # อัปเดต Bids
            for price, size, _ in data.get("bids", []):
                price = float(price)
                size = float(size)
                if size == 0:
                    self.order_book["bids"].pop(price, None)
                else:
                    self.order_book["bids"][price] = size
            
            # อัปเดต Asks
            for price, size, _ in data.get("asks", []):
                price = float(price)
                size = float(size)
                if size == 0:
                    self.order_book["asks"].pop(price, None)
                else:
                    self.order_book["asks"][price] = size
        
        self.update_stats()
    
    def on_error(self, ws, error):
        print(f"ข้อผิดพลาด WebSocket: {error}")
    
    def on_close(self, ws, *args):
        self.is_running = False
        print("การเชื่อมต่อถูกปิด")
    
    def on_open(self, ws):
        """ส่งคำสั่ง Subscribe พร้อมเลือก Order Book 25 ระดับ"""
        subscribe_msg = {
            "op": "subscribe",
            "args": [{
                "channel": "books",
                "instId": self.symbol,
                "sz": str(self.levels)
            }]
        }
        ws.send(json.dumps(subscribe_msg))
        self.is_running = True
        print(f"สมัครรับข้อมูล Order Book {self.levels} ระดับสำหรับ {self.symbol}")
    
    def connect(self):
        """เริ่มการเชื่อมต่อ WebSocket"""
        self.ws = websocket.WebSocketApp(
            "wss://ws.okx.com:8443/ws/v5/public",
            on_message=self.on_message,
            on_error=self.on_error,
            on_close=self.on_close,
            on_open=self.on_open
        )
        self.ws.run_forever()

ทดสอบการทำงาน

if __name__ == "__main__": analyzer = BTCDepthAnalyzer("BTC-USDT", levels=25) analyzer.connect()

ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข

กรณีที่ 1: ได้รับข้อมูลซ้ำหรือข้อมูลไม่ตรงกัน (Duplicate Messages)

# ปัญหา: ข้อความซ้ำหรือ Sequence สูญหาย

สาเหตุ: การ Reconnect หรือ Network Timeout

import json import time class ReconnectHandler: def __init__(self): self.last_seq = None self.seq_gap_detected = False def validate_sequence(self, data): """ ตรวจสอบลำดับของข้อความ หากพบช่องว่าง (Gap) ให้ส่งคำสั่งขอ Snapshot ใหม่ """ if "arg" not in data or "data" not in data: return True for depth_data in data["data"]: current_seq = depth_data.get("seqId") if self.last_seq is not None: # ตรวจสอบว่าลำดับติดกันหรือไม่ if current_seq != self.last_seq + 1: self.seq_gap_detected = True print(f"⚠️ พบ Sequence Gap: {self.last_seq} -> {current_seq}") return False self.last_seq = current_seq return True def should_resubscribe(self, consecutive_errors=3): """ ตัดสินใจว่าควร Resubscribe หรือไม่ หากพบข้อผิดพลาดติดต่อกันหลายครั้ง """ return self.seq_gap_detected

การใช้งาน

handler = ReconnectHandler() test_data = json.loads('{"arg":{},"data":[{"seqId":1001}]}') is_valid = handler.validate_sequence(test_data) print(f"ข้อมูลถูกต้อง: {is_valid}")

กรณีที่ 2: Connection Timeout และ Heartbeat Issues

# ปัญหา: การเชื่อมต่อหมดเวลาหรือหยุดทำงานโดยไม่มีสัญญาณเตือน

สาเหตุ: Proxy, Firewall หรือ Network Instability

import threading import time import random class HeartbeatMonitor: def __init__(self, timeout_seconds=30, ping_interval=20): self.timeout = timeout_seconds self.ping_interval = ping_interval self.last_pong_time = time.time() self.last_ping_time = None self.monitor_thread = None self.is_monitoring = False self.on_timeout_callback = None def start_monitoring(self): """เริ่มเธรดตรวจสอบ Heartbeat""" self.is_monitoring = True self.monitor_thread = threading.Thread(target=self._monitor_loop) self.monitor_thread.daemon = True self.monitor_thread.start() def _monitor_loop(self): """ลูปตรวจสอบสถานะการเชื่อมต่อ""" while self.is_monitoring: time.sleep(1) time_since_pong = time.time() - self.last_pong_time if time_since_pong > self.timeout: print(f"❌ ไม่ได้รับ Pong ภายใน {self.timeout} วินาที") if self.on_timeout_callback: self.on_timeout_callback() break def on_pong_received(self): """อัปเดตเวลาที่ได้รับ Pong ล่าสุด""" self.last_pong_time = time.time() print(f"✅ ได้รับ Pong - การเชื่อมต่อยังทำงานปกติ") def stop_monitoring(self): """หยุดการตรวจสอบ""" self.is_monitoring = False def set_timeout_callback(self, callback): """ตั้งค่า Callback เมื่อเกิด Timeout""" self.on_timeout_callback = callback

ตัวอย่างการใช้งาน

def handle_timeout(): print("🔄 กำลัง Reconnect...") # เพิ่มโค้ด Reconnect ที่นี่ monitor = HeartbeatMonitor(timeout_seconds=30) monitor.set_timeout_callback(handle_timeout) monitor.start_monitoring()

จำลองการได้รับ Pong

for _ in range(5): time.sleep(5) monitor.on_pong_received()

กรณีที่ 3: Memory Leak จาก Order Book ที่ขยายตัวไม่สิ้นสุด

# ปัญหา: ข้อมูล Order Book สะสมใน Memory โดยไม่มีการลบ

สาเหตุ: คำสั่งซื้อขายใหม่ถูกเพิ่มเข้ามาเรื่อยๆ โดยไม่มีการ Cleanup

from collections import OrderedDict import threading class MemoryEfficientOrderBook: """ Order Book ที่ประหยัด Memory ด้วยการจำกัดขนาดสูงสุด ใช้ OrderedDict เพื่อการลบที่รวดเร็ว """ MAX_BIDS = 1000 MAX_ASKS = 1000 def __init__(self, max_bids=MAX_BIDS, max_asks=MAX_ASKS): self.max_bids = max_bids self.max_asks = max_asks self.bids = OrderedDict() # price -> size self.asks = OrderedDict() self.lock = threading.Lock() self.update_count = 0 self.last_cleanup = 0 self.cleanup_interval = 100 # ทำความสะอาดทุก 100 ครั้ง def update_bid(self, price, size): """อัปเดตคำสั่งซื้อ พร้อมตรวจสอบ Memory""" with self.lock: if size == 0: self.bids.pop(price, None) else: self.bids[price] = size self._enforce_limits("bids") self._maybe_cleanup() def update_ask(self, price, size): """อัปเดตคำสั่งขาย พร้อมตรวจสอบ Memory""" with self.lock: if size == 0: self.asks.pop(price, None) else: self.asks[price] = size self._enforce_limits("asks") self._maybe_cleanup() def _enforce_limits(self, side): """บังคับให้จำนวนรายการไม่เกินขีดจำกัด""" book = self.bids if side == "bids" else self.asks max_size = self.max_bids if side == "bids" else self.max_asks # สำหรับ Bids ให้เก็บราคาสูงสุด # สำหรับ Asks ให้เก็บราคาต่ำสุด if len(book) > max_size: if side == "bids": # เก็บรายการที่มีราคาสูงที่สุด (Top Bids) sorted_prices = sorted(book.keys(), reverse=True)[:max_size] book_copy = {p: book[p] for p in sorted_prices} else: # เก็บรายการที่มีราคาต่ำที่สุด (Top Asks) sorted_prices = sorted(book.keys())[:max_size] book_copy = {p: book[p] for p in sorted_prices} book.clear() book.update(book_copy) def _maybe_cleanup(self): """ตรวจสอบและทำความสะอาด Memory เป็นระยะ""" self.update_count += 1 if self.update_count - self.last_cleanup >= self.cleanup_interval: self._cleanup_stale_entries() self.last_cleanup = self.update_count print(f"🧹 ทำความสะอาด Memory - Bids: {len(self.bids)}, Asks: {len(self.asks)}") def _cleanup_stale_entries(self): """ลบรายการที่ไม่มีขนาด (Size = 0) ออกจาก Memory""" self.bids = OrderedDict({k: v for k, v in self.bids.items() if v > 0}) self.asks = OrderedDict({k: v for k, v in self.asks.items() if v > 0}) def get_memory_usage(self): """ประมาณการใช้งาน Memory (bytes)""" import sys bids_size = sum(sys.getsizeof(k) + sys.getsizeof(v) for k, v in self.bids.items()) asks_size = sum(sys.getsizeof(k) + sys.getsizeof(v) for k, v in self.asks.items()) return bids_size + asks_size def get_stats(self): """สถิติการใช้งาน""" return { "total_bids": len(self.bids), "total_asks": len(self.asks), "memory_bytes": self.get_memory_usage(), "update_count": self.update_count }

ทดสอบ

book = MemoryEfficientOrderBook(max_bids=100, max_asks=100)

เพิ่มข้อมูลจำนวนมาก

for i in range(200): book.update_bid(50000 + i, 1.5) print(book.get_stats())

เหมาะกับใคร / ไม่เหมาะกับใคร

กลุ่มผู้ใช้เหมาะกับไม่เหมาะกับ
นักเทรดรายวัน (Day Trader)ได้รับข้อมูล Real-time สำหรับตัดสินใจซื้อขายทันทีผู้ที่ต้องการเฉพาะข้อมูล Historical
นักพัฒนา Trading Botใช้ WebSocket สำหรับ Low Latency Executionผู้ที่ต้องการ Backtesting ด้วยข้อมูลย้อนหลัง
นักวิเคราะห์ตลาดใช้ร่วมกับเครื่องมือวิเคราะห์อื่นๆผู้ที่ไม่มีความรู้ด้านเทคนิค
ผู้ที่ต้องการ API แบบค

🔥 ลอง HolySheep AI

เกตเวย์ AI API โดยตรง รองรับ Claude, GPT-5, Gemini, DeepSeek — หนึ่งคีย์ ไม่ต้อง VPN

👉 สมัครฟรี →