En tant qu'ingénieur en trading algorithmique ayant développé des systèmes d'arbitrage crypto pendant plus de trois ans, je témoigne : la difficulté principale ne réside pas dans la conception de la stratégie, mais dans l'obtention de données fiables et dans la construction d'un framework de backtesting suffisamment rapide pour valider vos hypothèses avant de risquer du capital réel. J'ai testé des dizaines de configurations, brûlé des heures de calcul sur des données mal synchronisées, et développé ce framework qui condense tout ce que j'aurais aimé connaître en starting-block de ma carrière quantitatif.

Cet article détaille une architecture complète en Python permettant de récupérer des données OHLCV (Open, High, Low, Close, Volume) sur plusieurs exchanges simultanément, de détecter des opportunités d'arbitrage triangulaire et inter-exchange, puis de simuler ces stratégies sur un historique de plusieurs années avec une précision de latence inférieure à la milliseconde.

Architecture du Système de Récupération de Données Multi-Exchange

Le cœur du système repose sur une architecture asynchrone utilisant asyncio et aiohttp pour maximiser le débit de récupération tout en respectant les limites de rate-limiting de chaque plateforme. Chaque exchange (Binance, OKX, Bybit) possède sa propre structure d'API REST que notre framework unifie derrière une interface commune.

Installation et Configuration de l'Environnement

# Installation des dépendances requises
pip install aiohttp asyncio pandas numpy python-dotenv
pip install ccxt  # Couche d'abstraction multi-exchange
pip install pandas-ta  # Indicateurs techniques pour le backtesting
pip install scipy  # Optimisation des paramètres de stratégie

Structure du projet recommandée

project/ ├── config/ │ ├── __init__.py │ ├── api_credentials.py # Clés API exchange │ └── holy_config.py # Configuration HolySheep AI ├── data/ │ ├── raw/ # Données brutes K-line │ ├── processed/ # Données nettoyées et alignées │ └── backtest_results/ # Résultats de simulation ├── src/ │ ├── collectors/ │ │ ├── __init__.py │ │ ├── base_collector.py # Classe de base abstraite │ │ ├── binance_collector.py # Collecteur Binance │ │ ├── okx_collector.py # Collecteur OKX │ │ └── bybit_collector.py # Collecteur Bybit │ ├── arbitrage/ │ │ ├── __init__.py │ │ ├── triangular.py # Arbitrage triangulaire intra-exchange │ │ └── exchange.py # Arbitrage inter-exchange │ └── backtesting/ │ ├── __init__.py │ ├── engine.py # Moteur de simulation │ └── performance.py # Métriques de performance ├── notebooks/ │ └── analysis.ipynb # Analyse exploratoire ├── requirements.txt └── .env # Variables d'environnement

Collecteur de Données K-Line avec Gestion du Rate-Limiting

import asyncio
import aiohttp
import pandas as pd
from datetime import datetime, timedelta
from typing import Dict, List, Optional
from dataclasses import dataclass
import ccxt
import time

@dataclass
class KLineData:
    """Structure standardisée pour les données K-line"""
    timestamp: int
    open: float
    high: float
    low: float
    close: float
    volume: float
    quote_volume: float
    exchange: str
    symbol: str

