← Retour aux articles
May 26, 2026
5 min de lecture

DeepLOB : Apprentissage Profond sur les Carnets d'Ordres Limites

DeepLOB : Apprentissage Profond sur les Carnets d'Ordres Limites
#deep-learning
#order-book
#LOB
#microstructure
#HFT
#market-making
📖
Part 5 of 6 · Collection
Order Book & Market Microstructure

Le carnet d'ordres limites est la structure de données centrale des marchés électroniques modernes. Chaque offre d'achat, chaque offre de vente, chaque annulation — tout vit dans le LOB. Pendant des décennies, les chercheurs quantitatifs ont conçu à la main des caractéristiques à partir de ces données : ratios de déséquilibre des ordres, prix médians pondérés, signaux de position dans la file. DeepLOB a pris une voie différente — il apprend ces caractéristiques directement à partir d'instantanés bruts du carnet d'ordres grâce à une architecture hybride CNN-LSTM.

Publié par Zhang, Zohren et Roberts à l'Oxford-Man Institute en 2019, DeepLOB a montré qu'un unique réseau profond, entraîné de bout en bout sur des données brutes du carnet, est compétitif — voire meilleur — que de solides références classiques pour la prédiction du mouvement du prix médian. Il faut néanmoins être précis sur les preuves. Le jeu de données de référence FI-2010 est celui que la plupart des articles citent, mais les auteurs eux-mêmes le jugent insuffisant — « bien trop court, sous-échantillonné et issu d'un marché moins liquide » — et présentent une seconde évaluation, bien plus vaste, sur des données de la Bourse de Londres (LSE) comme leur résultat le plus solide. Cette étude LSE couvre environ un an sur cinq actions d'entraînement et cinq actions de test, de l'ordre de plus de 100 millions d'échantillons, et démontre l'apprentissage par transfert : un modèle entraîné sur un ensemble d'actions continue de prédire sur des actions jamais vues. Ce résultat de transfert, et non le classement FI-2010, est le véritable message de l'article. Ce billet dissèque l'architecture, les mathématiques, les vrais chiffres et une réimplémentation PyTorch fonctionnelle.

Le Carnet d'Ordres Limites comme Structure de Données

Vue isométrique d'un carnet d'ordres limites : niveaux de volume d'achat et de vente empilés formant un tenseur de profondeur autour du prix médian

Un carnet d'ordres limites maintient deux listes triées — les ordres d'achat (bids) et les ordres de vente (asks) — chacune organisée par niveau de prix. À chaque niveau, le carnet enregistre le prix et le volume agrégé des ordres en attente.

Pour un carnet d'ordres à LL niveaux, un instantané unique à l'instant tt est un vecteur de 4L4L valeurs :

xt=[p1a,v1a,p1b,v1b,p2a,v2a,p2b,v2b,,pLa,vLa,pLb,vLb]\mathbf{x}_t = [p_1^{a}, v_1^{a}, p_1^{b}, v_1^{b}, p_2^{a}, v_2^{a}, p_2^{b}, v_2^{b}, \ldots, p_L^{a}, v_L^{a}, p_L^{b}, v_L^{b}]

piap_i^{a} et viav_i^{a} sont le prix et le volume de vente au niveau ii, et pibp_i^{b}, vibv_i^{b} sont les valeurs correspondantes du côté achat. Le niveau 1 contient la meilleure offre d'achat et la meilleure offre de vente (le BBO) ; leur différence est le spread bid-ask.

Dans le jeu de données de référence FI-2010, L=10L = 10, ce qui donne 40 caractéristiques par instantané. FI-2010 est construit à partir d'environ 4 millions de messages bruts d'ordres limites de cinq actions finlandaises sur NASDAQ Nordic (Kesko, Outokumpu, Sampo, Rautaruukki, Wartsila) sur dix jours de bourse consécutifs, du 1er au 14 juin 2010. Fait crucial, DeepLOB ne s'entraîne pas sur 4 millions d'instantanés : le benchmark sous-échantillonne ces messages (un sur dix événements) en une représentation normalisée de 394 337 échantillons. C'est cette forme sous-échantillonnée et normalisée que le modèle voit réellement — l'une des raisons pour lesquelles les auteurs mettent en garde contre le fait de traiter FI-2010 comme la référence ultime.

Le Prix Médian et ses Mouvements

Le prix médian à l'instant tt est la moyenne de la meilleure offre d'achat et de vente :

ptmid=p1a(t)+p1b(t)2p_t^{mid} = \frac{p_1^{a}(t) + p_1^{b}(t)}{2}

