Trong thế giới tài chính định lượng, việc khai thác dữ liệu thị trường cryptocurrency thời gian thực là nền tảng cho mọi chiến lược giao dịch thuật toán. Bài viết này từ góc nhìn của một kỹ sư đã triển khai hệ thống giao dịch tần suất cao cho 7 sàn giao dịch khác nhau sẽ chia sẻ cách xây dựng pipeline lấy dữ liệu API production-ready, từ kiến trúc bất đồng bộ đến tối ưu chi phí với HolySheep AI.

Mục Lục

Kiến Trúc Hệ Thống Lấy Dữ Liệu Crypto

Kiến trúc production cho hệ thống lấy dữ liệu từ nhiều sàn giao dịch cần đáp ứng các yêu cầu khắt khe: độ trễ thấp dưới 50ms, khả năng chịu tải 10,000+ requests/giây, và failover tự động khi sàn gặp sự cố. Tôi đã thử nghiệm nhiều kiến trúc và kết luận rằng mô hình event-driven với connection pooling là tối ưu nhất.

┌─────────────────────────────────────────────────────────────┐
│                    SYSTEM ARCHITECTURE                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────┐    ┌─────────┐    ┌─────────┐                 │
│  │ Binance │    │  Bybit  │    │ OKX    │    ...           │
│  │   API   │    │   API   │    │  API   │                 │
│  └────┬────┘    └────┬────┘    └────┬────┘                 │
│       │              │              │                       │
│       └──────────────┼──────────────┘                       │
│                      ▼                                      │
│            ┌───────────────────┐                           │
│            │  Async Manager    │                           │
│            │  (Connection Pool) │                           │
│            └─────────┬─────────┘                           │
│                      │                                      │
│       ┌──────────────┼──────────────┐                       │
│       ▼              ▼              ▼                       │
│  ┌─────────┐   ┌─────────┐   ┌─────────┐                   │
│  │ Webhook │   │ REST    │   │  Redis  │                   │
│  │ Handler │   │ Client  │   │  Cache  │                   │
│  └────┬────┘   └────┬────┘   └────┬────┘                   │
│       │              │              │                       │
│       └──────────────┼──────────────┘                       │
│                      ▼                                      │
│            ┌───────────────────┐                           │
│            │   Data Pipeline   │                           │
│            │  (Transform/Load) │                           │
│            └─────────┬─────────┘                           │
│                      │                                      │
│                      ▼                                      │
│            ┌───────────────────┐                           │
│            │  Trading Engine    │                           │
│            │  (Signal Gen)      │                           │
│            └───────────────────┘                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Cài Đặt Môi Trường Và Dependencies

Để bắt đầu, bạn cần cài đặt các thư viện cần thiết. Tôi khuyến nghị sử dụng Python 3.11+ để tận dụng các cải tiến về async/await và performance.

# Tạo virtual environment và cài đặt dependencies
python -m venv venv
source venv/bin/activate  # Linux/Mac

hoặc: venv\Scripts\activate # Windows

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

pip install aiohttp==3.9.1 \ asyncio==3.4.3 \ ccxt==4.2.62 \ websockets==12.0 \ redis==5.0.1 \ pandas==2.1.4 \ numpy==1.26.2 \ python-dotenv==1.0.0 \ prometheus-client==0.19.0

Kiểm tra version

python --version # Python 3.11.8

Kết Nối Binance API - Hướng Dẫn Chi Tiết

Binance là sàn giao dịch có volume lớn nhất thế giới, cung cấp REST API ổn định với độ trễ trung bình 15-30ms từ server Singapore. Dưới đây là implementation production-ready với error handling và retry logic.

import asyncio
import aiohttp
import time
from typing import Optional, Dict, List, Any
from dataclasses import dataclass
from enum import Enum
import json
from datetime import datetime

class APIError(Exception):
    """Custom exception cho API errors"""
    def __init__(self, code: int, message: str):
        self.code = code
        self.message = message
        super().__init__(f"[{code}] {message}")

class RateLimitType(Enum):
    """Rate limit type constants"""
    WEIGHT = "weight"
    ORDERS = "orders"
    REQUEST = "requests"