class BaseCollector:
    """Classe de base pour tous les collecteurs d exchange"""
    
    def __init__(self, api_key: str, api_secret: str, rate_limit: int = 1200):
        self.api_key = api_key
        self.api_secret = api_secret
        self.rate_limit = rate_limit  # Requêtes par minute
        self.request_count = 0
        self.last_reset = time.time()
        self.session: Optional[aiohttp.ClientSession] = None
    
    async def _acquire_rate_limit(self):
        """Gestion intelligente du rate-limiting"""
        current_time = time.time()
        if current_time - self.last_reset >= 60:
            self.request_count = 0
            self.last_reset = current_time
        
        if self.request_count >= self.rate_limit:
            wait_time = 60 - (current_time - self.last_reset)
            await asyncio.sleep(max(0, wait_time))
            self.request_count = 0
            self.last_reset = time.time()
        
        self.request_count += 1
    
    async def fetch_klines(
        self,
        symbol: str,
        timeframe: str = "1m",
        since: Optional[int] = None,
        limit: int = 1000
    ) -> List[KLineData]:
        """Méthode abstraite à implémenter par chaque exchange"""
        raise NotImplementedError
    
    async def fetch_historical(
        self,
        symbol: str,
        start_date: datetime,
        end_date: datetime,
        timeframe: str = "1m"
    ) -> pd.DataFrame:
        """Récupère l historique complet avec pagination automatique"""
        all_klines = []
        current_time = start_date
        
        timeframe_minutes = {
            "1m": 1, "5m": 5, "15m": 15, "1h": 60,
            "4h": 240, "1d": 1440, "1w": 10080
        }
        
        interval_ms = timeframe_minutes.get(timeframe, 1) * 60 * 1000
        batch_size = 1000
        
        while current_time < end_date:
            since = int(current_time.timestamp() * 1000)
            end_timestamp = int(end_date.timestamp() * 1000)
            max_since = since + (batch_size * interval_ms)
            
            klines = await self.fetch_klines(
                symbol=symbol,
                timeframe=timeframe,
                since=since,
                limit=min(batch_size, 1000)
            )
            
            if not klines:
                break
                
            all_klines.extend(klines)
            last_timestamp = klines[-1].timestamp
            
            if last_timestamp >= max_since - interval_ms:
                current_time = datetime.fromtimestamp(
                    (last_timestamp + interval_ms) / 1000
                )
            else:
                break
            
            await asyncio.sleep(0.1)  # Éviter la surcharge
        
        df = pd.DataFrame([
            {
                "timestamp": k.timestamp,
                "datetime": pd.to_datetime(k.timestamp, unit="ms"),
                "open": k.open,
                "high": k.high,
                "low": k.low,
                "close": k.close,
                "volume": k.volume,
                "quote_volume": k.quote_volume,
                "exchange": k.exchange,
                "symbol": k.symbol
            }
            for k in all_klines
        ])
        
        return df.drop_duplicates(subset=["timestamp", "exchange"])


class BinanceCollector(BaseCollector):
    """Collecteur spécifique pour Binance Spot et Futures"""
    
    BASE_URL = "https://api.binance.com"
    
    async def fetch_klines(
        self,
        symbol: str,
        timeframe: str = "1m",
        since: Optional[int] = None,
        limit: int = 1000
    ) -> List[KLineData]:
        await self._acquire_rate_limit()
        
        # Mapping timeframe Binance
        binance_tf = {
            "1m": "1m", "5m": "5m", "15m": "15m",
            "1h": "1h", "4h": "4h", "1d": "1d"
        }.get(timeframe, "1m")
        
        url = f"{self.BASE_URL}/api/v3/klines"
        params = {
            "symbol": symbol.upper(),
            "interval": binance_tf,
            "limit": limit
        }
        if since:
            params["startTime"] = since
        
        async with self.session.get(url, params=params) as response:
            if response.status == 429:
                await asyncio.sleep(60)
                return await self.fetch_klines(symbol, timeframe, since, limit)
            
            data = await response.json()
            
            return [
                KLineData(
                    timestamp=int(k[0]),
                    open=float(k[1]),
                    high=float(k[2]),
                    low=float(k[3]),
                    close=float(k[4]),
                    volume=float(k[5]),
                    quote_volume=float(k[7]),
                    exchange="binance",
                    symbol=symbol
                )
                for k in data
            ]


class OKXCollector(BaseCollector):
    """Collecteur spécifique pour OKX"""
    
    BASE_URL = "https://www.okx.com"
    
    async def fetch_klines(
        self,
        symbol: str,
        timeframe: str = "1m",
        since: Optional[int] = None,
        limit: int = 1000
    ) -> List[KLineData]:
        await self._acquire_rate_limit()
        
        okx_tf = {
            "1m": "1m", "5m": "5m", "15m": "15m",
            "1h": "1H", "4h": "4H", "1d": "1D"
        }.get(timeframe, "1m")
        
        # OKX utilise des paires au format BTC-USDT
        okx_symbol = symbol.replace("/", "-")
        
        url = f"{self.BASE_URL}/api/v5/market/history-candles"
        params = {
            "instId": okx_symbol.upper(),
            "bar": okx_tf,
            "limit": limit
        }
        if since:
            params["after"] = since
        
        async with self.session.get(url, params=params) as response:
            data = await response.json()
            
            if data.get("code") != "0":
                raise Exception(f"OKX API Error: {data.get('msg')}")
            
            candles = data.get("data", [])
            
            return [
                KLineData(
                    timestamp=int(c[0]),
                    open=float(c[1]),
                    high=float(c[2]),
                    low=float(c[3]),
                    close=float(c[4]),
                    volume=float(c[5]),
                    quote_volume=float(c[7]),
                    exchange="okx",
                    symbol=symbol
                )
                for c in candles
            ]


