← Volver a los artículos
May 26, 2026
5 min de lectura

DeepLOB: Aprendizaje Profundo en Libros de Órdenes Límite

DeepLOB: Aprendizaje Profundo en Libros de Órdenes Límite
#deep-learning
#order-book
#LOB
#microstructure
#HFT
#market-making
📖
Part 5 of 6 · Collection
Order Book & Market Microstructure

El libro de órdenes límite es la estructura de datos central de los mercados electrónicos modernos. Cada oferta de compra, cada oferta de venta, cada cancelación — todo vive en el LOB. Durante décadas, los investigadores cuantitativos han diseñado a mano características a partir de estos datos: ratios de desequilibrio de órdenes, precios medios ponderados, señales de posición en cola. DeepLOB tomó un camino diferente — aprende estas características directamente a partir de instantáneas crudas del libro de órdenes usando una arquitectura híbrida CNN-LSTM.

Publicado por Zhang, Zohren y Roberts en el Oxford-Man Institute en 2019, DeepLOB demostró que una única red profunda, entrenada de extremo a extremo con datos crudos del libro, es competitiva con — o mejor que — sólidas líneas base clásicas en la predicción del movimiento del precio medio. Sin embargo, debemos ser precisos respecto a la evidencia. El conjunto de datos de referencia FI-2010 es el que la mayoría de los artículos citan, pero los propios autores lo consideran insuficiente — "demasiado corto, submuestreado y tomado de un mercado menos líquido" — y presentan una segunda evaluación, mucho más grande, con datos de la Bolsa de Londres (LSE) como su resultado más sólido. Ese estudio con datos de la LSE abarca aproximadamente un año de cinco acciones de entrenamiento y cinco acciones de prueba, del orden de más de 100 millones de muestras, y demuestra aprendizaje por transferencia: un modelo entrenado con un conjunto de acciones sigue prediciendo sobre acciones no vistas. Ese resultado de transferencia, no la clasificación de FI-2010, es el verdadero titular del artículo. Esta publicación diseca la arquitectura, las matemáticas, las cifras reales y una reimplementación funcional en PyTorch.

El Libro de Órdenes Límite como Estructura de Datos

Vista isométrica de un libro de órdenes límite: niveles de volumen de compra y venta apilados formando un tensor de profundidad alrededor del precio medio

Un libro de órdenes límite mantiene dos listas ordenadas — ofertas de compra (bids) y ofertas de venta (asks) — cada una organizada por nivel de precio. En cada nivel, el libro registra el precio y el volumen agregado de las órdenes pendientes.

Para un libro de órdenes con LL niveles, una única instantánea en el momento tt es un vector de 4L4L valores:

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

donde piap_i^{a} y viav_i^{a} son el precio y el volumen de venta en el nivel ii, y pibp_i^{b}, vibv_i^{b} son los valores correspondientes del lado de compra. El nivel 1 contiene la mejor oferta de compra y la mejor oferta de venta (el BBO); su diferencia es el spread bid-ask.

En el conjunto de datos de referencia FI-2010, L=10L = 10, lo que da 40 características por instantánea. FI-2010 se construye a partir de aproximadamente 4 millones de mensajes crudos de órdenes límite de cinco acciones finlandesas en NASDAQ Nordic (Kesko, Outokumpu, Sampo, Rautaruukki, Wartsila) durante diez días de negociación consecutivos, del 1 al 14 de junio de 2010. Es crucial señalar que DeepLOB no se entrena con 4 millones de instantáneas: el benchmark submuestrea esos mensajes (cada 10 eventos) en una representación normalizada de 394,337 muestras. Esa forma submuestreada y normalizada es lo que el modelo realmente ve — una de las razones por las que los autores advierten contra tratar FI-2010 como la palabra final.

El Precio Medio y sus Movimientos

El precio medio en el momento tt es el promedio de la mejor oferta de compra y de venta:

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

DeepLOB predice la dirección del movimiento del precio medio en un horizonte futuro de kk eventos. La etiqueta es un retorno suavizado, pero hay que tener en cuenta que existen dos convenciones de suavizado en la literatura, y no son intercambiables:

  • Media futura frente al precio actual (Tsantekidis et al.): compara el precio medio promedio de los próximos kk eventos con el precio medio actual sin suavizar.
  • Media futura frente a media previa (Ntakaris et al., la etiqueta canónica de FI-2010, también la que usó DeepLOB para sus datos de la LSE): tanto la referencia como el objetivo están suavizados.
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}

