← Torna agli articoli
May 26, 2026
5 min di lettura

DeepLOB: Deep Learning sui Limit Order Book

DeepLOB: Deep Learning sui Limit Order Book
#deep-learning
#order-book
#LOB
#microstructure
#HFT
#market-making
📖
Part 5 of 6 · Collection
Order Book & Market Microstructure

Il limit order book è la struttura dati centrale dei moderni mercati elettronici. Ogni bid, ogni ask, ogni cancellazione — tutto vive nel LOB. Per decenni i ricercatori quantitativi hanno costruito a mano feature a partire da questi dati: rapporti di order imbalance, mid-price ponderati, segnali di posizione in coda. DeepLOB ha imboccato una strada diversa — apprende queste feature direttamente dagli snapshot grezzi dell'order book usando un'architettura ibrida CNN-LSTM.

Pubblicato da Zhang, Zohren e Roberts all'Oxford-Man Institute nel 2019, DeepLOB ha mostrato che una singola rete profonda, addestrata end-to-end su dati grezzi del book, è competitiva con o migliore di solidi baseline classici nella previsione del movimento del mid-price. Bisogna però essere precisi riguardo alle evidenze. Il dataset di benchmark FI-2010 è quello che la maggior parte dei paper cita, ma gli autori stessi lo definiscono insufficiente — "decisamente troppo breve, sottocampionato e tratto da un mercato meno liquido" — e presentano una seconda valutazione, molto più ampia, su dati del London Stock Exchange (LSE) come loro risultato più solido. Quello studio sull'LSE copre all'incirca un anno di cinque titoli di training e cinque titoli di test, dell'ordine di oltre 100M di campioni, e dimostra il transfer learning: un modello addestrato su un insieme di titoli predice comunque su titoli mai visti. È quel risultato di transfer, non la classifica su FI-2010, il vero titolo del paper. Questo post analizza l'architettura, la matematica, i numeri reali e una reimplementazione funzionante in PyTorch.

Il Limit Order Book come Struttura Dati

Vista isometrica di un limit order book: livelli di volume bid e ask impilati che formano un tensore di profondità attorno al mid-price

Un limit order book mantiene due liste ordinate — i bid (ordini di acquisto) e gli ask (ordini di vendita) — ciascuna organizzata per livello di prezzo. A ogni livello, il book registra il prezzo e il volume aggregato degli ordini in attesa.

Per un order book con LL livelli, un singolo snapshot al tempo tt è un vettore di 4L4L valori:

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}]

dove piap_i^{a} e viav_i^{a} sono il prezzo e il volume ask al livello ii, e pibp_i^{b}, vibv_i^{b} sono i corrispondenti valori sul lato bid. Il livello 1 contiene il best bid e il best ask (il BBO); la loro differenza è il bid-ask spread.

Nel dataset di benchmark FI-2010, L=10L = 10, dando 40 feature per snapshot. FI-2010 è costruito a partire da circa 4 milioni di messaggi grezzi di limit-order relativi a cinque azioni finlandesi su NASDAQ Nordic (Kesko, Outokumpu, Sampo, Rautaruukki, Wartsila) lungo dieci giorni di contrattazione consecutivi, dal 1 al 14 giugno 2010. Fondamentale: DeepLOB non si addestra su 4M di snapshot: il benchmark sottocampiona quei messaggi (ogni 10 eventi) in una rappresentazione normalizzata di 394.337 campioni. È quella forma sottocampionata e normalizzata ciò che il modello vede effettivamente — una delle ragioni per cui gli autori mettono in guardia dal trattare FI-2010 come l'ultima parola.

Mid-Price e i Suoi Movimenti

Il mid-price al tempo tt è la media tra il best bid e il best ask:

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

DeepLOB predice la direzione del movimento del mid-price su un orizzonte futuro di kk eventi. L'etichetta è un rendimento smussato, ma occorre tenere presente che in letteratura esistono due convenzioni di smussamento, e non sono intercambiabili:

  • Media futura vs prezzo corrente (Tsantekidis et al.): si confronta il mid-price medio sui successivi kk eventi con il mid-price corrente grezzo.
  • Media futura vs media precedente (Ntakaris et al., l'etichetta canonica di FI-2010, anche quella usata da DeepLOB per i suoi dati LSE): sia il riferimento sia il target sono smussati.
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}