class BybitCollector(BaseCollector):
    """Collecteur spécifique pour Bybit"""
    
    BASE_URL = "https://api.bybit.com"
    
    async def fetch_klines(
        self,
        symbol: str,
        timeframe: str = "1m",
        since: Optional[int] = None,
        limit: int = 1000
    ) -> List[KLineData]:
        await self._acquire_rate_limit()
        
        bybit_tf = {
            "1m": "1", "5m": "5", "15m": "15",
            "1h": "60", "4h": "240", "1d": "D"
        }.get(timeframe, "1")
        
        url = f"{self.BASE_URL}/v5/market/kline"
        params = {
            "category": "spot",
            "symbol": symbol.upper(),
            "interval": bybit_tf,
            "limit": limit
        }
        if since:
            params["start"] = since // 1000
        
        async with self.session.get(url, params=params) as response:
            data = await response.json()
            
            if data.get("retCode") != 0:
                raise Exception(f"Bybit API Error: {data.get('retMsg')}")
            
            klines = data.get("result", {}).get("list", [])
            
            return [
                KLineData(
                    timestamp=int(k[0]),
                    open=float(k[1]),
                    high=float(k[2]),
                    low=float(k[3]),
                    close=float(k[4]),
                    volume=float(k[5]),
                    quote_volume=float(k[7]),
                    exchange="bybit",
                    symbol=symbol
                )
                for k in reversed(klines)  # Bybit retourne les données en ordre décroissant
            ]

Intégration avec l'Analyse IA via HolySheep AI

Pour optimiser l'analyse des patterns de prix et la détection automatisée des opportunités d'arbitrage, j'intègre désormais les capacités de modèle de langue de HolySheep AI directement dans mon pipeline de données. L'API HolySheep offre une latence inférieure à 50ms et des tarifs significativement inférieurs aux alternatives mainstream : DeepSeek V3.2 à 0,42 dollar par million de tokens contre 8 dollars pour GPT-4.1, soit une économie de plus de 85%.

L'intégration se fait via l'endpoint https://api.holysheep.ai/v1 avec votre clé API personnelle. Vous pouvez vous S'inscrire ici pour obtenir des crédits gratuits de démarrage et accéder à des méthodes de paiement locales comme WeChat Pay et Alipay, idéales pour les traders basés en Asie ou travaillant avec des volumes importants de données chinoises.

import aiohttp
import json
from typing import Dict, List, Optional
from dataclasses import dataclass

@dataclass
class ArbitrageOpportunity:
    """Opportunité d arbitrage détectée"""
    timestamp: int
    pair1: str  # BTC/USDT
    pair2: str  # ETH/USDT
    pair3: str  # ETH/BTC
    route: str  # "BTC→ETH→USDT→BTC"
    profit_pct: float
    volume_usd: float
    confidence: float
    exchanges: List[str]
    raw_prices: Dict[str, float]