Suavizar la referencia (no sólo el futuro) reduce sustancialmente el ruido de la etiqueta procedente de fluctuaciones de un solo tick, razón por la cual se prefiere la forma de media previa/siguiente. Cualquiera que sea la que se use, el retorno continuo se discretiza en tres clases — subida, bajada, estacionario — usando umbrales fijos ±α\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}

El conjunto de datos FI-2010 proporciona etiquetas para cinco horizontes de predicción: k{10,20,30,50,100}k \in \{10, 20, 30, 50, 100\} eventos.

Características Tradicionales del LOB

Antes del aprendizaje profundo, los investigadores dependían de características diseñadas a mano. Entender estas características es esencial, porque DeepLOB está deliberadamente estructurado para parecerse a muchas de ellas — su arquitectura está motivada por estas cantidades incluso cuando no las reproduce exactamente.

Desequilibrio del Libro de Órdenes (OBI)

La señal de microestructura más utilizada. En la parte superior del libro:

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

Más volumen de compra en relación con el volumen de venta sugiere presión alcista. El desequilibrio multinivel agrega a través de LL niveles:

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

Precio Medio Ponderado por Volumen

El precio medio estándar pondera ambos lados por igual. El precio medio ponderado por volumen (también llamado precio medio ponderado, o VAMP) se inclina hacia el lado más pesado del libro. Escribiendo el desequilibrio del tope del libro como I=v1b/(v1b+v1a)I = v_1^{b}/(v_1^{b}+v_1^{a}), es el promedio ponderado por el desequilibrio de los dos precios:

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

Nótese la ponderación cruzada: el precio de venta se pondera por el volumen de compra y viceversa. Cuando el volumen de compra domina (I1I \to 1), el precio medio ponderado se desplaza hacia el precio de venta — reflejando la expectativa de que un libro desequilibrado se resolverá moviéndose hacia el lado más pesado.

Una palabra de precaución sobre el nombre. Este precio medio ponderado por volumen, estático, no es el microprecio de Stoikov. El microprecio de Stoikov (2018) es un estimador ajustado por martingala Mt+g(I,S)M_t + g(I, S) obtenido mediante una corrección recursiva de cómo evolucionan el spread y el desequilibrio — todo el punto de Stoikov es que el precio medio ponderado ingenuo está sesgado y no es una martingala. Son dos objetos distintos; aquí usamos el precio medio ponderado por su simplicidad, no como sustituto del microprecio.

Desequilibrio de Profundidad

Mide la forma del libro más allá del 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 y 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}}

Estas características son potentes pero limitadas. Requieren experiencia en el dominio para diseñarlas, no capturan interacciones no lineales complejas e ignoran la dinámica temporal entre instantáneas consecutivas. DeepLOB aborda las tres limitaciones.

Arquitectura de DeepLOB

Arquitectura de DeepLOB: un tensor crudo del libro de órdenes fluyendo a través de capas convolucionales, un módulo inception y una LSTM

La arquitectura tiene tres bloques, cada uno con un papel distinto:

  1. Bloque convolucional — extrae características espaciales de la instantánea del libro de órdenes
  2. Módulo inception — captura patrones temporales multiescala
  3. Bloque LSTM — modela dependencias secuenciales a través del tiempo

La entrada de la red es un tensor de forma (T,L×4)(T, L \times 4), donde TT es la ventana de retrospección (100 pasos de tiempo en el artículo) y L×4=40L \times 4 = 40 para 10 niveles de precio y volumen en ambos lados.

Bloque 1: Extracción de Características Convolucionales

Las primeras capas convolucionales operan a lo largo de la dimensión de características para aprender interacciones entre precio y volumen en cada nivel.

Capa 1: Una convolución con tamaño de filtro 1×21 \times 2 y stride (1,2)(1, 2) aplicada a cada par precio-volumen. Esto resume cada par (pi,vi)(p_i, v_i) en una única característica — análogo a calcular una "interacción precio-volumen" en cada nivel. El ancho se reduce de 40 a 20.