Smussare il riferimento (non solo il futuro) riduce in modo sostanziale il rumore dell'etichetta dovuto alle fluttuazioni di singolo tick, ed è il motivo per cui la forma media-precedente/media-successiva è preferita. Qualunque si usi, il rendimento continuo viene discretizzato in tre classi — up, down, stationary — usando soglie fisse ±α\pm\alpha:

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

Il dataset FI-2010 fornisce etichette per cinque orizzonti di previsione: k{10,20,30,50,100}k \in \{10, 20, 30, 50, 100\} eventi.

Feature LOB Tradizionali

Prima del deep learning, i ricercatori si affidavano a feature costruite a mano. Capirle è essenziale, perché DeepLOB è strutturato deliberatamente per assomigliare a molte di esse — la sua architettura è motivata da queste quantità anche dove non le riproduce esattamente.

Order Book Imbalance (OBI)

Il segnale di microstruttura più usato. In cima al book:

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

Più volume bid rispetto al volume ask suggerisce pressione al rialzo. L'imbalance multi-livello aggrega sui LL livelli:

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}}

Mid-Price Ponderato per il Volume

Il mid-price standard pesa i due lati allo stesso modo. Il mid-price ponderato per il volume (chiamato anche weighted mid, o VAMP) si inclina verso il lato più pesante del book. Scrivendo l'imbalance in cima al book come I=v1b/(v1b+v1a)I = v_1^{b}/(v_1^{b}+v_1^{a}), esso è la media dei due prezzi ponderata per l'imbalance:

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}}

Si noti la ponderazione incrociata: il prezzo ask è pesato dal volume bid e viceversa. Quando il volume bid domina (I1I \to 1), il weighted mid si sposta verso l'ask — riflettendo l'aspettativa che un book sbilanciato si risolverà muovendosi verso il lato più pesante.

Una nota di cautela sulla denominazione. Questo mid statico, ponderato per il volume, non è il microprice di Stoikov. Il micro-price di Stoikov (2018) è uno stimatore corretto per la martingala Mt+g(I,S)M_t + g(I, S) ottenuto da una correzione ricorsiva su come spread e imbalance evolvono — tutto il suo punto è che il weighted mid ingenuo è distorto e non è una martingala. Sono due oggetti distinti; qui usiamo il weighted mid per la sua semplicità, non come sostituto del microprice.

Depth Imbalance

Misura la forma del book oltre il 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 e Spread Relativo

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}}

Queste feature sono potenti ma limitate. Richiedono competenza di dominio per essere progettate, non catturano interazioni non lineari complesse e ignorano le dinamiche temporali tra snapshot consecutivi. DeepLOB affronta tutti e tre i limiti.

Architettura di DeepLOB

Architettura di DeepLOB: un tensore grezzo dell'order book che attraversa strati convoluzionali, un modulo inception e una LSTM

L'architettura ha tre blocchi, ciascuno con un ruolo distinto:

  1. Blocco convoluzionale — estrae feature spaziali dallo snapshot dell'order book
  2. Modulo inception — cattura pattern temporali multi-scala
  3. Blocco LSTM — modella le dipendenze sequenziali nel tempo

L'input della rete è un tensore di forma (T,L×4)(T, L \times 4), dove TT è la finestra di lookback (100 timestep nel paper) e L×4=40L \times 4 = 40 per 10 livelli di prezzo e volume su entrambi i lati.

Blocco 1: Estrazione di Feature Convoluzionali

I primi strati convoluzionali operano lungo la dimensione delle feature per apprendere le interazioni tra prezzo e volume a ciascun livello.

Strato 1: Una convoluzione con filtro di dimensione 1×21 \times 2 e stride (1,2)(1, 2) applicata a ciascuna coppia prezzo-volume. Questo riassume ogni coppia (pi,vi)(p_i, v_i) in una singola feature — analogamente al calcolo di un'"interazione prezzo-volume" a ciascun livello. La larghezza si riduce da 40 a 20.

Strato 2: Una convoluzione 1×21 \times 2, stride (1,2)(1, 2) che opera sulle coppie bid-ask abbinate allo stesso livello. Questo cattura informazioni su spread e imbalance per livello. La larghezza si riduce da 20 a 10.

Strato 3: Una convoluzione 1×101 \times 10 che integra su tutti i 10 livelli, collassando la dimensione di profondità a larghezza 1. Questo produce una singola feature aggregata per timestep che codifica l'intero profilo di profondità.