class HolySheepAnalyzer:
    """Analyseur IA pour la détection de patterns et l optimisation de stratégies"""
    
    BASE_URL = "https://api.holysheep.ai/v1"
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.model = "deepseek-v3.2"  # Modèle économique, excellent pour l'analyse
        self.session: Optional[aiohttp.ClientSession] = None
    
    async def _request(self, messages: List[Dict]) -> str:
        """Requête générique à l API HolySheep"""
        if not self.session:
            self.session = aiohttp.ClientSession(
                headers={
                    "Authorization": f"Bearer {self.api_key}",
                    "Content-Type": "application/json"
                }
            )
        
        payload = {
            "model": self.model,
            "messages": messages,
            "temperature": 0.3,  # Température basse pour des analyses déterministes
            "max_tokens": 2000
        }
        
        async with self.session.post(
            f"{self.BASE_URL}/chat/completions",
            json=payload
        ) as response:
            if response.status != 200:
                error = await response.text()
                raise Exception(f"HolySheep API Error: {error}")
            
            result = await response.json()
            return result["choices"][0]["message"]["content"]
    
    async def analyze_arbitrage_window(
        self,
        opportunities: List[ArbitrageOpportunity],
        market_context: Dict
    ) -> Dict:
        """Demande à l IA d analyser une fenêtre d opportunités d arbitrage"""
        
        prompt = f"""Analyse les opportunités d arbitrage suivantes pour la période 
        {market_context.get('period', 'recent')}:

Opportunités détectées:
{json.dumps([{
    'route': o.route,
    'profit': f"{o.profit_pct:.4f}%",
    'volume': f"${o.volume_usd:,.2f}",
    'confiance': f"{o.confidence*100:.1f}%"
} for o in opportunities[:20]], indent=2)}

Contexte marché:
- Volatilité: {market_context.get('volatility', 'N/A')}
- Volume 24h: {market_context.get('volume_24h', 'N/A')}
- Spread moyen: {market_context.get('avg_spread', 'N/A')}

 Fournis:
1. Évaluation du risque globale (faible/moyen/élevé)
2. Meilleure opportunité avec justification
3. Paramètres de position recommandés (size, stop-loss)
4. Moments à éviter"""

        messages = [
            {"role": "system", "content": "Tu es un analyste quantitatif expert en trading crypto."},
            {"role": "user", "content": prompt}
        ]
        
        return await self._request(messages)
    
    async def optimize_strategy_params(
        self,
        historical_results: Dict,
        target_metrics: Dict
    ) -> Dict:
        """Optimise les paramètres de stratégie basés sur l historique"""
        
        prompt = f"""Optimise les paramètres pour cette stratégie d arbitrage:

Résultats historiques:
{json.dumps(historical_results, indent=2)}

Métriques cibles:
- Sharpe ratio minimum: {target_metrics.get('min_sharpe', 1.5)}
- Drawdown maximum: {target_metrics.get('max_drawdown', 0.1)}%
- Profit mensuel minimum: ${target_metrics.get('min_monthly_profit', 1000)}

Propose une optimisation des hyperparamètres avec justification mathématique."""

        messages = [
            {"role": "system", "content": "Tu es un expert en optimisation de stratégies de trading."},
            {"role": "user", "content": prompt}
        ]
        
        return await self._request(messages)
    
    async def detect_anomalies(self, price_data: pd.DataFrame) -> List[Dict]:
        """Détecte les anomalies dans les données de prix"""
        
        # Préparation des statistiques
        stats = {
            "paires_analysées": price_data["symbol"].nunique(),
            "période": f"{price_data['timestamp'].min()} - {price_data['timestamp'].max()}",
            "nb_obs": len(price_data),
            "volatilité_moyenne": price_data.groupby("symbol")["close"].pct_change().std().mean(),
            "spike_prix_detectes": len(price_data[price_data["close"].pct_change().abs() > 0.05])
        }
        
        prompt = f"""Analyse ces statistiques de données de marché:

{json.dumps(stats, indent=2)}

Identifie:
1. Anomalies potentielles (spikes, gaps, données manquantes)
2. Périodes de liquidité anormale
3. Recommandations de nettoyage de données
4. Risques de survivorship bias"""

        messages = [
            {"role": "system", "content": "Tu es un data scientist expert en finance quantitative."},
            {"role": "user", "content": prompt}
        ]
        
        return await self._request(messages)