Capa 2: Una convolución 1×21 \times 2, con stride (1,2)(1, 2) que opera a través de pares de compra-venta emparejados en el mismo nivel. Esto captura información de spread y desequilibrio por nivel. El ancho se reduce de 20 a 10.

Capa 3: Una convolución 1×101 \times 10 que integra a través de los 10 niveles, colapsando la dimensión de profundidad a un ancho de 1. Esto produce una única característica agregada por paso de tiempo que codifica el perfil de profundidad completo.

Cada paso 1×21 \times 2 / 1×101 \times 10 es seguido por dos convoluciones (4,1)(4, 1) a lo largo del eje temporal, cada una con activación LeakyReLU y normalización por lotes. Nótese que estas convoluciones (4,1)(4, 1) con padding=(1, 0) no preservan la dimensión temporal: cada una la acorta en un paso (Tout=Tin1T_{\text{out}} = T_{\text{in}} - 1). A través de las seis capas de este tipo en los bloques 1-3, el eje temporal se reduce de 100 a 94 antes del módulo inception — algo que los comentarios de forma en el código a continuación hacen explícito.

El diseño evita deliberadamente los filtros espaciales grandes. Los filtros 1×21 \times 2 respetan la estructura de emparejamiento natural de los datos LOB — precio con volumen, compra con venta. Este sesgo inductivo es crucial: un filtro 3×33 \times 3 mezclaría niveles y lados adyacentes de una manera que no tiene sentido financiero.

Bloque 2: Módulo Inception

Después de que el bloque convolucional reduce las 40 características a una representación espacial compacta (ancho 1), el módulo inception opera a lo largo de la dimensión temporal para capturar patrones en múltiples escalas temporales simultáneamente.

El módulo inception de DeepLOB tiene cuatro rutas paralelas, cada una produciendo 32 canales, todas concatenadas:

  • Cuello de botella 1×11 \times 1 \to convolución temporal 3×13 \times 1: patrones temporales a corto plazo (3 pasos de tiempo)
  • Cuello de botella 1×11 \times 1 \to convolución temporal 5×15 \times 1: patrones temporales a medio plazo (5 pasos de tiempo)
  • Max-pool 3×13 \times 1 \to convolución 1×11 \times 1: una ruta de pooling que mantiene una vista suavizada y submuestreada
  • (las reducciones 1×11 \times 1 actúan como cuellos de botella puntuales antes de las convoluciones temporales)

Cada ruta temporal usa relleno simétrico para que la dimensión temporal se mantenga dentro del bloque inception (a diferencia de los bloques convolucionales anteriores). Las salidas se concatenan a lo largo de la dimensión de canal, dando a la red acceso a características temporales multiescala a la vez.

Esto está inspirado en la arquitectura inception de GoogLeNet, pero adaptado para datos temporales 1D en lugar de imágenes 2D. La idea clave es que la dinámica del LOB opera en múltiples escalas de tiempo: el ruido tick a tick, el momentum a corto plazo y la reversión a la media a más largo plazo coexisten.

La reimplementación en PyTorch a continuación presenta una variante simplificada de tres rutas (las dos convoluciones temporales más una ruta 1×11\times1) para que el código siga siendo legible; omite la ruta de max-pool. Señalamos esto explícitamente porque es una desviación del artículo, no el módulo original.

Bloque 3: Modelado de Secuencias con LSTM

La salida del módulo inception se alimenta a una capa LSTM que procesa toda la secuencia de longitud TT. La LSTM captura dependencias temporales de largo alcance que las convoluciones por sí solas pasarían por alto — cambios de régimen, efectos de la hora del día y condiciones de mercado cambiantes.

El estado oculto final de la LSTM se pasa por una capa completamente conectada con activación softmax para producir una distribución de probabilidad sobre las tres clases (subida, bajada, estacionario).

Función de Pérdida

El modelo se entrena con entropía cruzada categórica:

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

donde yi,cy_{i,c} es la etiqueta verdadera codificada en one-hot y y^i,c\hat{y}_{i,c} es la probabilidad predicha para la clase cc.

Reimplementación en PyTorch