DeepLOB prédit la direction du mouvement du prix médian sur un horizon futur de kk événements. L'étiquette est un rendement lissé, mais attention, il existe deux conventions de lissage dans la littérature, et elles ne sont pas interchangeables :

  • Moyenne future vs prix actuel (Tsantekidis et al.) : compare le prix médian moyen sur les kk événements suivants au prix médian actuel brut.
  • Moyenne future vs moyenne précédente (Ntakaris et al., l'étiquette canonique de FI-2010, aussi celle utilisée par DeepLOB pour ses données LSE) : la référence et la cible sont toutes deux lissées.
lt(k)=m+(t)m(t)m(t),m(t)=1ki=0k1ptimid,m+(t)=1ki=1kpt+imidl_t(k) = \frac{m_+(t) - m_-(t)}{m_-(t)}, \qquad m_-(t) = \frac{1}{k}\sum_{i=0}^{k-1} p_{t-i}^{mid}, \quad m_+(t) = \frac{1}{k}\sum_{i=1}^{k} p_{t+i}^{mid}

Lisser la référence (pas seulement le futur) réduit considérablement le bruit de l'étiquette provenant des fluctuations tick par tick, ce qui explique pourquoi la forme moyenne-précédente/moyenne-suivante est préférée. Quelle que soit la convention utilisée, le rendement continu est discrétisé en trois classes — hausse, baisse, stationnaire — à l'aide de seuils fixes ±α\pm\alpha :

yt={1si lt(k)>α0si lt(k)α1si lt(k)<αy_t = \begin{cases} 1 & \text{si } l_t(k) > \alpha \\ 0 & \text{si } |l_t(k)| \leq \alpha \\ -1 & \text{si } l_t(k) < -\alpha \end{cases}

Le jeu de données FI-2010 fournit des étiquettes pour cinq horizons de prédiction : k{10,20,30,50,100}k \in \{10, 20, 30, 50, 100\} événements.

Caractéristiques Traditionnelles du LOB

Avant l'apprentissage profond, les chercheurs s'appuyaient sur des caractéristiques conçues à la main. Comprendre celles-ci est essentiel, car DeepLOB est délibérément structuré pour leur ressembler — son architecture est motivée par ces grandeurs même lorsqu'elle ne les reproduit pas exactement.

Déséquilibre du Carnet d'Ordres (OBI)

Le signal de microstructure le plus utilisé. Au sommet du carnet :

OBI1=v1bv1av1b+v1a\text{OBI}_1 = \frac{v_1^{b} - v_1^{a}}{v_1^{b} + v_1^{a}}

Plus de volume d'achat par rapport au volume de vente suggère une pression haussière. Le déséquilibre multi-niveaux agrège sur LL niveaux :

OBIL=i=1Lvibi=1Lviai=1Lvib+i=1Lvia\text{OBI}_L = \frac{\sum_{i=1}^{L} v_i^{b} - \sum_{i=1}^{L} v_i^{a}}{\sum_{i=1}^{L} v_i^{b} + \sum_{i=1}^{L} v_i^{a}}

Prix Médian Pondéré par le Volume

Le prix médian standard pondère les deux côtés de manière égale. Le prix médian pondéré par le volume (aussi appelé prix médian pondéré, ou VAMP) penche vers le côté le plus lourd du carnet. En notant le déséquilibre du sommet du carnet I=v1b/(v1b+v1a)I = v_1^{b}/(v_1^{b}+v_1^{a}), il s'agit de la moyenne pondérée par le déséquilibre des deux prix :

pwmid=Ip1a+(1I)p1b=v1bp1a+v1ap1bv1b+v1ap^{wmid} = I\, p_1^{a} + (1 - I)\, p_1^{b} = \frac{v_1^{b}\, p_1^{a} + v_1^{a}\, p_1^{b}}{v_1^{b} + v_1^{a}}

Notez la pondération croisée : le prix de vente est pondéré par le volume d'achat et vice-versa. Lorsque le volume d'achat domine (I1I \to 1), le prix médian pondéré se déplace vers le prix de vente — reflétant l'attente qu'un carnet déséquilibré se résorbera en se déplaçant vers le côté le plus lourd.

Un mot de prudence sur le nom. Ce prix médian pondéré par volume, statique, n'est pas le microprix de Stoikov. Le microprix de Stoikov (2018) est un estimateur ajusté par martingale Mt+g(I,S)M_t + g(I, S) obtenu par une correction récursive de l'évolution du spread et du déséquilibre — tout l'argument de Stoikov est que le prix médian pondéré naïf est biaisé et n'est pas une martingale. Ce sont deux objets distincts ; nous utilisons ici le prix médian pondéré pour sa simplicité, non comme substitut du microprix.

Déséquilibre de Profondeur

Mesure la forme du carnet au-delà du BBO :

DIi=vibviavib+via,i=1,,L\text{DI}_i = \frac{v_i^{b} - v_i^{a}}{v_i^{b} + v_i^{a}}, \quad i = 1, \ldots, L

Spread et Spread Relatif

st=p1a(t)p1b(t),strel=stptmids_t = p_1^{a}(t) - p_1^{b}(t), \qquad s_t^{rel} = \frac{s_t}{p_t^{mid}}

Ces caractéristiques sont puissantes mais limitées. Elles nécessitent une expertise du domaine pour être conçues, ne capturent pas les interactions non linéaires complexes et ignorent la dynamique temporelle entre instantanés consécutifs. DeepLOB s'attaque à ces trois limitations.

Architecture de DeepLOB

Architecture DeepLOB : un tenseur brut du carnet d'ordres traversant des couches convolutionnelles, un module inception et un LSTM

L'architecture comporte trois blocs, chacun ayant un rôle distinct :

  1. Bloc convolutionnel — extrait les caractéristiques spatiales de l'instantané du carnet d'ordres
  2. Module inception — capture les motifs temporels multi-échelles
  3. Bloc LSTM — modélise les dépendances séquentielles à travers le temps

L'entrée du réseau est un tenseur de forme (T,L×4)(T, L \times 4), où TT est la fenêtre de rétrospection (100 pas de temps dans l'article) et L×4=40L \times 4 = 40 pour 10 niveaux de prix et de volume des deux côtés.

Bloc 1 : Extraction de Caractéristiques Convolutionnelles

Les premières couches convolutionnelles opèrent le long de la dimension des caractéristiques pour apprendre les interactions entre prix et volume à chaque niveau.

Couche 1 : Une convolution avec un filtre de taille 1×21 \times 2 et un pas (1,2)(1, 2) appliquée à chaque paire prix-volume. Cela résume chaque paire (pi,vi)(p_i, v_i) en une seule caractéristique — analogue au calcul d'une « interaction prix-volume » à chaque niveau. La largeur passe de 40 à 20.

Couche 2 : Une convolution 1×21 \times 2, pas (1,2)(1, 2), qui opère sur les paires achat-vente appariées au même niveau. Cela capture les informations de spread et de déséquilibre par niveau. La largeur passe de 20 à 10.

Couche 3 : Une convolution 1×101 \times 10 qui intègre l'ensemble des 10 niveaux, réduisant la dimension de profondeur à une largeur de 1. Cela produit une seule caractéristique agrégée par pas de temps, encodant le profil de profondeur complet.

Chaque étape 1×21 \times 2 / 1×101 \times 10 est suivie de deux convolutions (4,1)(4, 1) le long de l'axe temporel, chacune avec une activation LeakyReLU et une normalisation par lots. Notez que ces convolutions (4,1)(4, 1) avec padding=(1, 0) ne préservent pas la dimension temporelle : chacune la raccourcit d'un pas (Tout=Tin1T_{\text{out}} = T_{\text{in}} - 1). Sur les six couches de ce type dans les blocs 1 à 3, l'axe temporel passe de 100 à 94 avant le module inception — ce que les commentaires de forme dans le code ci-dessous rendent explicite.

La conception évite délibérément les grands filtres spatiaux. Les filtres 1×21 \times 2 respectent la structure d'appariement naturelle des données LOB — prix avec volume, achat avec vente. Ce biais inductif est crucial : un filtre 3×33 \times 3 mélangerait des niveaux et des côtés adjacents d'une manière qui n'a aucun sens financier.

Bloc 2 : Module Inception

Après que le bloc convolutionnel réduit les 40 caractéristiques à une représentation spatiale compacte (largeur 1), le module inception opère le long de la dimension temporelle pour capturer des motifs à plusieurs échelles temporelles simultanément.

Le module inception de DeepLOB comporte quatre chemins parallèles, chacun produisant 32 canaux, tous concaténés :

  • Goulot d'étranglement 1×11 \times 1 \to convolution temporelle 3×13 \times 1 : motifs temporels à court terme (3 pas de temps)
  • Goulot d'étranglement 1×11 \times 1 \to convolution temporelle 5×15 \times 1 : motifs temporels à moyen terme (5 pas de temps)
  • Max-pool 3×13 \times 1 \to convolution 1×11 \times 1 : un chemin de pooling qui conserve une vue lissée et sous-échantillonnée
  • (les réductions 1×11 \times 1 agissent comme des goulots d'étranglement ponctuels avant les convolutions temporelles)

Chaque chemin temporel utilise un padding symétrique afin que la dimension temporelle soit maintenue à l'intérieur du bloc inception (contrairement aux blocs convolutionnels ci-dessus). Les sorties sont concaténées le long de la dimension des canaux, donnant au réseau accès à des caractéristiques temporelles multi-échelles simultanément.

Ceci s'inspire de l'architecture inception de GoogLeNet, mais adaptée aux données temporelles 1D plutôt qu'aux images 2D. L'idée clé est que la dynamique du LOB opère à plusieurs échelles de temps : le bruit tick par tick, le momentum à court terme et le retour à la moyenne à plus long terme coexistent tous.

La réimplémentation PyTorch ci-dessous présente une variante simplifiée à trois chemins (les deux convolutions temporelles plus un chemin 1×11\times1) afin que le code reste lisible ; elle omet le chemin max-pool. Nous le signalons explicitement car c'est un écart par rapport à l'article, pas le module original.

Bloc 3 : Modélisation de Séquence avec LSTM

La sortie du module inception est transmise à une couche LSTM qui traite l'ensemble de la séquence de longueur TT. Le LSTM capture des dépendances temporelles à longue portée que les convolutions seules manqueraient — changements de régime, effets de l'heure de la journée et conditions de marché évolutives.

L'état caché final du LSTM est passé à travers une couche entièrement connectée avec activation softmax pour produire une distribution de probabilité sur les trois classes (hausse, baisse, stationnaire).

Fonction de Perte

Le modèle est entraîné avec l'entropie croisée catégorielle :

L=1Ni=1Nc=13yi,clog(y^i,c)\mathcal{L} = -\frac{1}{N} \sum_{i=1}^{N} \sum_{c=1}^{3} y_{i,c} \log(\hat{y}_{i,c})

yi,cy_{i,c} est l'étiquette vraie encodée en one-hot et y^i,c\hat{y}_{i,c} est la probabilité prédite pour la classe cc.

Réimplémentation PyTorch

Le DeepLOB original a été construit en Keras sur le backend TensorFlow ; le dépôt des auteurs (zcakhaa) a ajouté par la suite un portage PyTorch. Le code ci-dessous est notre propre réimplémentation illustrative en PyTorch, pas celle officielle. Il s'écarte de l'article de deux manières que nous signalons en ligne : il omet le chemin max-pool de l'inception (trois chemins au lieu de quatre) et augmente le nombre de canaux. L'article utilise 16 filtres dans tous les blocs convolutionnels et 32 filtres par chemin d'inception (entrée LSTM 3×32=963 \times 32 = 96), donnant un modèle compact d'environ 60 000 paramètres. L'extrait ci-dessous utilise 32 filtres conv / 64 filtres d'inception (entrée LSTM 192) — cohérent en interne et exécutable, mais plus lourd que l'original. Pour correspondre à l'article, réglez les canaux conv à 16, les canaux d'inception à 32, et input_size=96.

import torch
import torch.nn as nn


class DeepLOB(nn.Module):
    """
    DeepLOB-style CNN-Inception-LSTM for limit order books.
    Reimplementation inspired by Zhang, Zohren, Roberts (2019),
    IEEE Transactions on Signal Processing, arXiv:1808.03668.

    NOTE: this is an illustrative reimplementation, NOT the official model.
    Differences from the paper:
      - inception has 3 paths here vs 4 in the paper (max-pool path omitted);
      - channel counts are scaled up (paper: 16 conv / 32 inception, input 96).

    Input shape: (batch_size, 1, T, 40)
        T = lookback window (e.g. 100)
        40 = 10 levels x 4 features (ask_price, ask_vol, bid_price, bid_vol)
    """

    def __init__(self, num_classes: int = 3, T: int = 100):
        super().__init__()
        self.num_classes = num_classes

        self.conv1 = nn.Sequential(
            nn.Conv2d(1, 32, kernel_size=(1, 2), stride=(1, 2)),
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(32),
            nn.Conv2d(32, 32, kernel_size=(4, 1), padding=(1, 0)),  # time -1
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(32),
            nn.Conv2d(32, 32, kernel_size=(4, 1), padding=(1, 0)),  # time -1
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(32),
        )

        self.conv2 = nn.Sequential(
            nn.Conv2d(32, 32, kernel_size=(1, 2), stride=(1, 2)),
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(32),
            nn.Conv2d(32, 32, kernel_size=(4, 1), padding=(1, 0)),  # time -1
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(32),
            nn.Conv2d(32, 32, kernel_size=(4, 1), padding=(1, 0)),  # time -1
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(32),
        )

        self.conv3 = nn.Sequential(
            nn.Conv2d(32, 32, kernel_size=(1, 10)),
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(32),
            nn.Conv2d(32, 32, kernel_size=(4, 1), padding=(1, 0)),  # time -1
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(32),
            nn.Conv2d(32, 32, kernel_size=(4, 1), padding=(1, 0)),  # time -1
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(32),
        )

        self.inp1 = nn.Sequential(
            nn.Conv2d(32, 64, kernel_size=(1, 1), padding=(0, 0)),
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(64),
        )
        self.inp2 = nn.Sequential(
            nn.Conv2d(32, 64, kernel_size=(1, 1), padding=(0, 0)),
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(64),
            nn.Conv2d(64, 64, kernel_size=(3, 1), padding=(1, 0)),
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(64),
        )
        self.inp3 = nn.Sequential(
            nn.Conv2d(32, 64, kernel_size=(1, 1), padding=(0, 0)),
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(64),
            nn.Conv2d(64, 64, kernel_size=(5, 1), padding=(2, 0)),
            nn.LeakyReLU(negative_slope=0.01),
            nn.BatchNorm2d(64),
        )

        self.lstm = nn.LSTM(
            input_size=192,  # 64 * 3 paths from the simplified inception
            hidden_size=64,
            num_layers=1,
            batch_first=True,
        )

        self.fc = nn.Linear(64, num_classes)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """
        Args:
            x: Tensor of shape (batch, 1, T, 40)
        Returns:
            Tensor of shape (batch, num_classes)
        """
        h = self.conv1(x)   # (batch, 32, T-2, 20)
        h = self.conv2(h)   # (batch, 32, T-4, 10)
        h = self.conv3(h)   # (batch, 32, T-6, 1)   e.g. T=100 -> 94

        h_inp1 = self.inp1(h)  # (batch, 64, T-6, 1)
        h_inp2 = self.inp2(h)  # (batch, 64, T-6, 1)
        h_inp3 = self.inp3(h)  # (batch, 64, T-6, 1)
        h = torch.cat([h_inp1, h_inp2, h_inp3], dim=1)  # (batch, 192, T-6, 1)

        h = h.squeeze(-1)          # (batch, 192, T-6)
        h = h.permute(0, 2, 1)     # (batch, T-6, 192)

        h, _ = self.lstm(h)
        h = h[:, -1, :]  # take last hidden state

        out = self.fc(h)
        return out

Prétraitement des Données

Le jeu de données FI-2010 est distribué normalisé par z-score. Pour des données de carnet d'ordres en direct, il faut normaliser de manière similaire :

import numpy as np
from torch.utils.data import Dataset


class LOBDataset(Dataset):
    """Dataset for limit order book snapshots."""

    def __init__(
        self,
        data: np.ndarray,
        labels: np.ndarray,
        T: int = 100,
    ):
        """
        Args:
            data: shape (num_snapshots, 40), raw LOB features
            labels: shape (num_snapshots,), class labels {0, 1, 2}
            T: lookback window length
        """
        self.data = data
        self.labels = labels
        self.T = T

        self.mean = data[:len(data) // 2].mean(axis=0)
        self.std = data[:len(data) // 2].std(axis=0)
        self.std[self.std == 0] = 1.0
        self.data = (self.data - self.mean) / self.std

    def __len__(self) -> int:
        return len(self.data) - self.T

    def __getitem__(self, idx: int):
        x = self.data[idx : idx + self.T].reshape(1, self.T, 40)
        y = self.labels[idx + self.T - 1]
        return torch.tensor(x, dtype=torch.float32), torch.tensor(
            y, dtype=torch.long
        )

Boucle d'Entraînement

def train_deeplob(
    model: DeepLOB,
    train_loader,
    val_loader,
    epochs: int = 50,
    lr: float = 0.01,
    device: str = "cuda",
):
    model = model.to(device)
    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=lr, eps=1)

    best_val_f1 = 0.0
    for epoch in range(epochs):
        model.train()
        train_loss = 0.0
        for x_batch, y_batch in train_loader:
            x_batch = x_batch.to(device)
            y_batch = y_batch.to(device)

            optimizer.zero_grad()
            logits = model(x_batch)
            loss = criterion(logits, y_batch)
            loss.backward()
            optimizer.step()
            train_loss += loss.item()

        model.eval()
        all_preds, all_labels = [], []
        with torch.no_grad():
            for x_val, y_val in val_loader:
                x_val = x_val.to(device)
                preds = model(x_val).argmax(dim=1).cpu()
                all_preds.extend(preds.numpy())
                all_labels.extend(y_val.numpy())

        from sklearn.metrics import f1_score
        val_f1 = f1_score(all_labels, all_preds, average="weighted")

        print(
            f"Epoch {epoch+1}/{epochs} | "
            f"Train Loss: {train_loss/len(train_loader):.4f} | "
            f"Val F1: {val_f1:.4f}"
        )

        if val_f1 > best_val_f1:
            best_val_f1 = val_f1
            torch.save(model.state_dict(), "deeplob_best.pt")

Principaux hyperparamètres d'entraînement de l'article original :

  • Optimiseur : Adam avec ϵ=1\epsilon = 1 (inhabituellement grand — aide à stabiliser les mises à jour sur des données financières bruitées)
  • Taux d'apprentissage : 0.01
  • Taille de lot : 32
  • Fenêtre de rétrospection : T=100T = 100 pas de temps

Nous suivons le F1 pondéré plutôt que la précision en validation, car les trois classes sont déséquilibrées — et, comme le montre la section suivante, le F1 est la métrique que l'article original met en avant pour exactement cette raison.

Ce que DeepLOB est Construit pour Capturer

Signaux de microstructure appris par DeepLOB : déséquilibre du flux d'ordres, dynamique de file d'attente et pression du prix médian pondéré par volume

Un aspect séduisant de DeepLOB est à quel point ses blocs sont motivés par les caractéristiques conçues à la main ci-dessus. Nous devons être prudents ici : l'article présente cela comme des intuitions de conception, pas comme des propriétés prouvées du réseau entraîné. Il ne publie ni visualisations de filtres, ni corrélations entre activation et déséquilibre, ni analyse de ce à quoi le LSTM porte attention. Il faut donc lire ce qui suit comme une intention architecturale, pas comme des résultats mesurés.

Premier bloc convolutionnel. Les filtres 1×21 \times 2 apparient prix et volume à chaque niveau. Structurellement, c'est la même opération qui forme un prix pondéré par le volume — l'article motive la première couche en notant que ses cartes de caractéristiques forment une grandeur proche d'un microprix. Le réseau est conçu pour retrouver quelque chose de cette famille ; nous ne prétendons pas qu'il redécouvre de manière prouvée un estimateur spécifique.

Second bloc convolutionnel. En opérant sur les paires achat-vente, ces filtres sont positionnés pour apprendre des caractéristiques semblables au déséquilibre à plusieurs niveaux — encore une fois, par construction plutôt que par corrélation démontrée.

Module inception. Les convolutions temporelles multi-échelles visent à capturer le momentum à différentes fréquences : le chemin 1×11\times1 répond à la structure ponctuelle, le chemin 3×13\times1 aux tendances à court terme, le chemin 5×15\times1 à des tendances légèrement plus longues et (dans le module complet) le chemin max-pool à une vue lissée.

Couche LSTM. La composante récurrente ajoute une mémoire des dépendances à longue portée que les convolutions ne peuvent pas représenter — changements de régime et effets liés à l'heure de la journée. Nous évitons les affirmations plus fortes (comme le fait que le LSTM implémenterait une « attention adaptative » qui repondère les données récentes en régime volatil) ; l'article ne montre pas cela.

Performance sur FI-2010

L'article original évalue deux protocoles. Le Setup 1 utilise la division de normalisation antérieure du jeu de données ; le Setup 2 est la configuration d'apprentissage profond à laquelle la plupart des travaux ultérieurs se comparent. Comme FI-2010 présente un déséquilibre de classes, l'article met l'accent sur le F1, pas sur la précision, et rapporte des chiffres basés sur la précision/rappel pour les références non neuronales (SVM, MLP). Voici les vrais chiffres du Setup 2 (F1, %) :

Horizon (k) SVM MLP CNN-I LSTM DeepLOB
10 35.88 48.27 55.21 66.33 83.40
20 72.82
50 80.35

À k=10k = 10, DeepLOB rapporte également 84,47 % de précision aux côtés de son F1 de 83,40. Le Setup 2 ne rapporte que k{10,20,50}k \in \{10, 20, 50\} — il n'y a pas de ligne k=100k = 100 ici. (Le Setup 1 rapporte bien k{10,50,100}k \in \{10, 50, 100\}, avec un F1 DeepLOB de 77,66 / 74,96 / 76,58 respectivement.) Les tirets ci-dessus marquent les cellules de référence que l'article ne tabule pas à ces horizons dans ce protocole.

Plusieurs observations se démarquent :

  1. DeepLOB bat les références par une large marge sur l'horizon court. À k=10k = 10, son F1 (83,40) est nettement supérieur à LSTM (66,33), CNN-I (55,21), MLP (48,27) et SVM (35,88). L'ordre qualitatif — modèles convolutionnels-récurrents profonds au-dessus des simples CNN, MLP et SVM — est la conclusion robuste.
  2. La tendance selon l'horizon est non monotone, ce n'est pas « plus long est plus facile ». Sur le Setup 2, le F1 de DeepLOB va 83,4072,8280,3583,40 \to 72,82 \to 80,35 à mesure que kk passe de 10205010 \to 20 \to 50 — un motif en forme de U, avec k=20k=20 le plus difficile, pas une progression ascendante nette. Le fait qu'un horizon soit « plus facile » dépend fortement de la configuration, du seuil α\alpha et du schéma de lissage des étiquettes ; il ne faut pas y lire une loi universelle.
  3. DeepLOB ne domine pas toutes les méthodes classiques sur chaque cellule. Face à de solides références comme C(TABL), les marges se resserrent à certains horizons, si bien que « surpasse toutes les approches classiques partout » exagère le propos. L'affirmation défendable est qu'il est compétitif à excellent, et clairement le meilleur sur l'horizon court.

Et encore une fois : la propre position de l'article est que FI-2010 est trop petit et trop sous-échantillonné pour trancher la question. Sa preuve la plus solide est l'étude LSE, où DeepLOB s'entraîne sur un ensemble d'actions et continue de prédire sur des actions non vues — un résultat d'apprentissage par transfert que FI-2010 ne peut pas montrer.

LOBFrame et la Crise de Réplication

Courbes divergentes in-sample et out-of-sample illustrant la crise de réplication de l'apprentissage profond sur les carnets d'ordres

Le cadre de référence LOBFrame (Briola, Bartolucci, Aste, 2024) apporte un complément salutaire à ces résultats. Bien que l'architecture de DeepLOB soit solide, ce cadre met en lumière plusieurs mises en garde importantes :

Dépendance à la microstructure. La performance du modèle varie considérablement selon les actions ayant des caractéristiques microstructurelles différentes. Les actions liquides à gros tick sont plus faciles à prédire que les instruments illiquides à petit tick. Un modèle qui semble performant sur une action peut se dégrader substantiellement sur une autre.

Prédiction vs profit. Une précision de classification élevée ne se traduit pas automatiquement en profit de trading. LOBFrame met l'accent sur des métriques liées au fait qu'un mouvement prédit soit suffisamment important pour compenser le spread bid-ask. Un modèle doit avoir raison suffisamment souvent, et sur des mouvements assez importants, pour surmonter le spread — et pour les actions à spread serré, cette barre est élevée.

Sensibilité de l'étiquette. Le choix du seuil α\alpha et de l'horizon kk affecte considérablement à la fois la précision rapportée et la valeur pratique des prédictions. Des étiquettes trop serrées produisent des cibles bruitées ; des étiquettes trop larges produisent des prédictions trivialement « précises » mais inutiles.

Au-delà de DeepLOB : Le Paysage Actuel

Constellation d'approches modernes de prédiction du carnet d'ordres gravitant autour du modèle DeepLOB original

Depuis la publication de DeepLOB, plusieurs extensions sont apparues :

Transformers pour carnets d'ordres limites (Wallbridge, 2020). Souvent appelé de manière imprécise « TransLOB » dans la littérature ultérieure, le modèle lui-même est un extracteur de caractéristiques convolutionnel causal/dilaté suivi d'une auto-attention multi-têtes masquée — pas simplement DeepLOB avec le LSTM remplacé par un Transformer ; il ne réutilise pas la pile inception+conv. Il a rapporté un nouvel état de l'art sur FI-2010 pour son époque.

Variantes du domaine crypto. Plusieurs groupes appliquent des architectures de style DeepLOB aux marchés de cryptomonnaies, où la dynamique du carnet d'ordres diffère substantiellement des actions — spreads plus larges, trading 24h/24 et 7j/7, et un mix de participants différent qui exigent tous un réentraînement.

Variantes augmentées par l'attention. Une ligne de travaux insère de l'attention multi-têtes entre les blocs inception et LSTM, permettant au réseau de se concentrer sur les niveaux ou les pas de temps les plus informatifs pour la prédiction actuelle.

LOB-Bench (Nagy, Frey, Li, Sarkar, Vyetrenko, Zohren, Calinescu, Foerster, 2025). Ce benchmark déplace l'attention de la prédiction vers la génération, en évaluant à quel point les modèles génératifs de données de carnet d'ordres sont réalistes — pertinent pour le backtesting et l'entraînement d'agents plutôt que pour la prévision directionnelle.

Considérations Pratiques pour la Production

Si vous envisagez de déployer un modèle de style DeepLOB dans un système de trading en direct, plusieurs préoccupations d'ingénierie se posent :

Latence

Le modèle doit produire des prédictions dans le budget de latence de votre système de trading. Pour les systèmes de HFT fonctionnant à des latences inférieures à la milliseconde, même une inférence PyTorch optimisée peut être trop lente. Les options incluent :

  • Export ONNX avec optimisation TensorRT
  • Quantification en INT8
  • Déploiement FPGA pour les applications les plus sensibles à la latence

Pipeline de Données

Le modèle attend des instantanés de carnet d'ordres normalisés et alignés à une fréquence fixe. En pratique, les mises à jour du LOB arrivent de manière asynchrone. Il faut :

  • Un moteur de reconstruction d'instantanés qui maintient l'état actuel du carnet
  • Un échantillonneur à fréquence fixe qui produit le tenseur d'entrée (T,40)(T, 40)
  • Une normalisation en ligne utilisant des statistiques glissantes

Stabilité des Caractéristiques

Les paramètres de normalisation z-score calculés sur les données d'entraînement dérivent avec le temps. Les prix changent, les régimes de volatilité évoluent, la structure du marché évolue. Un système de production a besoin de :

  • Fenêtres de normalisation glissantes (par ex., recalculer les statistiques quotidiennement)
  • Détection de régime pour déclencher le réentraînement du modèle
  • Surveillance du décalage de distribution des entrées

Surapprentissage à la Microstructure

Les motifs du LOB sont spécifiques à la plateforme et à l'instrument. Un modèle entraîné sur des actions NASDAQ ne fonctionnera pas sur Binance BTC/USDT sans réentraînement. Même au sein d'une même plateforme, les modèles peuvent surapprendre :

  • Le régime de taille de tick (actions à gros tick vs petit tick)
  • Les motifs liés à l'heure de la journée (enchère d'ouverture, creux de midi, croisement de clôture)
  • Le comportement des teneurs de marché qui évolue dans le temps

Conclusion

DeepLOB représente une application propre et bien motivée de l'apprentissage profond à la microstructure de marché. Son architecture en trois blocs — CNN pour les caractéristiques spatiales, inception pour les motifs temporels multi-échelles, LSTM pour les dépendances séquentielles — se mappe naturellement sur la structure des données de carnet d'ordres limites.

L'idée clé n'est pas seulement qu'un modèle profond puisse égaler ou surpasser des caractéristiques conçues à la main, mais que les biais inductifs de l'architecture — les filtres 1×21 \times 2, le parallélisme de l'inception, la mémoire récurrente — encodent une connaissance véritable du domaine sur le fonctionnement des carnets d'ordres. Ce n'est pas un réseau profond générique appliqué à des données financières ; c'est une architecture conçue autour de la géométrie spécifique du carnet d'ordres limites. Et sa preuve la plus convaincante n'est pas le classement FI-2010 dont les auteurs eux-mêmes se méfient, mais le résultat d'apprentissage par transfert sur un an de données LSE.

Pour les praticiens, DeepLOB est une solide référence et un bloc de construction utile. Pour les chercheurs, il démontre qu'une conception d'architecture réfléchie — pas seulement l'échelle du modèle — pilote la performance sur des données financières structurées. N'oubliez pas de citer les vrais chiffres, de surveiller le schéma d'étiquetage, et de traiter la précision de classification comme étant loin, très loin, du profit de trading.


Références

  1. Zhang, Z., Zohren, S., & Roberts, S. (2019). DeepLOB: Deep Convolutional Neural Networks for Limit Order Books. IEEE Transactions on Signal Processing, 67(11), 3001-3012. arXiv:1808.03668

  2. Ntakaris, A., Magris, M., Kanniainen, J., Gabbouj, M., & Iosifidis, A. (2018). Benchmark Dataset for Mid-Price Forecasting of Limit Order Book Data with Machine Learning Methods. Journal of Forecasting, 37(8), 852-866. arXiv:1705.03233

  3. Briola, A., Bartolucci, S., & Aste, T. (2024). Deep Limit Order Book Forecasting (LOBFrame). arXiv:2403.09267

  4. Wallbridge, J. (2020). Transformers for Limit Order Books. arXiv:2003.00130

  5. Cont, R., Kukanov, A., & Stoikov, S. (2014). The Price Impact of Order Book Events. Journal of Financial Econometrics, 12(1), 47-88.

  6. Stoikov, S. (2018). The Micro-Price: A High-Frequency Estimator of Future Prices. Quantitative Finance, 18(12), 1959-1966.

  7. Nagy, P., Frey, S., Li, K., Sarkar, B., Vyetrenko, S., Zohren, S., Calinescu, A., & Foerster, J. (2025). LOB-Bench: Benchmarking Generative AI for Finance — an Application to Limit Order Book Data. ICML 2025. arXiv:2502.09172

  8. DeepLOB code (original in Keras/TensorFlow; the repository also provides a PyTorch port): github.com/zcakhaa/DeepLOB-Deep-Convolutional-Neural-Networks-for-Limit-Order-Books

blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Gardez une longueur d'avance sur le marché

Abonnez-vous à notre newsletter pour des insights exclusifs sur le trading IA, des analyses de marché et des mises à jour de la plateforme.

Nous respectons votre vie privée. Désabonnement possible à tout moment.