async def example_pipeline():
    """Exemple complet d utilisation du pipeline"""
    
    # Initialisation
    holy_sheep = HolySheepAnalyzer(api_key="YOUR_HOLYSHEEP_API_KEY")
    
    # Contexte marché à analyser
    market_context = {
        "period": "2024-01-15 14:00 - 15:00 UTC",
        "volatility": "Élevée (VIX crypto > 45)",
        "volume_24h": "$45.2B",
        "avg_spread": "0.08%"
    }
    
    # Opportunités fictives pour l'exemple
    opportunities = [
        ArbitrageOpportunity(
            timestamp=1705324800000,
            pair1="BTC/USDT",
            pair2="ETH/USDT",
            pair3="ETH/BTC",
            route="BTC→ETH→USDT→BTC",
            profit_pct=0.12,
            volume_usd=50000,
            confidence=0.85,
            exchanges=["binance", "okx"],
            raw_prices={"binance": {"BTC": 42000, "ETH": 2650}, 
                       "okx": {"BTC": 42005, "ETH": 2648}}
        ),
        ArbitrageOpportunity(
            timestamp=1705324860000,
            pair1="ETH/USDT",
            pair2="BTC/USDT",
            pair3="BTC/ETH",
            route="ETH→BTC→USDT→ETH",
            profit_pct=0.08,
            volume_usd=35000,
            confidence=0.72,
            exchanges=["bybit", "binance"],
            raw_prices={"bybit": {"ETH": 2652, "BTC": 42010},
                       "binance": {"ETH": 2650, "BTC": 42000}}
        )
    ]
    
    # Analyse IA
    analysis = await holy_sheep.analyze_arbitrage_window(
        opportunities, market_context
    )
    
    print("=== ANALYSE HOLYSHEEP AI ===")
    print(analysis)
    
    # Fermeture de session
    await holy_sheep.session.close()


Exécution

if __name__ == "__main__": asyncio.run(example_pipeline())

Détection d'Arbitrage Triangulaire et Inter-Exchange

Le framework implémente deux types principaux de stratégies d'arbitrage : l'arbitrage triangulaire (exploitant les inefficiencies entre trois paires sur un même exchange) et l'arbitrage inter-exchange (profitant des différences de prix pour un même actif entre plateformes).

import pandas as pd
import numpy as np
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass
from itertools import permutations
import asyncio

@dataclass
class TriangularOpportunity:
    """Opportunité d arbitrage triangulaire"""
    exchange: str
    pair1: str  # Base → Quote
    pair2: str
    pair3: str
    route: str
    initial_amount: float
    fees: float
    gross_profit_pct: float
    net_profit_pct: float
    timestamp: int
    data: Dict[str, Dict[str, float]]