El DeepLOB original se construyó en Keras sobre el backend de TensorFlow; el repositorio de los autores (zcakhaa) añadió más tarde un port a PyTorch. El código a continuación es nuestra propia reimplementación ilustrativa en PyTorch, no la oficial. Se desvía del artículo en dos aspectos que señalamos en línea: omite la ruta de max-pool del inception (tres rutas en lugar de cuatro) y aumenta el número de canales. El artículo usa 16 filtros en todos los bloques convolucionales y 32 filtros por ruta de inception (entrada LSTM 3×32=963 \times 32 = 96), dando un modelo compacto de aproximadamente 60 mil parámetros. El fragmento a continuación usa 32 filtros conv / 64 filtros de inception (entrada LSTM 192) — internamente consistente y ejecutable, pero más pesado que el original. Para igualar el artículo, establezca los canales convolucionales en 16, los canales de inception en 32, y 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

Preprocesamiento de Datos

El conjunto de datos FI-2010 se distribuye normalizado con z-score. Para datos de libro de órdenes en vivo, es necesario normalizar de manera similar:

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
        )

Bucle de Entrenamiento

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

Hiperparámetros clave de entrenamiento del artículo original:

  • Optimizador: Adam con ϵ=1\epsilon = 1 (inusualmente grande — ayuda a estabilizar las actualizaciones en datos financieros ruidosos)
  • Tasa de aprendizaje: 0.01
  • Tamaño de lote: 32
  • Ventana de retrospección: T=100T = 100 pasos de tiempo

Rastreamos el F1 ponderado en lugar de la precisión en la validación, porque las tres clases están desequilibradas — y, como muestra la siguiente sección, el F1 es la métrica que el artículo original enfatiza precisamente por esa razón.

Lo que DeepLOB está Diseñado para Capturar

Señales de microestructura que DeepLOB aprende: desequilibrio del flujo de órdenes, dinámica de la cola y presión del precio medio ponderado por volumen

Un aspecto atractivo de DeepLOB es cuán estrechamente sus bloques están motivados por las características diseñadas a mano descritas anteriormente. Debemos tener cuidado aquí: el artículo presenta esto como intuiciones de diseño, no como propiedades demostradas de la red entrenada. No publica visualizaciones de filtros, correlaciones de desequilibrio de activación, ni un análisis de a qué atiende la LSTM. Así que hay que leer lo siguiente como intención arquitectónica, no como hallazgos medidos.

Primer bloque convolucional. Los filtros 1×21 \times 2 emparejan precio con volumen en cada nivel. Estructuralmente, esta es la misma operación que forma un precio ponderado por volumen — el artículo motiva la primera capa señalando que sus mapas de características forman una cantidad similar a un microprecio. La red está construida para recuperar algo de esa familia; no afirmamos que redescubra demostrablemente ningún estimador específico.

Segundo bloque convolucional. Al operar a través de pares de compra-venta, estos filtros están posicionados para aprender características similares al desequilibrio en múltiples niveles — de nuevo, por construcción más que por correlación demostrada.

Módulo inception. Las convoluciones temporales multiescala están destinadas a capturar el momentum en diferentes frecuencias: la ruta 1×11\times1 responde a la estructura puntual, la ruta 3×13\times1 a las tendencias a corto plazo, la ruta 5×15\times1 a tendencias ligeramente más largas y (en el módulo completo) la ruta de max-pool a una vista suavizada.

Capa LSTM. El componente recurrente añade memoria de dependencias de largo alcance que las convoluciones no pueden representar — cambios de régimen y efectos de la hora del día. Evitamos afirmaciones más fuertes (como que la LSTM implementa una "atención adaptativa" que reajusta el peso de los datos recientes en regímenes volátiles); el artículo no muestra esto.

Rendimiento en FI-2010

El artículo original evalúa dos protocolos. La Configuración 1 usa la división de normalización más antigua del conjunto de datos; la Configuración 2 es la configuración de aprendizaje profundo con la que se compara la mayoría del trabajo posterior. Debido a que FI-2010 tiene clases desequilibradas, el artículo enfatiza el F1, no la precisión, e informa cifras basadas en precisión/recall para las líneas base no neuronales (SVM, MLP). Aquí están las cifras reales de la Configuración 2 (F1, %):

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

En k=10k = 10, DeepLOB también reporta una precisión del 84.47% junto a su F1 de 83.40. La Configuración 2 sólo reporta k{10,20,50}k \in \{10, 20, 50\} — no hay fila para k=100k = 100 aquí. (La Configuración 1 sí reporta k{10,50,100}k \in \{10, 50, 100\}, con un F1 de DeepLOB de 77.66 / 74.96 / 76.58 respectivamente.) Los guiones anteriores marcan celdas de líneas base que el artículo no tabula en esos horizontes en este protocolo.