@dataclass
class APICredentials:
    """Lưu trữ thông tin xác thực API một cách an toàn"""
    api_key: str
    api_secret: str
    passphrase: Optional[str] = None  # Cho một số sàn

class BinanceDataFetcher:
    """
    Async data fetcher cho Binance API
    Hỗ trợ: Klines, Orderbook, Trades, Ticker, Account Balance
    """
    
    BASE_URL = "https://api.binance.com"
    TESTNET_URL = "https://testnet.binance.vision"
    
    # Rate limits (weight system)
    WEIGHT_LIMITS = {
        "klines": 1,        # 1 weight per request
        "depth": 5,         # 5 weight per request
        "trades": 1,        # 1 weight per request
        "ticker": 1,        # 1 weight per request
        "account": 10,      # 10 weight per request
    }
    
    # Max requests per minute (1200 weight/min default)
    MAX_WEIGHT_PER_MINUTE = 1200
    
    def __init__(
        self, 
        credentials: Optional[APICredentials] = None,
        testnet: bool = False,
        proxy: Optional[str] = None
    ):
        self.credentials = credentials
        self.base_url = self.TESTNET_URL if testnet else self.BASE_URL
        self.proxy = proxy
        
        # Connection pooling
        self._session: Optional[aiohttp.ClientSession] = None
        self._rate_limiter = asyncio.Semaphore(50)  # Max concurrent requests
        
        # Request tracking for rate limiting
        self._request_weights: List[tuple] = []  # [(timestamp, weight), ...]
        
    async def __aenter__(self):
        """Context manager entry"""
        connector = aiohttp.TCPConnector(
            limit=100,              # Max connections
            limit_per_host=50,      # Max connections per host
            ttl_dns_cache=300,      # DNS cache TTL
            keepalive_timeout=30,   # Keep-alive timeout
        )
        
        timeout = aiohttp.ClientTimeout(
            total=30,
            connect=10,
            sock_read=15
        )
        
        self._session = aiohttp.ClientSession(
            connector=connector,
            timeout=timeout
        )
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit - cleanup"""
        if self._session:
            await self._session.close()
    
    async def _check_rate_limit(self, endpoint: str) -> None:
        """
        Kiểm tra và áp dụng rate limiting
        Sử dụng sliding window algorithm
        """
        now = time.time()
        weight = self.WEIGHT_LIMITS.get(endpoint, 1)
        
        # Remove requests older than 1 minute
        self._request_weights = [
            (ts, w) for ts, w in self._request_weights 
            if now - ts < 60
        ]
        
        # Calculate current weight usage
        current_weight = sum(w for _, w in self._request_weights)
        
        if current_weight + weight > self.MAX_WEIGHT_PER_MINUTE:
            # Calculate wait time
            oldest = min(ts for ts, _ in self._request_weights) if self._request_weights else now
            wait_time = 60 - (now - oldest) + 0.1
            await asyncio.sleep(wait_time)
        
        self._request_weights.append((now, weight))
    
    async def _request(
        self, 
        method: str,
        endpoint: str,
        params: Optional[Dict] = None,
        signed: bool = False,
        return_raw: bool = False
    ) -> Dict[str, Any]:
        """
        Core request method với retry logic và error handling
        """
        url = f"{self.base_url}{endpoint}"
        headers = {
            "Content-Type": "application/json",
            "X-MBX-APIKEY": self.credentials.api_key if self.credentials else ""
        }
        
        # Retry configuration
        max_retries = 3
        retry_delay = 1.0
        
        for attempt in range(max_retries):
            try:
                async with self._rate_limiter:
                    await self._check_rate_limit(endpoint.split('/')[-1])
                    
                    async with self._session.request(
                        method=method,
                        url=url,
                        params=params,
                        headers=headers,
                        proxy=self.proxy
                    ) as response:
                        
                        # Handle rate limiting
                        if response.status == 429:
                            retry_after = int(response.headers.get('Retry-After', 60))
                            await asyncio.sleep(retry_after)
                            continue
                        
                        # Parse response
                        if response.content_type == 'application/json':
                            data = await response.json()
                        else:
                            data = await response.text()
                        
                        if response.status >= 400:
                            error_msg = data.get('msg', 'Unknown error') if isinstance(data, dict) else data
                            raise APIError(response.status, error_msg)
                        
                        return data
                        
            except aiohttp.ClientError as e:
                if attempt == max_retries - 1:
                    raise
                await asyncio.sleep(retry_delay * (2 ** attempt))
                
            except asyncio.TimeoutError:
                if attempt == max_retries - 1:
                    raise APIError(408, "Request timeout")
                await asyncio.sleep(retry_delay)
        
        raise APIError(500, "Max retries exceeded")
    
    async def get_klines(
        self,
        symbol: str,
        interval: str = "1m",
        limit: int = 500,
        start_time: Optional[int] = None,
        end_time: Optional[int] = None
    ) -> List[Dict[str, Any]]:
        """
        Lấy dữ liệu candlestick/kline
        symbol: BTCUSDT, ETHUSDT, etc.
        interval: 1m, 5m, 15m, 1h, 4h, 1d, 1w
        limit: max 1500 cho historical, 1000 cho real-time
        """
        params = {
            "symbol": symbol.upper(),
            "interval": interval,
            "limit": limit
        }
        
        if start_time:
            params["startTime"] = start_time
        if end_time:
            params["endTime"] = end_time
        
        data = await self._request("GET", "/api/v3/klines", params=params)
        
        # Transform to structured format
        return [
            {
                "open_time": kline[0],
                "open": float(kline[1]),
                "high": float(kline[2]),
                "low": float(kline[3]),
                "close": float(kline[4]),
                "volume": float(kline[5]),
                "close_time": kline[6],
                "quote_volume": float(kline[7]),
                "trades": int(kline[8]),
                "taker_buy_base": float(kline[9]),
                "taker_buy_quote": float(kline[10]),
            }
            for kline in data
        ]
    
    async def get_orderbook(
        self,
        symbol: str,
        limit: int = 100
    ) -> Dict[str, Any]:
        """
        Lấy orderbook depth data
        limit: 5, 10, 20, 50, 100, 500, 1000, 5000
        """
        params = {
            "symbol": symbol.upper(),
            "limit": limit
        }
        
        data = await self._request("GET", "/api/v3/depth", params=params)
        
        return {
            "last_update_id": data["lastUpdateId"],
            "bids": [[float(price), float(qty)] for price, qty in data["bids"]],
            "asks": [[float(price), float(qty)] for price, qty in data["asks"]],
        }
    
    async def get_recent_trades(self, symbol: str, limit: int = 500) -> List[Dict]:
        """Lấy danh sách trades gần đây"""
        params = {
            "symbol": symbol.upper(),
            "limit": limit
        }
        
        data = await self._request("GET", "/api/v3/trades", params=params)
        
        return [
            {
                "id": trade["id"],
                "price": float(trade["price"]),
                "qty": float(trade["qty"]),
                "time": trade["time"],
                "is_buyer_maker": trade["isBuyerMaker"],
            }
            for trade in data
        ]
    
    async def get_24hr_ticker(self, symbol: Optional[str] = None) -> List[Dict]:
        """Lấy 24h ticker statistics"""
        endpoint = "/api/v3/ticker/24hr" if symbol else "/api/v3/ticker/24hr"
        params = {"symbol": symbol.upper()} if symbol else {}
        
        data = await self._request("GET", endpoint, params=params)
        
        if symbol:
            return [data]
        return data


============== USAGE EXAMPLE ==============

async def main(): """Ví dụ sử dụng Binance Data Fetcher""" async with BinanceDataFetcher() as fetcher: # Lấy 500 candlestick 1 giờ của BTC btc_klines = await fetcher.get_klines( symbol="BTCUSDT", interval="1h", limit=500 ) print(f"Đã lấy {len(btc_klines)} candles BTC/USDT") # Lấy orderbook orderbook = await fetcher.get_orderbook("ETHUSDT", limit=100) print(f"Bid/Ask spread: {orderbook['asks'][0][0] - orderbook['bids'][0][0]}") # Lấy ticker của BTC ticker = await fetcher.get_24hr_ticker("BTCUSDT") print(f"Giá hiện tại: ${float(ticker[0]['lastPrice']):,.2f}") if __name__ == "__main__": asyncio.run(main())

Kết Nối Bybit API Với WebSocket

Bybit là sàn phái sinh phổ biến với API WebSocket cho dữ liệu real-time. Tôi sử dụng Bybit cho các chiến lược arbitrage và funding rate monitoring vì độ trễ thấp hơn đáng kể so với REST API.

import asyncio
import json
import hmac
import hashlib
import time
import websockets
from typing import Callable, Dict, List, Optional, Any
from dataclasses import dataclass
from enum import Enum

class BybitEnvironment(Enum):
    """Môi trường Bybit"""
    MAINNET = "wss://stream.bybit.com"
    TESTNET = "wss://stream-testnet.bybit.com"

class BybitCategory(Enum):
    """Product category"""
    SPOT = "spot"
    LINEAR = "linear"      # USDT perpetual
    INVERSE = "inverse"    # Inverse perpetual
    OPTION = "option"

@dataclass
class WebSocketMessage:
    """Structured WebSocket message"""
    topic: str
    data: Any
    timestamp: int

class BybitWebSocketClient:
    """
    Async WebSocket client cho Bybit
    Hỗ trợ: Trade, Orderbook, Ticker, Kline, Position
    """
    
    def __init__(
        self,
        api_key: Optional[str] = None,
        api_secret: Optional[str] = None,
        environment: BybitEnvironment = BybitEnvironment.MAINNET,
        category: BybitCategory = BybitCategory.LINEAR,
        trace_id: str = "custom_stream"
    ):
        self.api_key = api_key
        self.api_secret = api_secret
        self.base_url = f"{environment.value}/v5/public/{category.value}"
        self.trace_id = trace_id
        
        self._ws: Optional[websockets.WebSocketClientProtocol] = None
        self._subscriptions: List[str] = []
        self._handlers: Dict[str, List[Callable]] = {}
        self._running = False
        self._reconnect_delay = 1
        self._max_reconnect_delay = 60
        
        # Performance metrics
        self._messages_received = 0
        self._last_message_time = 0
        self._latencies: List[float] = []
    
    async def connect(self):
        """Kết nối WebSocket với auto-reconnect"""
        while True:
            try:
                headers = {}
                if self.api_key:
                    headers["X-BAPI-API-KEY"] = self.api_key
                
                self._ws = await websockets.connect(
                    self.base_url,
                    extra_headers=headers,
                    ping_interval=20,
                    ping_timeout=10
                )
                
                self._running = True
                self._reconnect_delay = 1  # Reset delay on successful connect
                
                # Re-subscribe to previous topics
                for topic in self._subscriptions:
                    await self._subscribe_topic(topic)
                
                await self._listen()
                
            except websockets.ConnectionClosed as e:
                print(f"Connection closed: {e.code} - {e.reason}")
            except Exception as e:
                print(f"Connection error: {e}")
            
            self._running = False
            await asyncio.sleep(self._reconnect_delay)
            self._reconnect_delay = min(
                self._reconnect_delay * 2, 
                self._max_reconnect_delay
            )
    
    async def _listen(self):
        """Listen for incoming messages"""
        async for message in self._ws:
            if not self._running:
                break
            
            try:
                self._messages_received += 1
                self._last_message_time = time.time()
                
                data = json.loads(message)
                
                # Handle different message types
                if "topic" in data:
                    await self._handle_message(data)
                elif "op" in data:
                    await self._handle_operation_response(data)
                    
            except json.JSONDecodeError:
                print(f"Invalid JSON: {message}")
            except Exception as e:
                print(f"Message handling error: {e}")
    
    async def _handle_message(self, data: Dict):
        """Xử lý subscription message"""
        topic = data["topic"]
        msg_type = data.get("type", "snapshot")
        msg_time = int(data.get("ts", 0))
        
        # Calculate latency
        if msg_time:
            latency_ms = (time.time() * 1000) - msg_time
            self._latencies.append(latency_ms)
        
        # Call registered handlers
        if topic in self._handlers:
            for handler in self._handlers[topic]:
                try:
                    await handler(data["data"], msg_type)
                except Exception as e:
                    print(f"Handler error for {topic}: {e}")
    
    async def _handle_operation_response(self, data: Dict):
        """Xử lý operation response (subscribe/unsubscribe)"""
        op = data.get("op", "")
        success = data.get("success", False)
        
        if not success:
            print(f"Operation {op} failed: {data}")
    
    async def _send(self, payload: Dict):
        """Gửi message qua WebSocket"""
        if self._ws and self._running:
            await self._ws.send(json.dumps(payload))
    
    async def subscribe(
        self,
        topic: str,
        handler: Optional[Callable] = None,
        symbol: Optional[str] = None
    ):
        """
        Subscribe to a topic
        topic: "orderbook.50.L1BTCUSDT", "trades", "kline.1.BTCUSDT"
        """
        # Build full topic
        full_topic = topic if symbol is None else f"{topic}.{symbol}"
        
        # Register handler
        if handler:
            if full_topic not in self._handlers:
                self._handlers[full_topic] = []
            self._handlers[full_topic].append(handler)
        
        # Subscribe if connected
        if self._ws and self._running:
            await self._subscribe_topic(full_topic)
        else:
            self._subscriptions.append(full_topic)
    
    async def _subscribe_topic(self, topic: str):
        """Internal subscribe method"""
        await self._send({
            "op": "subscribe",
            "args": [topic],
            "req_id": f"{self.trace_id}_{int(time.time() * 1000)}"
        })
        print(f"Subscribed to: {topic}")
    
    def get_metrics(self) -> Dict:
        """Lấy performance metrics"""
        avg_latency = sum(self._latencies) / len(self._latencies) if self._latencies else 0
        p95_latency = sorted(self._latencies)[int(len(self._latencies) * 0.95)] if self._latencies else 0
        
        return {
            "messages_received": self._messages_received,
            "avg_latency_ms": round(avg_latency, 2),
            "p95_latency_ms": round(p95_latency, 2),
            "active_subscriptions": len(self._handlers),
        }


============== HANDLER EXAMPLES ==============

async def orderbook_handler(data: List, msg_type: str): """Handler cho orderbook updates""" if msg_type == "snapshot": print(f"Orderbook snapshot: {len(data)} levels") else: # delta update for item in data: side = "bid" if item["b"] else "ask" print(f"Update: {side} {item['p']} x {item['v']}") async def trade_handler(data: List, msg_type: str): """Handler cho trade data""" for trade in data: print(f"Trade: {trade['S']} {trade['p']} x {trade['v']} @ {trade['T']}") async def kline_handler(data: List, msg_type: str): """Handler cho kline/candlestick updates""" for candle in data: kline = candle.get('k', candle) if isinstance(candle, dict) else candle print(f"Kline: O={kline['o']} H={kline['h']} L={kline['l']} C={kline['c']}")

============== USAGE EXAMPLE ==============

async def main(): """Ví dụ sử dụng Bybit WebSocket Client""" client = BybitWebSocketClient( category=BybitCategory.LINEAR ) # Subscribe to multiple topics await client.subscribe("orderbook.50", orderbook_handler, "BTCUSDT") await client.subscribe("publicTrade", trade_handler, "BTCUSDT") await client.subscribe("kline.1", kline_handler, "BTCUSDT") # Start connection in background listener_task = asyncio.create_task(client.connect()) # Run for 60 seconds try: await asyncio.sleep(60) finally: # Print metrics metrics = client.get_metrics() print(f"\n=== Performance Metrics ===") print(f"Messages received: {metrics['messages_received']}") print(f"Average latency: {metrics['avg_latency_ms']}ms") print(f"P95 latency: {metrics['p95_latency_ms']}ms") await listener_task if __name__ == "__main__": asyncio.run(main())

Tối Ưu Hiệu Suất Và Chi Phí

Benchmark Kết Quả Thực Tế

Qua 6 tháng vận hành hệ thống lấy dữ liệu cho 5 sàn giao dịch, đây là các metrics quan trọng tôi đã đo được:

MetricREST APIWebSocketCải thiện
Average Latency45ms12ms73%
P99 Latency180ms35ms81%
Requests/sec50010,000+20x
Cost/1M requests$2.50$0.5080%

Tối Ưu Connection Pooling

# Configuration tối ưu cho production
OPTIMAL_CONFIG = {
    # aiohttp connection settings
    "tcp_connections": {
        "max_per_host": 50,
        "max_total": 100,
        "ttl_dns_cache": 300,  # 5 phút
        "keepalive_timeout": 30,
    },
    
    # Rate limiting
    "rate_limit": {
        "requests_per_minute": 1200,
        "burst_size": 50,
        "cooldown": 0.1,  # seconds between requests
    },
    
    # Retry strategy
    "retry": {
        "max_attempts": 3,
        "base_delay": 1.0,
        "max_delay": 30.0,
        "exponential_base": 2,
        "jitter": True,
    },
    
    # Cache settings
    "cache": {
        "enabled": True,
        "ttl": {
            "ticker": 5,      # 5 seconds
            "orderbook": 1,   # 1 second
            "klines": 60,     # 1 minute
            "trades": 0,      # no cache
        }
    }
}

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

1. Lỗi 429 - Too Many Requests

# ❌ Sai cách - Không có rate limit
for symbol in symbols:
    await fetch_klines(symbol)  # Có thể bị ban

✅ Đúng cách - Có rate limiting

import asyncio from collections import deque from time import time class RateLimiter: """Token bucket rate limiter với sliding window""" def __init__(self, rate: int, per: float): """ rate: số requests per: thời gian (giây) """ self.rate = rate self.per = per self.allowance = rate self.last_check = time() self._lock = asyncio.Lock() async def acquire(self): """Acquire permission to make a request""" async with self._lock: current = time() time_passed = current - self.last_check self.last_check = current # Restore tokens based on time passed self.allowance += time_passed * (self.rate / self.per) if self.allowance > self.rate: self.allowance = self.rate if self.allowance < 1.0: # Wait until we have enough tokens wait_time = (1.0 - self.allowance) * (self.per / self.rate) await asyncio.sleep(wait_time) self.allowance = 0.0 else: self.allowance -= 1.0

Sử dụng

async def fetch_all_symbols(symbols: List[str]): limiter = RateLimiter(rate=1200, per=60) # 1200/min tasks = [] for symbol in symbols: async def fetch_with_limit(s): await limiter.acquire() return await fetch_klines(s) tasks.append(fetch_with_limit(symbol)) return await asyncio.gather(*tasks)

2. Lỗi Connection Timeout Liên Tục

# ❌ Nguyên nhân thường gặy:

1. Không có proxy, IP bị sàn chặn

2. DNS resolution chậm

3. Keep-alive connections exhausted

✅ Giải pháp toàn diện

import aiohttp import asyncio from typing import Optional import aiodns class OptimizedHTTPClient: """HTTP client với multi-layer optimization""" def __init__( self, proxy: Optional[str] = None, # Sử dụng proxy nếu cần dns_servers: List[str] = ["8.8.8.8", "1.1.1.1"], timeout: float = 30.0, ): self.proxy = proxy # Custom DNS resolver resolver = aiodns.DNSResolver(nameservers=dns_servers) # Connection settings connector = aiohttp.TCPConnector( limit=100, # Total connection limit limit_per_host=50, # Per-host limit limit_for_dest_host=50, ttl_dns_cache=300, keepalive_timeout=30, enable_cleanup_closed=True, force_close=False, # Reuse connections ) timeout_config = aiohttp.ClientTimeout( total=timeout, connect=10, # Connection timeout sock_connect=10, sock_read=15, # Read timeout ) self.session = aiohttp.ClientSession( connector=connector, timeout=timeout_config, ) async def get_with_retry( self, url: str, max_retries: int = 3, retry_codes: List[int] = [408, 429, 500, 502, 503, 504] ): """GET request với exponential backoff""" for attempt in range(max_retries): try: async with self.session.get( url, proxy=self.proxy, allow_redirects=True, compress=True, # Enable gzip ) as response: if response.status not in retry_codes: return await response.json() # Rate limited - wait longer if response.status == 429: retry_after = int( response.headers.get('Retry-After', 60) ) await asyncio.sleep(retry_after) continue # Server error - exponential backoff delay = min(2 ** attempt + asyncio.random.uniform(0, 1), 30) await asyncio.sleep(delay) except asyncio.TimeoutError: delay = min(2 ** attempt, 30) await asyncio.sleep(delay) except aiohttp.ClientError as e: await asyncio.sleep(2 ** attempt) raise Exception(f"Failed after {max_retries} attempts")

3. Dữ Liệu Không Nhất Quán (Stale Data)

# ❌ Vấn đề: Race condition khi fetch order