Trong lĩnh vực trading crypto, việc dự đoán chính xác funding rate (phí funding) là một trong những thách thức phức tạp nhất. Bài viết này sẽ hướng dẫn bạn xây dựng hệ thống feature engineering hoàn chỉnh để dự đoán funding rate, từ việc thu thập dữ liệu, xử lý features, cho đến huấn luyện mô hình machine learning.
Kịch bản lỗi thực tế
Tôi vẫn nhớ rõ ngày đầu tiên triển khai hệ thống dự đoán funding rate cho một sàn futures phổ biến. Mọi thứ看似 hoàn hảo cho đến khi logs bắt đầu xuất hiện một loạt lỗi:
2024-11-15 03:24:15 | ERROR | FundingRateCollector
ConnectionError: HTTPSConnectionPool(host='api.binance.com', port=443):
Max retries exceeded with url: /fapi/v1/premiumIndex (Caused by
ConnectTimeoutError: <Connection(HTTPConnectionPool(host='api.binance.com',
port=443): Connect timeout: 5 seconds)>)
2024-11-15 03:24:15 | WARNING | Rate limit exceeded. Retrying in 300 seconds...
2024-11-15 03:24:15 | CRITICAL | Data gap detected: 15 minutes of missing funding data
Không chỉ có timeout, mà hệ thống còn gặp phải vấn đề về rate limiting và xử lý dữ liệu thiếu liên tục. Sau 3 ngày debug liên tục, tôi đã tìm ra giải pháp toàn diện và chia sẻ trong bài viết này.
Tổng quan hệ thống
Hệ thống feature engineering cho funding rate prediction bao gồm 4 module chính:
- Data Collector - Thu thập dữ liệu từ các sàn giao dịch
- Feature Extractor - Trích xuất features từ raw data
- Feature Store - Lưu trữ và quản lý features
- Model Trainer - Huấn luyện mô hình dự đoán
1. Data Collector - Module thu thập dữ liệu
Việc thu thập dữ liệu funding rate từ nhiều sàn là bước nền tảng. Dưới đây là implementation hoàn chỉnh với xử lý lỗi và retry mechanism:
import requests
import time
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Optional
from dataclasses import dataclass
from threading import Lock
import asyncio
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class FundingRateData:
symbol: str
funding_rate: float
mark_price: float
index_price: float
next_funding_time: datetime
timestamp: datetime
class FundingRateCollector:
"""
Collector for funding rate data from multiple exchanges.
Handles rate limiting, retries, and data validation.
"""
def __init__(self, api_base_url: str, api_key: str):
self.api_base_url = api_base_url
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({
'X-API-KEY': api_key,
'Content-Type': 'application/json'
})
self.rate_limit_lock = Lock()
self.last_request_time = {}
self.min_request_interval = 0.1 # 100ms between requests
# Retry configuration
self.max_retries = 3
self.retry_delay = 5 # seconds
self.timeout = 10 # seconds
def _rate_limit(self, endpoint: str):
"""Enforce rate limiting per endpoint"""
with self.rate_limit_lock:
current_time = time.time()
if endpoint in self.last_request_time:
elapsed = current_time - self.last_request_time[endpoint]
if elapsed < self.min_request_interval:
time.sleep(self.min_request_interval - elapsed)
self.last_request_time[endpoint] = time.time()
def _make_request(self, url: str, params: Optional[Dict] = None) -> Dict:
"""Make HTTP request with retry logic"""
for attempt in range(self.max_retries):
try:
self._rate_limit(url)
response = self.session.get(
url,
params=params,
timeout=self.timeout
)
if response.status_code == 200:
return response.json()
elif response.status_code == 429:
logger.warning(f"Rate limit hit. Retrying in {self.retry_delay}s...")
time.sleep(self.retry_delay)
elif response.status_code == 401:
raise ValueError("401 Unauthorized: Invalid API key")
else:
raise requests.HTTPError(f"HTTP {response.status_code}: {response.text}")
except requests.exceptions.Timeout:
logger.warning(f"Timeout on attempt {attempt + 1}/{self.max_retries}")
if attempt < self.max_retries - 1:
time.sleep(self.retry_delay * (attempt + 1))
except requests.exceptions.ConnectionError as e:
logger.warning(f"ConnectionError: {e}")
if attempt < self.max_retries - 1:
time.sleep(self.retry_delay * (attempt + 1))
raise ConnectionError(f"Failed after {self.max_retries} attempts")
def get_funding_rate(self, symbol: str) -> Optional[FundingRateData]:
"""Fetch current funding rate for a symbol"""
url = f"{self.api_base_url}/fapi/v1/premiumIndex"
params = {'symbol': symbol}
try:
data = self._make_request(url, params)
return FundingRateData(
symbol=symbol,
funding_rate=float(data['lastFundingRate']) * 100, # Convert to percentage
mark_price=float(data['markPrice']),
index_price=float(data['indexPrice']),
next_funding_time=datetime.fromtimestamp(
data['nextFundingTime'] / 1000
),
timestamp=datetime.now()
)
except Exception as e:
logger.error(f"Error fetching funding rate for {symbol}: {e}")
return None
def get_funding_history(self, symbol: str, days: int = 30) -> List[Dict]:
"""Fetch historical funding rates"""