class ArbitrageDetector:
    """Détecteur d opportunités d arbitrage multi-stratégie"""
    
    # Frais de transaction par exchange (Maker/Taker combinés)
    FEES = {
        "binance": {"maker": 0.001, "taker": 0.001},  # 0.1%
        "okx": {"maker": 0.0015, "taker": 0.002},      # 0.15-0.2%
        "bybit": {"maker": 0.001, "taker": 0.002}      # 0.1-0.2%
    }
    
    def __init__(self, min_profit_threshold: float = 0.001):
        self.min_profit_threshold = min_profit_threshold
        self.opportunities: List[TriangularOpportunity] = []
    
    def calculate_triangular_arbitrage(
        self,
        prices: Dict[str, Tuple[float, float]],
        symbol: str = "BTC"
    ) -> List[TriangularOpportunity]:
        """
        Calcule les opportunités d arbitrage triangulaire
        
        prices: Dict avec format {"PAIR": (bid, ask)}
        Exemple: {"BTC/USDT": (42000, 42001), "ETH/BTC": (0.063, 0.0631), "ETH/USDT": (2646, 2647)}
        """
        opportunities = []
        
        # Extraction des prix (bid = meilleur acheteur, ask = meilleur vendeur)
        btc_usdt = prices.get("BTC/USDT", prices.get("BTCUSDT"))
        eth_btc = prices.get("ETH/BTC", prices.get("ETHBTC"))
        eth_usdt = prices.get("ETH/USDT", prices.get("ETHUSDT"))
        
        if not all([btc_usdt, eth_btc, eth_usdt]):
            return opportunities
        
        btc_usdt_bid, btc_usdt_ask = btc_usdt
        eth_btc_bid, eth_btc_ask = eth_btc
        eth_usdt_bid, eth_usdt_ask = eth_usdt
        
        # Route 1: USDT → BTC → ETH → USDT
        # Acheter BTC (payer USDT), Acheter ETH (payer BTC), Vendre ETH (recevoir USDT)
        route1_gross = (1 / btc_usdt_ask) * (1 / eth_btc_ask) * eth_usdt_bid
        
        # Route 2: USDT → ETH → BTC → USDT
        # Acheter ETH (payer USDT), Vendre ETH (recevoir BTC), Vendre BTC (recevoir USDT)
        route2_gross = (1 / eth_usdt_ask) * eth_btc_bid * btc_usdt_bid
        
        # Route 3: BTC → USDT → ETH → BTC
        route3_gross = btc_usdt_bid * eth_usdt_ask * eth_btc_bid
        
        # Route 4: ETH → BTC → USDT → ETH
        route4_gross = (1 / eth_btc_ask) * btc_usdt_bid * eth_usdt_ask
        
        routes = [
            ("USDT→BTC→ETH→USDT", route1_gross, "BTC/USDT", "ETH/BTC", "ETH/USDT"),
            ("USDT→ETH→BTC→USDT", route2_gross, "ETH/USDT", "ETH/BTC", "BTC/USDT"),
            ("BTC→USDT→ETH→BTC", route3_gross, "BTC/USDT", "ETH/USDT", "ETH/BTC"),
            ("ETH→BTC→USDT→ETH", route4_gross, "ETH/BTC", "BTC/USDT", "ETH/USDT"),
        ]
        
        for route_name, gross, p1, p2, p3 in routes:
            # Calcul des frais (3 transactions)
            total_fees = 0.006  # ~0.2% par transaction * 3
            
            net_profit = (gross - 1) - total_fees
            
            if net_profit > self.min_profit_threshold:
                opportunities.append(TriangularOpportunity(
                    exchange="binance",
                    pair1=p1,
                    pair2=p2,
                    pair3=p3,
                    route=route_name,
                    initial_amount=10000,
                    fees=total_fees,
                    gross_profit_pct=(gross - 1) * 100,
                    net_profit_pct=net_profit * 100,
                    timestamp=int(pd.Timestamp.now().timestamp() * 1000),
                    data={
                        "BTC/USDT": {"bid": btc_usdt_bid, "ask": btc_usdt_ask},
                        "ETH/BTC": {"bid": eth_btc_bid, "ask": eth_btc_ask},
                        "ETH/USDT": {"bid": eth_usdt_bid, "ask": eth_usdt_ask}
                    }
                ))
        
        return opportunities
    
    async def detect_inter_exchange_arbitrage(
        self,
        all_prices: Dict[str, Dict[str, Tuple[float, float]]],
        min_profit: float = 0.001
    ) -> List[Dict]:
        """
        Détecte les opportunités d arbitrage inter-exchange
        
        all_prices: {
            "binance": {"BTC/USDT": (41950, 41952), ...},
            "okx": {"BTC/USDT": (41955, 41957), ...},
            "bybit": {"BTC/USDT": (41948, 41950), ...}
        }
        """
        opportunities = []
        exchanges = list(all_prices.keys())
        
        # Comparaison des prix pour chaque paire
        for pair in ["BTC/USDT", "ETH/USDT", "SOL/USDT", "BNB/USDT"]:
            best_buy = None  # Prix le plus bas (où acheter)
            best_sell = None  # Prix le plus haut (où vendre)
            
            for exchange, prices in all_prices.items():
                if pair not in prices:
                    continue
                    
                bid, ask = prices[pair]
                
                if best_buy is None or ask < best_buy[1]:
                    best_buy = (exchange, ask)
                
                if best_sell is None or bid > best_sell[1]:
                    best_sell = (exchange, bid)
            
            if best_buy and best_sell and best_buy[0] != best_sell[0]:
                profit_pct = ((best_sell[1] / best_buy[1]) - 1) * 100
                fees = 0.004  # Frais achat + vente
                net_profit = profit_pct - (fees * 100)
                
                if net_profit > min_profit * 100:
                    opportunities.append({
                        "pair": pair,
                        "buy_exchange": best_buy[0],
                        "sell_exchange": best_sell[0],
                        "buy_price": best_buy[1],
                        "sell_price": best_sell[1],
                        "gross_profit_pct": profit_pct,
                        "net_profit_pct": net_profit,
                        "estimated_volume": 10000,
                        "potential_profit": (10000 / best_buy[1]) * (1 - fees) * best_sell[1] * (1 - fees) - 10000,
                        "timestamp": int(pd.Timestamp.now().timestamp() * 1000)
                    })
        
        return opportunities
    
    def scan_all_pairs(self, prices_df: pd.DataFrame, exchanges: List[str]) -> pd.DataFrame:
        """
        Scan toutes les combinaisons de paires pour des opportunités
        """
        results = []
        
        for exchange in exchanges:
            exchange_data = prices_df[prices_df["exchange"] == exchange]
            
            # Construction du dictionnaire de prix
            price_dict = {}
            for _, row in exchange_data.iterrows():
                key = row["symbol"]
                price_dict[key] = (row["close"] * 0.9999, row["close"] * 1.0001)  # Simuler bid/ask
            
            # Calcul des opportunités triangulaires
            opps = self.calculate_triangular_arbitrage(price_dict)
            results.extend(opps)
        
        if results:
            return pd.DataFrame([
                {
                    "exchange": o.exchange,
                    "route": o.route,
                    "gross_profit_pct": o.gross_profit_pct,
                    "net_profit_pct": o.net_profit_pct,
                    "fees": o.fees,
                    "timestamp": o.timestamp
                }
                for o in results
            ])
        
        return pd.DataFrame()