Varias observaciones destacan:

  1. DeepLOB supera a las líneas base por un amplio margen en el horizonte corto. En k=10k = 10 su F1 (83.40) está muy por encima de LSTM (66.33), CNN-I (55.21), MLP (48.27) y SVM (35.88). El orden cualitativo — modelos convolucionales-recurrentes profundos por encima de CNNs simples, MLPs y SVMs — es la conclusión robusta.
  2. La tendencia del horizonte es no monótona, no "más largo es más fácil". A lo largo de la Configuración 2, el F1 de DeepLOB va 83.4072.8280.3583.40 \to 72.82 \to 80.35 a medida que kk va de 10205010 \to 20 \to 50 — un patrón en forma de U, con k=20k=20 siendo el más difícil, no una progresión ascendente limpia. Si un horizonte es "más fácil" depende en gran medida de la configuración, el umbral α\alpha y el esquema de suavizado de etiquetas; no deberíamos leer una ley universal en estos números.
  3. DeepLOB no domina todos los métodos clásicos en cada celda. Frente a líneas base sólidas como C(TABL), los márgenes se estrechan en algunos horizontes, por lo que "supera a todos los enfoques clásicos en todas partes" exagera el caso. La afirmación defendible es que es competitivo a superior, y claramente el mejor en el horizonte corto.

Y de nuevo: la propia visión del artículo es que FI-2010 es demasiado pequeño y demasiado submuestreado para zanjar la cuestión. Su evidencia más sólida es el estudio de la LSE, donde DeepLOB se entrena con un conjunto de acciones y sigue prediciendo sobre acciones no vistas — un resultado de aprendizaje por transferencia que FI-2010 no puede mostrar.

LOBFrame y la Crisis de Replicación

Curvas divergentes dentro y fuera de la muestra que ilustran la crisis de replicación del aprendizaje profundo en libros de órdenes

El marco de referencia LOBFrame (Briola, Bartolucci, Aste, 2024) proporciona un complemento aleccionador a estos resultados. Si bien la arquitectura de DeepLOB es sólida, el marco destaca varias advertencias importantes:

Dependencia de la microestructura. El rendimiento del modelo varía significativamente entre acciones con diferentes características microestructurales. Las acciones líquidas y de tick grande son más fáciles de predecir que los instrumentos ilíquidos y de tick pequeño. Un modelo que parece sólido en una acción puede degradarse sustancialmente en otra.

Predicción frente a beneficio. Una alta precisión de clasificación no se traduce automáticamente en beneficio comercial. LOBFrame enfatiza métricas ligadas a si un movimiento predicho es lo suficientemente grande como para superar el spread bid-ask. Un modelo necesita acertar con la suficiente frecuencia, y en movimientos suficientemente grandes, para superar el spread — y para acciones de spread ajustado ese listón es alto.

Sensibilidad de la etiqueta. La elección del umbral α\alpha y del horizonte kk afecta dramáticamente tanto a la precisión reportada como al valor práctico de las predicciones. Etiquetas demasiado ajustadas producen objetivos ruidosos; etiquetas demasiado amplias producen predicciones trivialmente "precisas" pero inútiles.

Más Allá de DeepLOB: El Panorama Actual

Constelación de enfoques modernos de predicción del libro de órdenes orbitando el modelo DeepLOB original

Desde la publicación de DeepLOB, han aparecido varias extensiones:

Transformers para libros de órdenes límite (Wallbridge, 2020). A menudo referido de manera imprecisa como "TransLOB" en la literatura posterior, el modelo en sí es un extractor de características convolucional causal/dilatado seguido de autoatención enmascarada de múltiples cabezas — no simplemente DeepLOB con la LSTM sustituida por un Transformer; no reutiliza la pila de inception+conv. Reportó un nuevo estado del arte en FI-2010 para su época.

Variantes en el dominio cripto. Varios grupos aplican arquitecturas al estilo DeepLOB a mercados de criptomonedas, donde la dinámica del libro de órdenes difiere sustancialmente de las acciones — spreads más amplios, negociación 24/7 y una combinación diferente de participantes que exigen un reentrenamiento.