Ogni passo 1×21 \times 2 / 1×101 \times 10 è seguito da due convoluzioni (4,1)(4, 1) lungo l'asse del tempo, ciascuna con attivazione LeakyReLU e batch normalization. Si noti che queste convoluzioni (4,1)(4, 1) con padding=(1, 0) non preservano la dimensione temporale: ciascuna la accorcia di un passo (Tout=Tin1T_{\text{out}} = T_{\text{in}} - 1). Attraverso i sei strati di questo tipo nei blocchi 1-3, l'asse del tempo si riduce da 100 a 94 prima del modulo inception — qualcosa che i commenti sulle forme nel codice qui sotto rendono esplicito.

Il design evita deliberatamente filtri spaziali grandi. I filtri 1×21 \times 2 rispettano la naturale struttura ad accoppiamento dei dati LOB — prezzo con volume, bid con ask. Questo bias induttivo è cruciale: un filtro 3×33 \times 3 mescolerebbe livelli e lati adiacenti in un modo che non ha alcun significato finanziario.

Blocco 2: Modulo Inception

Dopo che il blocco convoluzionale ha ridotto le 40 feature a una rappresentazione spaziale compatta (larghezza 1), il modulo inception opera lungo la dimensione temporale per catturare pattern a scale temporali multiple simultaneamente.

Il modulo inception di DeepLOB ha quattro percorsi paralleli, ciascuno che produce 32 canali, tutti concatenati:

  • bottleneck 1×11 \times 1 \to conv temporale 3×13 \times 1: pattern temporali a breve termine (3 timestep)
  • bottleneck 1×11 \times 1 \to conv temporale 5×15 \times 1: pattern temporali a medio termine (5 timestep)
  • max-pool 3×13 \times 1 \to conv 1×11 \times 1: un percorso di pooling che mantiene una vista smussata e sottocampionata
  • (le riduzioni 1×11 \times 1 agiscono come bottleneck puntuali nel tempo prima delle convoluzioni temporali)

Ogni percorso temporale usa un padding simmetrico così che la dimensione temporale sia mantenuta all'interno del blocco inception (a differenza dei blocchi conv sopra). Gli output sono concatenati lungo la dimensione dei canali, dando alla rete accesso a feature temporali multi-scala tutte in una volta.

Questo si ispira all'architettura inception di GoogLeNet, ma è adattato a dati temporali 1D anziché a immagini 2D. L'intuizione chiave è che le dinamiche del LOB operano a scale temporali multiple: rumore tick-by-tick, momentum a breve termine e mean reversion su orizzonti più lunghi coesistono tutti.

La reimplementazione PyTorch qui sotto presenta una variante semplificata a tre percorsi (le due conv temporali più un percorso 1×11\times1) così che il codice resti leggibile; omette il percorso max-pool. Lo segnaliamo esplicitamente perché è una deviazione dal paper, non il modulo originale.

Blocco 3: Modellazione di Sequenze con LSTM

L'output del modulo inception viene alimentato in uno strato LSTM che elabora l'intera sequenza di lunghezza TT. La LSTM cattura le dipendenze temporali a lungo raggio che le sole convoluzioni mancherebbero — cambi di regime, effetti legati all'ora del giorno e condizioni di mercato in evoluzione.

Lo stato nascosto finale della LSTM viene passato attraverso uno strato fully connected con attivazione softmax per produrre una distribuzione di probabilità sulle tre classi (up, down, stationary).

Funzione di Loss

Il modello è addestrato con la cross-entropy categorica:

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})

dove yi,cy_{i,c} è l'etichetta vera codificata one-hot e y^i,c\hat{y}_{i,c} è la probabilità predetta per la classe cc.

Reimplementazione in PyTorch

DeepLOB originale era costruito in Keras sul backend TensorFlow; il repository degli autori (zcakhaa) ha aggiunto in seguito un port in PyTorch. Il codice qui sotto è la nostra reimplementazione PyTorch illustrativa, non quella ufficiale. Devia dal paper in due modi che segnaliamo inline: omette il percorso max-pool dell'inception (tre percorsi anziché quattro) e aumenta il numero di canali. Il paper usa 16 filtri in tutti i blocchi conv e 32 filtri per percorso inception (input LSTM 3×32=963 \times 32 = 96), dando un modello compatto di circa 60k parametri. Lo snippet qui sotto usa 32 filtri conv / 64 filtri inception (input LSTM 192) — internamente coerente ed eseguibile, ma più pesante dell'originale. Per corrispondere al paper, imposta i canali conv a 16, i canali inception a 32 e 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