Exemple d utilisation

async def main(): detector = ArbitrageDetector(min_profit_threshold=0.001) # Données de prix simulées (en réalité, récupérées via les collecteurs) sample_prices = { "BTC/USDT": (41950.00, 41952.50), "ETH/BTC": (0.06310, 0.06312), "ETH/USDT": (2648.00, 2649.00) } # Calcul des opportunités triangulaires opps = detector.calculate_triangular_arbitrage(sample_prices) print("=== OPPORTUNITÉS TRIANGULAIRES ===") for opp in opps: print(f"Route: {opp.route}") print(f" Profit brut: {opp.gross_profit_pct:.4f}%") print(f" Frais: {opp.fees * 100:.2f}%") print(f" Profit net: {opp.net_profit_pct:.4f}%") print() # Arbitrage inter-exchange inter_exchange_prices = { "binance": {"BTC/USDT": (41950, 41952)}, "okx": {"BTC/USDT": (41955, 41958)}, "bybit": {"BTC/USDT": (41948, 41950)} } inter_opps = await detector.detect_inter_exchange_arbitrage(inter_exchange_prices) print("=== OPPORTUNITÉS INTER-EXCHANGE ===") for opp in inter_opps: print(f"{opp['pair']}: Acheter sur {opp['buy_exchange']} à {opp['buy_price']}, " f"Vendre sur {opp['sell_exchange']} à {opp['sell_price']}") print(f" Profit net: {opp['net_profit_pct']:.4f}%") if __name__ == "__main__": asyncio.run(main())

Moteur de Backtesting avec Gestion du Slippage

Le backtesting constitue la phase critique de validation. Un moteur réaliste doit reproduire fidèlement les conditions réelles de marché : slippage, latence d'exécution, frais de transaction variables, et impact sur le prix en fonction de la taille de l'ordre.

import pandas as pd
import numpy as np
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Callable
from datetime import datetime
from enum import Enum
import json

class OrderType(Enum):
    MARKET = "market"
    LIMIT = "limit"
    STOP_LOSS = "stop_loss"
    TAKE_PROFIT = "take_profit"

@dataclass
class Order:
    timestamp: int
    symbol: str
    side: str  # "buy" ou "sell"
    order_type: OrderType
    amount: float
    price: Optional[float] = None
    executed_price: Optional[float] = None
    fee: float = 0.0
    status: str = "pending"

@dataclass
class Position:
    symbol: str
    side: str
    entry_price: float
    amount: float
    entry_time: int
    pnl: float = 0.0
    realized_pnl: float = 0.0

@dataclass
class BacktestConfig:
    initial_capital: float = 100000
    commission_rate: float = 0.001  # 0.1%
    slippage_pct: float = 0.0005    # 0.05%
    max_position_size_pct: float = 0.1  # Max 10% du capital par position
    leverage: float = 1.0
    risk_free_rate: float = 0.02   # Taux sans risque annuel

@dataclass
class BacktestResult:
    total_return: float
    sharpe_ratio: float
    max_drawdown: float
    win_rate: float
    profit_factor: float
    total_trades: int
    avg_trade_duration: float
    equity_curve: pd.DataFrame
    trades: List[Dict]
    metrics: Dict

class BacktestingEngine:
    """Moteur de backtesting haute fidélité"""
    
    def __init__(self, config: BacktestConfig):
        self.config = config
        self.positions: List[Position] = []
        self.orders: List[Order] = []
        self.equity_history: List[Dict] = []