Variantes aumentadas con atención. Una línea de trabajo inserta atención de múltiples cabezas entre los bloques de inception y LSTM, permitiendo que la red se enfoque en los niveles o pasos de tiempo más informativos para la predicción actual.

LOB-Bench (Nagy, Frey, Li, Sarkar, Vyetrenko, Zohren, Calinescu, Foerster, 2025). Este benchmark desplaza la atención de la predicción a la generación, calificando cuán realistas son los modelos generativos de datos del libro de órdenes — relevante para backtesting y entrenamiento de agentes más que para la predicción direccional.

Consideraciones Prácticas para Producción

Si se está considerando desplegar un modelo al estilo DeepLOB en un sistema de trading en vivo, surgen varias preocupaciones de ingeniería:

Latencia

El modelo debe producir predicciones dentro del presupuesto de latencia de tu sistema de trading. Para sistemas de HFT que operan a latencias inferiores al milisegundo, incluso una inferencia optimizada en PyTorch puede ser demasiado lenta. Las opciones incluyen:

  • Exportación a ONNX con optimización TensorRT
  • Cuantización a INT8
  • Despliegue en FPGA para las aplicaciones más sensibles a la latencia

Pipeline de Datos

El modelo espera instantáneas del libro de órdenes normalizadas y alineadas a una frecuencia fija. En la práctica, las actualizaciones del LOB llegan de forma asíncrona. Se necesita:

  • Un motor de reconstrucción de instantáneas que mantenga el estado actual del libro
  • Un muestreador de frecuencia fija que produzca el tensor de entrada (T,40)(T, 40)
  • Normalización en línea usando estadísticas móviles

Estabilidad de las Características

Los parámetros de normalización z-score calculados sobre datos de entrenamiento derivan con el tiempo. Los precios cambian, los regímenes de volatilidad se desplazan, la estructura del mercado evoluciona. Un sistema de producción necesita:

  • Ventanas de normalización móviles (por ejemplo, recalcular estadísticas diariamente)
  • Detección de régimen para desencadenar el reentrenamiento del modelo
  • Monitoreo de cambios en la distribución de la entrada

Sobreajuste a la Microestructura

Los patrones del LOB son específicos de la plaza y del instrumento. Un modelo entrenado con acciones de NASDAQ no funcionará en Binance BTC/USDT sin reentrenamiento. Incluso dentro de la misma plaza, los modelos pueden sobreajustarse a:

  • El régimen de tamaño de tick (acciones de tick grande frente a tick pequeño)
  • Patrones de hora del día (subasta de apertura, calma del mediodía, cruce de cierre)
  • Comportamiento de los creadores de mercado que cambia con el tiempo

Conclusión

DeepLOB representa una aplicación limpia y bien motivada del aprendizaje profundo a la microestructura de mercado. Su arquitectura de tres bloques — CNN para características espaciales, inception para patrones temporales multiescala, LSTM para dependencias secuenciales — se mapea naturalmente en la estructura de los datos del libro de órdenes límite.

La idea clave no es sólo que un modelo profundo pueda igualar o superar las características diseñadas a mano, sino que los sesgos inductivos de la arquitectura — los filtros 1×21 \times 2, el paralelismo del inception, la memoria recurrente — codifican conocimiento genuino del dominio sobre cómo funcionan los libros de órdenes. Esta no es una red profunda genérica aplicada a datos financieros; es una arquitectura diseñada en torno a la geometría específica del libro de órdenes límite. Y su evidencia más convincente no es la clasificación de FI-2010 de la que los propios autores desconfían, sino el resultado de aprendizaje por transferencia sobre un año de datos de la LSE.

Para los profesionales, DeepLOB es una sólida línea base y un útil bloque de construcción. Para los investigadores, demuestra que el diseño reflexivo de arquitectura — no sólo la escala del modelo — impulsa el rendimiento en datos financieros estructurados. Sólo recuerda citar las cifras reales, vigilar el esquema de etiquetado y tratar la precisión de clasificación como algo muy lejano al beneficio comercial.


Referencias

  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

Mantente a la vanguardia

Suscríbete a nuestro boletín para recibir información exclusiva sobre trading con IA, análisis de mercado y actualizaciones de la plataforma.

Respetamos tu privacidad. Puedes darte de baja en cualquier momento.