Preprocessing dei Dati

Il dataset FI-2010 viene fornito già normalizzato con z-score. Per i dati live dell'order book, devi normalizzare in modo analogo:

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
        )

Ciclo di Training

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")

Iperparametri chiave di training dal paper originale:

  • Optimizer: Adam con ϵ=1\epsilon = 1 (insolitamente grande — aiuta a stabilizzare gli aggiornamenti su dati finanziari rumorosi)
  • Learning rate: 0.01
  • Batch size: 32
  • Finestra di lookback: T=100T = 100 timestep

Monitoriamo l'F1 ponderato anziché l'accuratezza in validazione, perché le tre classi sono sbilanciate — e, come mostra la sezione successiva, l'F1 è la metrica che il paper originale enfatizza proprio per questo motivo.

Cosa DeepLOB È Costruito per Catturare

Segnali di microstruttura che DeepLOB apprende: order-flow imbalance, dinamiche di coda e pressione del mid-price ponderato per il volume

Un aspetto attraente di DeepLOB è quanto strettamente i suoi blocchi siano motivati dalle feature costruite a mano sopra. Vogliamo essere cauti qui: il paper le presenta come intuizioni di design, non come proprietà dimostrate della rete addestrata. Non pubblica visualizzazioni dei filtri, correlazioni attivazione-imbalance, né un'analisi di ciò a cui la LSTM presta attenzione. Quindi leggi quanto segue come intento architetturale, non come risultati misurati.

Primo blocco convoluzionale. I filtri 1×21 \times 2 accoppiano prezzo con volume a ciascun livello. Strutturalmente, questa è la stessa operazione che forma un prezzo ponderato per il volume — il paper motiva il primo strato notando che le sue feature map formano una quantità simile al micro-price. La rete è costruita per recuperare qualcosa in quella famiglia; non sosteniamo che riscopra in modo dimostrabile alcuno stimatore specifico.

Secondo blocco convoluzionale. Operando sulle coppie bid-ask, questi filtri sono posizionati per apprendere feature simili all'imbalance a livelli multipli — di nuovo, per costruzione anziché per correlazione dimostrata.

Modulo inception. Le convoluzioni temporali multi-scala intendono catturare il momentum a frequenze diverse: il percorso 1×11\times1 risponde alla struttura puntuale nel tempo, il percorso 3×13\times1 alle tendenze a breve termine, il percorso 5×15\times1 a quelle leggermente più lunghe, e (nel modulo completo) il percorso max-pool a una vista smussata.

Strato LSTM. Il componente ricorrente aggiunge una memoria delle dipendenze a lungo raggio che le convoluzioni non possono rappresentare — cambi di regime ed effetti legati all'ora del giorno. Evitiamo affermazioni più forti (come che la LSTM implementi un'"attenzione adattiva" che ripondera i dati recenti nei regimi volatili); il paper non lo mostra.

Prestazioni su FI-2010

Il paper originale valuta due protocolli. Il Setup 1 usa lo split di normalizzazione precedente del dataset; il Setup 2 è il setup di deep learning con cui la maggior parte dei lavori successivi si confronta. Poiché FI-2010 è sbilanciato per classe, il paper enfatizza l'F1, non l'accuratezza, e riporta cifre basate su precision/recall per i baseline non neurali (SVM, MLP). Ecco i numeri reali del Setup 2 (F1, %):

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

A k=10k = 10, DeepLOB riporta anche un'accuratezza dell'84.47% accanto al suo F1 di 83.40. Il Setup 2 riporta solo k{10,20,50}k \in \{10, 20, 50\} — qui non c'è una riga k=100k = 100. (Il Setup 1 riporta invece k{10,50,100}k \in \{10, 50, 100\}, con F1 di DeepLOB pari rispettivamente a 77.66 / 74.96 / 76.58.) I trattini sopra segnano le celle dei baseline che il paper non tabula a quegli orizzonti in questo protocollo.

