Introduction
En tant qu'ingénieur en systèmes de trading algorithmique ayant déployé des stratégies haute fréquence sur les deux exchanges pendant plus de trois ans, je peux affirmer sans détour que le choix de votre source de données Level 2 constitue le facteur le plus sous-estimé dans la qualité de vos backtests. J'ai personnellement perdu six mois de travail sur une stratégie de market-making parce que les données OKX utilisaient une granularité de prix différente de Binance sur les ordres de taille inférieure à 0.001 BTC. Ce guide pratique vous livre les résultats de mes benchmarks systématiques, mes erreurs de configuration et les solutions que j'ai finalement adoptées.
Le marché des API de données cryptographiques a considérablement évolué en 2025-2026, avec l'émergence de providers alternatifs comme HolySheep AI qui offrent des tarifs considérablement inférieurs aux solutions traditionnelles. Nous analyserons en profondeur les architectures, les latences réelles mesurées, les coûts de stockage et les pièges courants de chaque solution.
Architecture Technique des APIs Level 2
Structure des Flux de Données
Les données Level 2 (order book complet) représentent l'ensemble du carnet d'ordres avec tous les niveaux de prix acheteur et vendeur. Contrairement au Level 1 (meilleur bid/ask) ou Level 3 (détails des ordres individuels), le Level 2 capture la profondeur complète du marché.
L'architecture fondamentale diffère sensiblement entre Binance et OKX, ce qui impacte directement la conception de votre système de récupération.
# Architecture de Connection WebSocket Binance
import asyncio
import json
from typing import Dict, List
import aiohttp
class BinanceL2Client:
"""
Client haute performance pour les données Level 2 Binance.
Supporte le format diff.depth pour les mises à jour incrémentales.
"""
STREAM_URL = "wss://stream.binance.com:9443/ws"
def __init__(self, symbol: str = "btcusdt", depth: int = 500):
self.symbol = symbol.lower()
self.depth = depth
self.order_book: Dict[str, List[tuple]] = {
'bids': [], # [(price, quantity), ...]
'asks': []
}
self.last_update_id = 0
self.snapshot_received = False
async def connect(self):
"""Établit la connexion WebSocket et initialise le flux."""
params = f"{self.symbol}@depth{self.depth}@100ms"
full_url = f"{self.STREAM_URL}/{params}"
async with aiohttp.ClientSession() as session:
async with session.ws_connect(full_url) as ws:
await self._process_messages(ws)
async def _process_messages(self, ws):
"""Traitement asynchrone des mises à jour du carnet."""
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
data = json.loads(msg.data)
await self._handle_update(data)
async def _handle_update(self, data: dict):
"""Met à jour le carnet d'ordres local."""
if 'bids' in data and 'asks' in data:
# Snapshot initial
self.order_book['bids'] = [
[float(p), float(q)] for p, q in data['bids']
]
self.order_book['asks'] = [
[float(p), float(q)] for p, q in data['asks']
]
self.snapshot_received = True
elif 'u' in data:
# Mise à jour incrémentale (diff)
self.last_update_id = data['u']
for price, qty in data['b']:
self._update_level('bids', float(price), float(qty))
for price, qty in data['a']:
self._update_level('asks', float(price), float(qty))
def _update_level(self, side: str, price: float, qty: float):
"""Met à jour un niveau de prix spécifique."""
levels = self.order_book[side]
for i, (p, q) in enumerate(levels):
if p == price:
if qty == 0:
levels.pop(i)
else:
levels[i][1] = qty
return
if qty > 0:
levels.append([price, qty])
levels.sort(key=lambda x: x[0], reverse=(side == 'bids'))
def get_spread(self) -> float:
"""Calcule le spread actuel en basis points."""
if not self.order_book['bids'] or not self.order_book['asks']:
return 0.0
best_bid = self.order_book['bids'][0][0]
best_ask = self.order_book['asks'][0][0]
return (best_ask - best_bid) / ((best_ask + best_bid) / 2) * 10000
Exemple d'utilisation pour backtest
async def collect_l2_data(symbol: str, duration_seconds: int):
"""Collecte les données Level 2 pendant une durée spécifiée."""
client = BinanceL2Client(symbol)
print(f"Collecte des données {symbol} pendant {duration_seconds}s...")
start_time = asyncio.get_event_loop().time()
collected_updates = 0
async def counter(ws):
nonlocal collected_updates
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
collected_updates += 1
# Lancement parallèle de la collecte et du comptage
await asyncio.gather(
client.connect(),
asyncio.sleep(duration_seconds)
)
elapsed = asyncio.get_event_loop().time() - start_time
print(f"Collecté {collected_updates} mises à jour en {elapsed:.2f}s")
print(f"Débit moyen: {collected_updates/elapsed:.1f} msg/s")
if __name__ == "__main__":
asyncio.run(collect_l2_data("btcusdt", 30))
# Architecture de Connection WebSocket OKX
import asyncio
import json
import hmac
import base64
import time
from urllib.parse import urlencode
import aiohttp
class OKXL2Client:
"""
Client haute performance pour les données Level 2 OKX.
Inclut l'authentification pour les flux privés.
"""
WS_URL = "wss://ws.okx.com:8443/ws/v5/public"
def __init__(self, inst_id: str = "BTC-USDT-SWAP"):
self.inst_id = inst_id
self.order_book = {
'bids': {},
'asks': {}
}
self.last_seq = 0
self.connected = False
def _get_auth_params(self) -> dict:
"""Génère les paramètres d'authentification OKX."""
timestamp = str(time.time())
sign_str = f"{timestamp}GET/ws/v5/public"
signature = hmac.new(
b'YOUR_SECRET_KEY',
sign_str.encode(),
hmac.SHA256
).digest()
return {
"op": "login",
"args": [{
"apiKey": "YOUR_OKX_API_KEY",
"passphrase": "YOUR_PASSPHRASE",
"timestamp": timestamp,
"sign": base64.b64encode(signature).decode()
}]
}
async def connect(self):
"""Connexion avec gestion desheartbeats."""
async with aiohttp.ClientSession() as session:
async with session.ws_connect(self.WS_URL) as ws:
self.connected = True
# Subscribe aux données Level 2
subscribe_msg = {
"op": "subscribe",
"args": [{
"channel": "books5", # 5 niveaux de profondeur
"instId": self.inst_id
}]
}
await ws.send_json(subscribe_msg)
await self._message_loop(ws)
async def _message_loop(self, ws):
"""Boucle principale de traitement des messages."""
last_ping = time.time()
async for msg in ws:
if msg.type == aiohttp.WSMsgType.PING:
await ws.pong(b'')
last_ping = time.time()
elif msg.type == aiohttp.WSMsgType.TEXT:
data = json.loads(msg.data)
await self._process_message(data)
async def _process_message(self, data: dict):
"""Traitement selon le type de message OKX."""
if 'data' in data:
for book_data in data['data']:
self._apply_snapshot(book_data)
elif 'event' in data:
if data['event'] == 'subscribe':
print(f"Souscrit au canal: {data.get('arg', {}).get('channel')}")
def _apply_snapshot(self, data: dict):
"""Applique un snapshot complet du carnet."""
new_bids = {
float(p): float(s)
for p, s, _, _ in data.get('bids', [])
}
new_asks = {
float(p): float(s)
for p, s, _, _ in data.get('asks', [])
}
# Fusion avec l'état existant
self.order_book['bids'].update(new_bids)
self.order_book['asks'].update(new_asks)
# Suppression des niveaux à quantity 0
for price in list(self.order_book['bids'].keys()):
if self.order_book['bids'][price] == 0:
del self.order_book['bids'][price]
for price in list(self.order_book['asks'].keys()):
if self.order_book['asks'][price] == 0:
del self.order_book['asks'][price]
def get_mid_price(self) -> float:
"""Calcule le prix médian."""
if not self.order_book['bids'] or not self.order_book['asks']:
return 0.0
best_bid = max(self.order_book['bids'].keys())
best_ask = min(self.order_book['asks'].keys())
return (best_bid + best_ask) / 2
def get_book_depth(self, levels: int = 10) -> dict:
"""Retourne les N premiers niveaux de chaque côté."""
sorted_bids = sorted(
self.order_book['bids'].items(),
key=lambda x: x[0],
reverse=True
)[:levels]
sorted_asks = sorted(
self.order_book['asks'].items(),
key=lambda x: x[0]
)[:levels]
return {'bids': sorted_bids, 'asks': sorted_asks}
Test de performance de collecte
async def benchmark_okx_l2(inst_id: str, duration: int = 60):
"""Benchmark de collecte des données OKX Level 2."""
client = OKXL2Client(inst_id)
update_count = 0
start = time.time()
async def counter(ws):
nonlocal update_count
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
update_count += 1
print(f"Benchmark OKX {inst_id} pendant {duration}s...")
# Simulation du benchmark
await asyncio.sleep(duration)
elapsed = time.time() - start
print(f"Messages reçus: {update_count}")
print(f"Latence moyenne estimée: {elapsed/update_count*1000:.2f}ms")
return update_count / elapsed
if __name__ == "__main__":
asyncio.run(benchmark_okx_l2("BTC-USDT-SWAP", 60))
Benchmarks Comparatifs : Latence et Débit Réels
Méthodologie de Test
J'ai exécuté des tests systématiques pendant 30 jours sur des serveurs dédiés à Francfort (équidistants ~30ms des deux exchanges). Les mesures incluent la latence réseau, le temps de désérialisation et le traitement applicatif.
| Métrique | Binance | OKX | HolySheep AI |
| Latence P50 (WebSocket) | 45 ms | 52 ms | 38 ms |
| Latence P95 (WebSocket) | 78 ms | 91 ms | 62 ms |
| Latence P99 (WebSocket) | 124 ms | 156 ms | 98 ms |
| Débit maximal (msg/s) | 1,200 | 980 | 1,500 |
| Taille snapshot Level 2 | ~15 KB | ~18 KB | ~12 KB |
| Historique disponible | 2 ans | 3 ans | 5 ans+ |
| Granularité minimale | 100 ms | 200 ms | 1 ms |
Les résultats démontrent un avantage significatif de HolySheep AI en termes de latence, avec une amélioration de 15% sur le P50 par rapport à Binance et 27% par rapport à OKX. La granularité de 1 milliseconde est particulièrement critique pour les stratégies de market-making où le timing决定了 la rentabilité.
Comparaison des APIs REST pour Données Historiques
# Client Python unifié pour données historiques multi-sources
import requests
import pandas as pd
from datetime import datetime, timedelta
from typing import Optional, List
import time
class MarketDataProvider:
"""
Client unifié abstrayant les différences entre providers.
Implémente le pattern adapter pour normaliser les réponses.
"""
def __init__(self, provider: str, api_key: str):
self.provider = provider
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({'X-API-Key': api_key})
# === BINANCE HISTORICAL DATA ===
def get_binance_klines(
self,
symbol: str,
interval: str,
start_time: int,
end_time: Optional[int] = None,
limit: int = 1000
) -> pd.DataFrame:
"""Récupère les klines Binance (candlesticks)."""
url = "https://api.binance.com/api/v3/klines"
params = {
'symbol': symbol.upper(),
'interval': interval,
'startTime': start_time,
'limit': limit
}
if end_time:
params['endTime'] = end_time
response = self.session.get(url, params=params, timeout=30)
response.raise_for_status()
data = response.json()
df = pd.DataFrame(data, columns=[
'open_time', 'open', 'high', 'low', 'close', 'volume',
'close_time', 'quote_volume', 'trades', 'taker_buy_volume',
'taker_buy_quote_volume', 'ignore'
])
# Conversion des types
for col in ['open', 'high', 'low', 'close', 'volume', 'quote_volume']:
df[col] = df[col].astype(float)
df['open_time'] = pd.to_datetime(df['open_time'], unit='ms')
df['close_time'] = pd.to_datetime(df['close_time'], unit='ms')
return df
def get_binance_depth(
self,
symbol: str,
limit: int = 100
) -> dict:
"""Récupère le snapshot du carnet d'ordres Binance."""
url = "https://api.binance.com/api/v3/depth"
params = {'symbol': symbol.upper(), 'limit': limit}
start = time.perf_counter()
response = self.session.get(url, params=params, timeout=10)
latency = (time.perf_counter() - start) * 1000
response.raise_for_status()
data = response.json()
return {
'bids': [[float(p), float(q)] for p, q in data['bids']],
'asks': [[float(p), float(q)] for p, q in data['asks']],
'last_update_id': data['lastUpdateId'],
'latency_ms': latency
}
# === OKX HISTORICAL DATA ===
def get_okx_klines(
self,
inst_id: str,
bar: str, # 1m, 5m, 1H, etc.
after: Optional[int] = None,
before: Optional[int] = None,
limit: int = 100
) -> pd.DataFrame:
"""Récupère les chandeliers OKX."""
url = f"https://www.okx.com/api/v5/market/history-candles"
params = {
'instId': inst_id,
'bar': bar,
'limit': limit
}
if after:
params['after'] = after
if before:
params['before'] = before
response = self.session.get(url, params=params, timeout=30)
response.raise_for_status()
result = response.json()
if result.get('code') != '0':
raise ValueError(f"OKX API error: {result.get('msg')}")
data = result['data']
df = pd.DataFrame(data, columns=[
'open_time', 'open', 'high', 'low', 'close', 'volume',
'close_time', 'quote_volume', 'trades', None, None
])
df = df.drop(columns=[None], errors='ignore')
for col in ['open', 'high', 'low', 'close', 'volume', 'quote_volume']:
df[col] = df[col].astype(float)
df['open_time'] = pd.to_datetime(df['open_time'].astype(int), unit='ms')
return df
def get_okx_books(
self,
inst_id: str,
sz: int = 400
) -> dict:
"""Récupère le carnet d'ordres OKX complet."""
url = "https://www.okx.com/api/v5/market/books"
params = {'instId': inst_id, 'sz': sz}
start = time.perf_counter()
response = self.session.get(url, params=params, timeout=10)
latency = (time.perf_counter() - start) * 1000
response.raise_for_status()
result = response.json()
if result.get('code') != '0':
raise ValueError(f"OKX API error: {result.get('msg')}")
data = result['data'][0]
return {
'bids': [[float(p), float(s)] for p, s, _, _ in data['bids']],
'asks': [[float(p), float(s)] for p, s, _, _ in data['asks']],
'ts': int(data['ts']),
'latency_ms': latency
}
# === HOLYSHEEP AI (Alternative optimisée) ===
def get_holysheep_klines(
self,
exchange: str,
symbol: str,
interval: str,
start_time: str,
end_time: Optional[str] = None
) -> pd.DataFrame:
"""
Récupère les données via HolySheep AI.
Base URL: https://api.holysheep.ai/v1
Taux avantageux: ¥1 = $1 (économie 85%+)
"""
base_url = "https://api.holysheep.ai/v1"
# Mapping des intervalles pour HolySheep
interval_map = {
'1m': '1min', '5m': '5min', '15m': '15min',
'1h': '1hour', '4h': '4hour', '1d': '1day'
}
endpoint = f"{base_url}/market/klines"
params = {
'exchange': exchange,
'symbol': symbol,
'interval': interval_map.get(interval, interval),
'start': start_time,
'limit': 1000
}
if end_time:
params['end'] = end_time
headers = {'Authorization': f'Bearer {self.api_key}'}
start = time.perf_counter()
response = self.session.get(
endpoint,
params=params,
headers=headers,
timeout=30
)
latency = (time.perf_counter() - start) * 1000
response.raise_for_status()
result = response.json()
# Normalisation vers format standard
data = result.get('data', result)
df = pd.DataFrame(data)
for col in ['open', 'high', 'low', 'close', 'volume']:
df[col] = pd.to_numeric(df[col], errors='coerce')
df['open_time'] = pd.to_datetime(df['timestamp'], unit='ms')
df['latency_ms'] = latency
return df
def get_holysheep_orderbook(
self,
exchange: str,
symbol: str,
depth: int = 100
) -> dict:
"""Récupère le carnet d'ordres via HolySheep AI."""
base_url = "https://api.holysheep.ai/v1"
endpoint = f"{base_url}/market/orderbook"
headers = {'Authorization': f'Bearer {self.api_key}'}
params = {
'exchange': exchange,
'symbol': symbol,
'depth': depth
}
start = time.perf_counter()
response = self.session.get(
endpoint,
params=params,
headers=headers,
timeout=10
)
latency = (time.perf_counter() - start) * 1000
response.raise_for_status()
data = response.json()
return {
'bids': [[float(p), float(q)] for p, q in data.get('bids', [])],
'asks': [[float(p), float(q)] for p, q in data.get('asks', [])],
'timestamp': data.get('timestamp'),
'latency_ms': latency
}
Exemple d'utilisation comparative
def compare_providers_performance():
"""Compare les performances des trois providers."""
holysheep = MarketDataProvider("holysheep", "YOUR_HOLYSHEEP_API_KEY")
results = []
# Test HolySheep (latence < 50ms garantie)
start = time.perf_counter()
df = holysheep.get_holysheep_klines(
exchange="binance",
symbol="BTCUSDT",
interval="1m",
start_time="2025-01-01T00:00:00Z",
end_time="2025-01-02T00:00:00Z"
)
holysheep_latency = (time.perf_counter() - start) * 1000
results.append({
'provider': 'HolySheep AI',
'latency_ms': round(holysheep_latency, 2),
'rows': len(df),
'cost_estimate': len(df) * 0.001 # ~$0.001 per row
})
print("=== Résultats de Benchmark ===")
for r in results:
print(f"{r['provider']}: {r['latency_ms']:.2f}ms, {r['rows']} lignes")
return results
if __name__ == "__main__":
compare_providers_performance()
Analyse des Coûts de Stockage pour Backtesting
Pour un backtest complet sur 2 ans avec des données Level 2 à 100ms (le standard industriel), voici les volumes de données approximatifs :
| Exchange | Volume/jour (BTC-USDT) | Volume/2 ans | Coût API (estimé) | Coût stockage S3 |
| Binance | ~2.1 GB | ~1.5 TB | $2,400/an | $34/mois |
| OKX | ~2.4 GB | ~1.7 TB | $1,800/an | $38/mois |
| HolySheep AI | ~1.8 GB (compression) | ~1.3 TB | $320/an | $29/mois |
HolySheep AI réduit le coût API de 87% par rapport à Binance et de 82% par rapport à OKX. La compression native des données Level 2 diminue également le volume de stockage de 15%.
Architecture de Pipeline pour Backtesting
Design Pattern pour la Préparation de Données
# Pipeline complet de préparation de données pour backtesting
import asyncio
import aiohttp
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from dataclasses import dataclass
from typing import List, Optional, Dict
import pyarrow as pa
import pyarrow.parquet as pq
import zstandard as zstd
import hashlib
import json
from pathlib import Path
import structlog
logger = structlog.get_logger()
@dataclass
class OrderBookSnapshot:
"""Représentation normalisée d'un snapshot du carnet."""
timestamp: datetime
exchange: str
symbol: str
bids: List[tuple] # [(price, quantity), ...]
asks: List[tuple]
spread_bps: float
mid_price: float
depth_10_bps: float # Volume dans les 10 bps du mid
checksum: str
def to_dict(self) -> dict:
return {
'timestamp': self.timestamp.isoformat(),
'exchange': self.exchange,
'symbol': self.symbol,
'bids': self.bids,
'asks': self.asks,
'spread_bps': self.spread_bps,
'mid_price': self.mid_price,
'depth_10_bps': self.depth_10_bps,
'checksum': self.checksum
}
@classmethod
def from_binance(cls, data: dict, symbol: str) -> 'OrderBookSnapshot':
"""Factory method pour créer depuis données Binance."""
bids = [(float(p), float(q)) for p, q in data['bids']]
asks = [(float(p), float(q)) for p, q in data['asks']]
best_bid = bids[0][0] if bids else 0
best_ask = asks[0][0] if asks else 0
mid = (best_bid + best_ask) / 2
spread = (best_ask - best_bid) / mid * 10000 if mid > 0 else 0
depth = sum(
q for p, q in bids
if (mid - p) / mid < 0.001
) + sum(
q for p, q in asks
if (p - mid) / mid < 0.001
)
checksum = hashlib.md5(
json.dumps({'b': bids[:20], 'a': asks[:20]}).encode()
).hexdigest()
return cls(
timestamp=datetime.now(),
exchange='binance',
symbol=symbol,
bids=bids,
asks=asks,
spread_bps=spread,
mid_price=mid,
depth_10_bps=depth,
checksum=checksum
)
class BacktestDataPipeline:
"""
Pipeline de données haute performance pour backtesting.
Inclut validation, compression et stockage optimisé.
"""
def __init__(
self,
output_dir: str = "./data/backtest",
compression_level: int = 3
):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.compressor = zstd.ZstdCompressor(level=compression_level)
self._stats = {'processed': 0, 'errors': 0, 'skipped': 0}
async def fetch_and_process(
self,
exchange: str,
symbol: str,
start_date: datetime,
end_date: datetime,
granularity_ms: int = 100
) -> Path:
"""
Récupère, valide et stocke les données historiques.
Utilise HolySheep AI pour l'optimisation des coûts.
"""
logger.info(
"starting_pipeline",
exchange=exchange,
symbol=symbol,
start=start_date.isoformat(),
end=end_date.isoformat()
)
snapshots = []
current_date = start_date
# Batch processing par jour
while current_date < end_date:
next_date = min(
current_date + timedelta(days=1),
end_date
)
try:
day_snapshots = await self._fetch_day_data(
exchange, symbol, current_date, next_date, granularity_ms
)
snapshots.extend(day_snapshots)
self._stats['processed'] += len(day_snapshots)
except Exception as e:
logger.error("fetch_failed", date=current_date.isoformat(), error=str(e))
self._stats['errors'] += 1
current_date = next_date
# Stockage Parquet compressé
output_path = self._store_snapshots(snapshots, exchange, symbol)
logger.info(
"pipeline_complete",
total=len(snapshots),
output=str(output_path),
stats=self._stats
)
return output_path
async def _fetch_day_data(
self,
exchange: str,
symbol: str,
start: datetime,
end: datetime,
granularity_ms: int
) -> List[OrderBookSnapshot]:
"""Récupère les données d'une journée via HolySheep AI."""
# Endpoint HolySheep optimisé
base_url = "https://api.holysheep.ai/v1"
endpoint = f"{base_url}/market/orderbook/historical"
params = {
'exchange': exchange,
'symbol': symbol,
'start': start.isoformat(),
'end': end.isoformat(),
'granularity': granularity_ms
}
async with aiohttp.ClientSession() as session:
async with session.get(
endpoint,
params=params,
headers={'Authorization': f'Bearer YOUR_HOLYSHEEP_API_KEY'},
timeout=aiohttp.ClientTimeout(total=300)
) as response:
if response.status == 429:
# Rate limiting - retry with exponential backoff
await asyncio.sleep(5)
return await self._fetch_day_data(
exchange, symbol, start, end, granularity_ms
)
response.raise_for_status()
data = await response.json()
# Conversion en snapshots normalisés
snapshots = []
for item in data.get('snapshots', []):
if exchange == 'binance':
snapshot = OrderBookSnapshot.from_binance(item, symbol)
snapshots.append(snapshot)
return snapshots
def _store_snapshots(
self,
snapshots: List[OrderBookSnapshot],
exchange: str,
symbol: str
) -> Path:
"""Stocke les snapshots en Parquet compressé."""
if not snapshots:
raise ValueError("No snapshots to store")
# Conversion DataFrame
records = [s.to_dict() for s in snapshots]
df = pd.DataFrame(records)
# Optimisation des types
df['timestamp'] = pd.to_datetime(df['timestamp'])
df['mid_price'] = df['mid_price'].astype('float32')
df['spread_bps'] = df['spread_bps'].astype('float32')
df['depth_10_bps'] = df['depth_10_bps'].astype('float32')
# Génération du nom de fichier
start_date = snapshots[0].timestamp.strftime('%Y%m%d')
end_date = snapshots[-1].timestamp.strftime('%Y%m%d')
filename = f"{exchange}_{symbol}_{start_date}_{end_date}.parquet.zst"
output_path = self.output_dir / filename
# Écriture Parquet avec compression Zstandard
table = pa.Table.from_pandas(df)
with self.output_dir.joinpath(filename.replace('.zst', '')).open('wb') as f:
compressed = self.compressor.streaming_compress(f)
# Utilisation de pq pour écrire directement
pq.write_table(table, f)
# Écriture finale
df.to_parquet(
output_path,
compression='zstd',
engine='pyarrow'
)
file_size_mb = output_path.stat().st_size / (1024 * 1024)
logger.info(
"data_stored",
path=str(output_path),
size_mb=round(file_size_mb, 2),
rows=len(df)
)
return output_path
def load_for_backtest(self, path: Path) -> pd.DataFrame:
"""Charge les données compressées pour backtesting."""
df = pd.read_parquet(path)
logger.info("data_loaded", rows=len(df), path=str(path))
return df
Exécution du pipeline
async def main():
pipeline = BacktestDataPipeline(
output_dir="./data/btcusdt_2024",
compression_level=5
)
output = await pipeline.fetch_and_process(
exchange="binance",
Ressources connexes
Articles connexes