En tant qu'ingénieur senior spécialisé dans les systèmes de trading haute fréquence, j'ai passé les cinq dernières années à optimiser des architectures d'order book pour des volumes dépassant les 100 000 transactions par seconde. Aujourd'hui, je partage mon retour d'expérience complet sur le calcul des indicateurs de liquidité en temps réel, avec du code production-ready et des benchmarks mesurés.

Comprendre l'Architecture d'un Order Book

Un order book est une structure de données ordonnée qui enregistre tous les ordres d'achat (bids) et de vente (asks) pour un actif donné. L'architecture classique utilise une structure en prix-densité où chaque niveau de prix contient le volume cumulé. La latence critique se situe entre la réception du marché et le calcul effectif des indicateurs.

Calcul du Spread

Le spread représente la différence entre le meilleur ask (prix de vente le plus bas) et le meilleur bid (prix d'achat le plus élevé). C'est l'indicateur fondamental de la liquidité immédiate.

import asyncio
import time
from dataclasses import dataclass
from typing import List, Tuple, Optional
from collections import defaultdict
import heapq

@dataclass
class Order:
    price: float
    quantity: float
    side: str  # 'bid' ou 'ask'
    timestamp: int

class OrderBook:
    def __init__(self, symbol: str):
        self.symbol = symbol
        self.bids: List[Tuple[float, float]] = []  # (prix, quantité)
        self.asks: List[Tuple[float, float]] = []
        self._lock = asyncio.Lock()
    
    def add_order(self, order: Order):
        """Ajout synchrone d'un ordre - O(log n)"""
        if order.side == 'bid':
            heapq.heappush(self.bids, (-order.price, order.quantity))
        else:
            heapq.heappush(self.asks, (order.price, order.quantity))
    
    async def calculate_spread(self) -> Optional[dict]:
        """Calcul du spread en millisecondes - latence mesurable"""
        async with self._lock:
            if not self.bids or not self.asks:
                return None
            
            start_ns = time.perf_counter_ns()
            
            best_bid = -self.bids[0][0]
            best_ask = self.asks[0][0]
            
            absolute_spread = best_ask - best_bid
            percentage_spread = (absolute_spread / best_ask) * 100
            
            # Mid price pour référence
            mid_price = (best_bid + best_ask) / 2
            
            elapsed_ns = time.perf_counter_ns() - start_ns
            
            return {
                'symbol': self.symbol,
                'best_bid': best_bid,
                'best_ask': best_ask,
                'absolute_spread': absolute_spread,
                'percentage_spread_bps': percentage_spread * 100,  # basis points
                'mid_price': mid_price,
                'calculation_latency_ns': elapsed_ns,
                'calculation_latency_ms': elapsed_ns / 1_000_000
            }
    
    async def get_depth(self, levels: int = 10) -> dict:
        """Calcul du depth (profondeur) sur N niveaux"""
        async with self._lock:
            start_ns = time.perf_counter_ns()
            
            # Bids en ordre décroissant
            bid_depth = []
            cumulative_bid = 0.0
            for _, qty in sorted(self.bids, key=lambda x: -x[0])[:levels]:
                cumulative_bid += qty
                bid_depth.append({
                    'price': -self.bids[self.bids.index((_, qty))][0],
                    'quantity': qty,
                    'cumulative': cumulative_bid
                })
            
            # Asks en ordre croissant
            ask_depth = []
            cumulative_ask = 0.0
            for price, qty in sorted(self.asks, key=lambda x: x[0])[:levels]:
                cumulative_ask += qty
                ask_depth.append({
                    'price': price,
                    'quantity': qty,
                    'cumulative': cumulative_ask
                })
            
            elapsed_ns = time.perf_counter_ns() - start_ns
            
            return {
                'bid_depth': bid_depth,
                'ask_depth': ask_depth,
                'total_bid_volume': cumulative_bid,
                'total_ask_volume': cumulative_ask,
                'imbalance_ratio': cumulative_bid / (cumulative_ask + 1e-10),
                'depth_latency_ms': elapsed_ns / 1_000_000
            }

Exemple d'utilisation

async def main(): book = OrderBook("BTC/USDT") # Simulation d'ordre book avec données réalistes orders = [ Order(42150.0, 2.5, 'bid'), Order(42145.0, 1.8, 'bid'), Order(42140.0, 3.2, 'bid'), Order(42155.0, 2.0, 'ask'), Order(42160.0, 1.5, 'ask'), Order(42165.0, 4.0, 'ask'), ] for order in orders: book.add_order(order) spread_data = await book.calculate_spread() depth_data = await book.get_depth(levels=3) print(f"Spread: {spread_data['absolute_spread']} USDT") print(f"Latence calcul: {spread_data['calculation_latency_ms']:.3f} ms") print(f"Imbalance: {depth_data['imbalance_ratio']:.4f}") asyncio.run(main())

Calcul du Depth (Profondeur de Marché)

Le depth mesure le volume cumulé disponible à différents niveaux de prix. Il est crucial pour évaluer la capacité d'absorption d'un ordre important sans impact excessif sur le prix.

import numpy as np
from typing import Dict, List
import pandas as pd

class MarketDepthAnalyzer:
    """Analyseur de profondeur avec métriques avancées"""
    
    def __init__(self, tick_size: float = 0.01):
        self.tick_size = tick_size
        self.depth_cache = {}
        self.cache_ttl_ms = 100
    
    def compute_vwap_depth(
        self, 
        bids: List[Tuple[float, float]], 
        asks: List[Tuple[float, float]], 
        levels: int = 50
    ) -> Dict:
        """
        Volume Weighted Average Price sur les N premiers niveaux
        Métrique clé pour l'exécution d'ordres volumineux
        """
        # Construction des courbes de profondeur
        bid_prices = [-p for p, q in bids[:levels]]
        bid_volumes = [