Spiccano diverse osservazioni:

  1. DeepLOB batte i baseline con ampio margine sull'orizzonte breve. A k=10k = 10 il suo F1 (83.40) è ben al di sopra di LSTM (66.33), CNN-I (55.21), MLP (48.27) e SVM (35.88). L'ordinamento qualitativo — modelli deep ricorrenti-convoluzionali sopra CNN semplici, MLP e SVM — è la conclusione robusta.
  2. L'andamento sull'orizzonte non è monotono, non è "più lungo è più facile". Lungo il Setup 2, l'F1 di DeepLOB va 83.4072.8280.3583.40 \to 72.82 \to 80.35 man mano che kk va 10205010 \to 20 \to 50 — un pattern a U, con k=20k=20 il più difficile, non una netta marcia al rialzo. Se un orizzonte sia "più facile" dipende fortemente dal setup, dalla soglia α\alpha e dallo schema di smussamento delle etichette; non dovremmo leggere una legge universale in questi numeri.
  3. DeepLOB non domina ogni metodo classico in ogni cella. Contro baseline forti come C(TABL) i margini si restringono ad alcuni orizzonti, quindi "supera ovunque tutti gli approcci classici" sovrastima il caso. L'affermazione difendibile è che è competitivo-fino-al-migliore, e chiaramente il migliore sull'orizzonte breve.

E di nuovo: la visione del paper stesso è che FI-2010 sia troppo piccolo e troppo sottocampionato per risolvere la questione. La sua evidenza più solida è lo studio sull'LSE, dove DeepLOB si addestra su un insieme di titoli e predice comunque su titoli tenuti da parte — un risultato di transfer learning che FI-2010 non può mostrare.

LOBFrame e la Crisi di Replicabilità

Curve in-sample e out-of-sample divergenti che illustrano la crisi di replicabilità del deep learning sugli order book

Il framework di benchmark LOBFrame (Briola, Bartolucci, Aste, 2024) fornisce un complemento che invita alla riflessione su questi risultati. Sebbene l'architettura di DeepLOB sia solida, il framework evidenzia diverse importanti riserve:

Dipendenza dalla microstruttura. Le prestazioni del modello variano significativamente tra titoli con caratteristiche microstrutturali diverse. I titoli liquidi e a tick grande sono più facili da prevedere rispetto agli strumenti illiquidi e a tick piccolo. Un modello che appare forte su un titolo può degradare sostanzialmente su un altro.

Previsione vs profitto. Un'elevata accuratezza di classificazione non si traduce automaticamente in profitto di trading. LOBFrame insiste su metriche legate al fatto che un movimento previsto sia abbastanza grande da superare il bid-ask spread. Un modello deve avere ragione abbastanza spesso, e su movimenti abbastanza grandi, da superare lo spread — e per i titoli a spread stretto quell'asticella è alta.

Sensibilità alle etichette. La scelta della soglia α\alpha e dell'orizzonte kk influenza drasticamente sia l'accuratezza riportata sia il valore pratico delle previsioni. Etichette troppo strette producono target rumorosi; etichette troppo larghe producono previsioni banalmente "accurate" ma inutili.

Oltre DeepLOB: Il Panorama Attuale

Costellazione di approcci moderni alla previsione sull'order book che orbitano attorno al modello DeepLOB originale

Dalla pubblicazione di DeepLOB sono apparse diverse estensioni:

Transformer per i limit order book (Wallbridge, 2020). Spesso indicato in modo impreciso come "TransLOB" nella letteratura successiva, il modello in sé è un estrattore di feature convoluzionale causale/dilatato seguito da self-attention multi-head mascherata — non semplicemente DeepLOB con la LSTM sostituita da un Transformer; non riusa lo stack inception+conv. Ha riportato un nuovo stato dell'arte su FI-2010 per il suo tempo.

Varianti per il dominio crypto. Diversi gruppi applicano architetture in stile DeepLOB ai mercati delle criptovalute, dove le dinamiche dell'order book differiscono sostanzialmente da quelle azionarie — spread più ampi, trading 24/7 e un mix di partecipanti diverso che richiedono tutti il riaddestramento.

Varianti con attention aggiunta. Una linea di lavoro inserisce attention multi-head tra i blocchi inception e LSTM, consentendo alla rete di concentrarsi sui livelli o sui timestep più informativi per la previsione corrente.

LOB-Bench (Nagy, Frey, Li, Sarkar, Vyetrenko, Zohren, Calinescu, Foerster, 2025). Questo benchmark sposta l'attenzione dalla previsione alla generazione, valutando quanto siano realistici i modelli generativi di dati dell'order book — rilevante per il backtesting e l'addestramento di agent più che per la previsione direzionale.

Considerazioni Pratiche per la Produzione

Se stai pensando di mettere in produzione un modello in stile DeepLOB in un sistema di trading live, sorgono diverse preoccupazioni ingegneristiche:

Latenza

Il modello deve produrre previsioni entro il budget di latenza del tuo sistema di trading. Per i sistemi HFT che operano a latenze sub-millisecondo, anche un'inferenza PyTorch ottimizzata può essere troppo lenta. Le opzioni includono:

  • Export in ONNX con ottimizzazione TensorRT
  • Quantizzazione a INT8
  • Deployment su FPGA per le applicazioni più sensibili alla latenza

Pipeline dei Dati

Il modello si aspetta snapshot dell'order book normalizzati e allineati a una frequenza fissa. In pratica, gli aggiornamenti del LOB arrivano in modo asincrono. Ti serve:

  • Un motore di ricostruzione degli snapshot che mantiene lo stato corrente del book
  • Un campionatore a frequenza fissa che produce il tensore di input (T,40)(T, 40)
  • Normalizzazione online tramite statistiche rolling

Stabilità delle Feature

I parametri di normalizzazione z-score calcolati sui dati di training derivano nel tempo. I prezzi cambiano, i regimi di volatilità si spostano, la struttura del mercato evolve. Un sistema di produzione necessita di:

  • Finestre di normalizzazione rolling (ad esempio, ricalcolare le statistiche ogni giorno)
  • Rilevamento del regime per innescare il riaddestramento del modello
  • Monitoraggio dello shift della distribuzione di input

Overfitting sulla Microstruttura

I pattern del LOB sono specifici per venue e per strumento. Un modello addestrato su azioni NASDAQ non funzionerà su Binance BTC/USDT senza riaddestramento. Anche all'interno della stessa venue, i modelli possono andare in overfitting su:

  • Regime di tick size (titoli a tick grande vs a tick piccolo)
  • Pattern legati all'ora del giorno (asta di apertura, pausa pranzo, asta di chiusura)
  • Comportamento dei market maker che cambia nel tempo

Conclusione

DeepLOB rappresenta un'applicazione pulita e ben motivata del deep learning alla microstruttura di mercato. La sua architettura a tre blocchi — CNN per le feature spaziali, inception per i pattern temporali multi-scala, LSTM per le dipendenze sequenziali — si mappa naturalmente sulla struttura dei dati del limit order book.

L'intuizione chiave non è solo che un modello deep possa eguagliare o battere le feature costruite a mano, ma che i bias induttivi dell'architettura — i filtri 1×21 \times 2, il parallelismo dell'inception, la memoria ricorrente — codificano genuina conoscenza di dominio su come funzionano gli order book. Questa non è una rete profonda generica lanciata su dati finanziari; è un'architettura progettata attorno alla geometria specifica del limit order book. E la sua evidenza più convincente non è la classifica su FI-2010 di cui gli autori stessi diffidano, ma il risultato di transfer learning su un anno di dati LSE.

Per i practitioner, DeepLOB è un baseline solido e un utile mattone costruttivo. Per i ricercatori, dimostra che una progettazione architetturale ponderata — non la semplice scala del modello — guida le prestazioni su dati finanziari strutturati. Ricorda solo di citare i numeri reali, di tenere d'occhio lo schema delle etichette e di trattare l'accuratezza di classificazione come ben lontana dal profitto di trading.


Riferimenti

  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. Codice di DeepLOB (originale in Keras/TensorFlow; il repository fornisce anche un port in PyTorch): github.com/zcakhaa/DeepLOB-Deep-Convolutional-Neural-Networks-for-Limit-Order-Books

Disclaimer: le informazioni fornite in questo articolo hanno solo scopo didattico e informativo e non costituiscono consulenza finanziaria, di investimento o di trading. Il trading di criptovalute comporta un rischio significativo di perdita.

Autori

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

Resta un Passo Avanti al Mercato

Iscriviti alla nostra newsletter per approfondimenti esclusivi sul trading con IA, analisi di mercato e aggiornamenti sulla piattaforma.

Rispettiamo la tua privacy. Annulla l'iscrizione in qualsiasi momento.