← Voltar aos artigos
May 26, 2026
5 min read

DeepLOB: Aprendizado Profundo em Livros de Ofertas Limitadas

DeepLOB: Aprendizado Profundo em Livros de Ofertas Limitadas
#deep-learning
#order-book
#LOB
#microstructure
#HFT
#market-making
📖
Part 5 of 6 · Collection
Order Book & Market Microstructure

O livro de ofertas limitadas é a estrutura de dados central dos mercados eletrônicos modernos. Cada oferta de compra, cada oferta de venda, cada cancelamento — tudo vive no LOB. Por décadas, pesquisadores quantitativos criaram manualmente características a partir desses dados: razões de desequilíbrio de ordens, preços médios ponderados, sinais de posição na fila. O DeepLOB seguiu um caminho diferente — aprende essas características diretamente a partir de instantâneos brutos do livro de ofertas usando uma arquitetura híbrida CNN-LSTM.

Publicado por Zhang, Zohren e Roberts no Oxford-Man Institute em 2019, o DeepLOB mostrou que uma única rede profunda, treinada de ponta a ponta com dados brutos do livro, é competitiva com — ou melhor do que — fortes baselines clássicas na previsão do movimento do preço médio. Devemos, no entanto, ser precisos quanto à evidência. O conjunto de dados de referência FI-2010 é o que a maioria dos artigos cita, mas os próprios autores o consideram insuficiente — "curto demais, subamostrado e proveniente de um mercado menos líquido" — e apresentam uma segunda avaliação, muito maior, com dados da Bolsa de Londres (LSE) como seu resultado mais forte. Esse estudo com dados da LSE abrange aproximadamente um ano em cinco ações de treinamento e cinco ações de teste, na ordem de mais de 100 milhões de amostras, e demonstra aprendizado por transferência: um modelo treinado em um conjunto de ações continua prevendo em ações não vistas. Esse resultado de transferência, e não a classificação do FI-2010, é a verdadeira manchete do artigo. Este post dissseca a arquitetura, a matemática, os números reais e uma reimplementação funcional em PyTorch.

O Livro de Ofertas Limitadas como Estrutura de Dados

Vista isométrica de um livro de ofertas limitadas: níveis de volume de compra e venda empilhados formando um tensor de profundidade ao redor do preço médio

Um livro de ofertas limitadas mantém duas listas ordenadas — bids (ordens de compra) e asks (ordens de venda) — cada uma organizada por nível de preço. Em cada nível, o livro registra o preço e o volume agregado das ordens pendentes.

Para um livro de ofertas com LL níveis, um único instantâneo no momento tt é um vetor 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}]

onde piap_i^{a} e viav_i^{a} são o preço e o volume de venda no nível ii, e pibp_i^{b}, vibv_i^{b} são os valores correspondentes do lado de compra. O nível 1 contém a melhor oferta de compra e a melhor oferta de venda (o BBO); sua diferença é o spread bid-ask.

No conjunto de dados de referência FI-2010, L=10L = 10, resultando em 40 características por instantâneo. O FI-2010 é construído a partir de aproximadamente 4 milhões de mensagens brutas de ordens limitadas de cinco ações finlandesas na NASDAQ Nordic (Kesko, Outokumpu, Sampo, Rautaruukki, Wartsila) ao longo de dez dias consecutivos de negociação, de 1 a 14 de junho de 2010. É crucial notar que o DeepLOB não treina com 4 milhões de instantâneos: o benchmark subamostra essas mensagens (a cada 10 eventos) em uma representação normalizada de 394.337 amostras. Essa forma subamostrada e normalizada é o que o modelo realmente vê — uma das razões pelas quais os autores advertem contra tratar o FI-2010 como a palavra final.

Preço Médio e Seus Movimentos

O preço médio no momento tt é a média entre a melhor oferta de compra e a de venda:

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

O DeepLOB prevê a direção do movimento do preço médio em um horizonte futuro de kk eventos. O rótulo é um retorno suavizado, mas atenção: existem duas convenções de suavização na literatura, e elas não são intercambiáveis:

  • Média futura vs. preço atual (Tsantekidis et al.): compara o preço médio médio dos próximos kk eventos com o preço médio atual bruto.
  • Média futura vs. média anterior (Ntakaris et al., o rótulo canônico do FI-2010, também o usado pelo DeepLOB para seus dados da LSE): tanto a referência quanto o alvo são 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 a referência (não apenas o futuro) reduz substancialmente o ruído do rótulo proveniente de flutuações de tick único, razão pela qual a forma de média anterior/seguinte é preferida. Seja qual for a usada, o retorno contínuo é discretizado em três classes — alta, baixa, estacionário — usando limiares fixos ±α\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}

O conjunto de dados FI-2010 fornece rótulos para cinco horizontes de previsão: k{10,20,30,50,100}k \in \{10, 20, 30, 50, 100\} eventos.

Características Tradicionais do LOB

Antes do aprendizado profundo, os pesquisadores dependiam de características criadas manualmente. Entender essas características é essencial, porque o DeepLOB é deliberadamente estruturado para se assemelhar a muitas delas — sua arquitetura é motivada por essas grandezas mesmo quando não as reproduz exatamente.

Desequilíbrio do Livro de Ofertas (OBI)

O sinal de microestrutura mais utilizado. No topo do livro:

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

Mais volume de compra em relação ao volume de venda sugere pressão de alta. O desequilíbrio multinível agrega através de LL níveis:

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

Preço Médio Ponderado por Volume

O preço médio padrão pondera igualmente os dois lados. O preço médio ponderado por volume (também chamado de preço médio ponderado, ou VAMP) inclina-se para o lado mais pesado do livro. Escrevendo o desequilíbrio do topo do livro como I=v1b/(v1b+v1a)I = v_1^{b}/(v_1^{b}+v_1^{a}), ele é a média ponderada pelo desequilíbrio dos dois preços:

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

Observe a ponderação cruzada: o preço de venda é ponderado pelo volume de compra e vice-versa. Quando o volume de compra domina (I1I \to 1), o preço médio ponderado se desloca em direção ao preço de venda — refletindo a expectativa de que um livro desequilibrado se resolverá movendo-se em direção ao lado mais pesado.

Uma palavra de cautela sobre a nomenclatura. Este preço médio ponderado por volume, estático, não é o microprice de Stoikov. O micro-price de Stoikov (2018) é um estimador ajustado por martingale Mt+g(I,S)M_t + g(I, S) obtido por uma correção recursiva de como o spread e o desequilíbrio evoluem — todo o ponto de Stoikov é que o preço médio ponderado ingênuo é enviesado e não é uma martingale. São dois objetos distintos; usamos aqui o preço médio ponderado por sua simplicidade, não como substituto do microprice.

Desequilíbrio de Profundidade

Mede a forma do livro além do 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}}

Essas características são poderosas, mas limitadas. Requerem expertise de domínio para serem projetadas, não capturam interações não lineares complexas e ignoram a dinâmica temporal entre instantâneos consecutivos. O DeepLOB aborda as três limitações.

Arquitetura do DeepLOB

Arquitetura do DeepLOB: um tensor bruto do livro de ofertas fluindo através de camadas convolucionais, um módulo inception e uma LSTM

A arquitetura possui três blocos, cada um com um papel distinto:

  1. Bloco convolucional — extrai características espaciais do instantâneo do livro de ofertas
  2. Módulo inception — captura padrões temporais em múltiplas escalas
  3. Bloco LSTM — modela dependências sequenciais ao longo do tempo

A entrada da rede é um tensor de forma (T,L×4)(T, L \times 4), onde TT é a janela de retrospecção (100 passos de tempo no artigo) e L×4=40L \times 4 = 40 para 10 níveis de preço e volume em ambos os lados.

Bloco 1: Extração de Características Convolucionais

As primeiras camadas convolucionais operam ao longo da dimensão de características para aprender interações entre preço e volume em cada nível.

Camada 1: Uma convolução com tamanho de filtro 1×21 \times 2 e stride (1,2)(1, 2) aplicada a cada par preço-volume. Isso resume cada par (pi,vi)(p_i, v_i) em uma única característica — análogo a calcular uma "interação preço-volume" em cada nível. A largura se reduz de 40 para 20.

Camada 2: Uma convolução 1×21 \times 2, stride (1,2)(1, 2) que opera sobre pares compra-venda pareados no mesmo nível. Isso captura informações de spread e desequilíbrio por nível. A largura se reduz de 20 para 10.

Camada 3: Uma convolução 1×101 \times 10 que integra ao longo de todos os 10 níveis, colapsando a dimensão de profundidade para largura 1. Isso produz uma única característica agregada por passo de tempo que codifica o perfil de profundidade completo.

Cada etapa 1×21 \times 2 / 1×101 \times 10 é seguida por duas convoluções (4,1)(4, 1) ao longo do eixo temporal, cada uma com ativação LeakyReLU e normalização em lote. Note que essas convoluções (4,1)(4, 1) com padding=(1, 0) não preservam a dimensão temporal: cada uma a encurta em um passo (Tout=Tin1T_{\text{out}} = T_{\text{in}} - 1). Ao longo das seis camadas desse tipo nos blocos 1-3, o eixo temporal se reduz de 100 para 94 antes do módulo inception — algo que os comentários de forma no código abaixo tornam explícito.

O design evita deliberadamente filtros espaciais grandes. Os filtros 1×21 \times 2 respeitam a estrutura de pareamento natural dos dados LOB — preço com volume, compra com venda. Esse viés indutivo é crucial: um filtro 3×33 \times 3 misturaria níveis e lados adjacentes de uma forma que não tem significado financeiro.

Bloco 2: Módulo Inception

Depois que o bloco convolucional reduz as 40 características a uma representação espacial compacta (largura 1), o módulo inception opera ao longo da dimensão temporal para capturar padrões em múltiplas escalas temporais simultaneamente.

O módulo inception do DeepLOB tem quatro caminhos paralelos, cada um produzindo 32 canais, todos concatenados:

  • Gargalo 1×11 \times 1 \to conv temporal 3×13 \times 1: padrões temporais de curto prazo (3 passos de tempo)
  • Gargalo 1×11 \times 1 \to conv temporal 5×15 \times 1: padrões temporais de médio prazo (5 passos de tempo)
  • Max-pool 3×13 \times 1 \to conv 1×11 \times 1: um caminho de pooling que mantém uma visão suavizada e subamostrada
  • (as reduções 1×11 \times 1 atuam como gargalos pontuais antes das convoluções temporais)

Cada caminho temporal usa preenchimento simétrico para que a dimensão temporal seja mantida dentro do bloco inception (diferentemente dos blocos convolucionais acima). As saídas são concatenadas ao longo da dimensão de canal, dando à rede acesso a características temporais em múltiplas escalas de uma só vez.

Isso é inspirado na arquitetura inception do GoogLeNet, mas adaptado para dados temporais 1D em vez de imagens 2D. A ideia chave é que a dinâmica do LOB opera em múltiplas escalas de tempo: ruído tick a tick, momentum de curto prazo e reversão à média de horizonte mais longo coexistem.

A reimplementação em PyTorch abaixo apresenta uma variante simplificada de três caminhos (as duas convoluções temporais mais um caminho 1×11\times1) para que o código permaneça legível; ela omite o caminho de max-pool. Chamamos isso explicitamente porque é um desvio do artigo, não o módulo original.

Bloco 3: Modelagem de Sequência com LSTM

A saída do módulo inception é alimentada a uma camada LSTM que processa toda a sequência de comprimento TT. A LSTM captura dependências temporais de longo alcance que apenas convoluções perderiam — mudanças de regime, efeitos de hora do dia e condições de mercado em evolução.

O estado oculto final da LSTM passa por uma camada totalmente conectada com ativação softmax para produzir uma distribuição de probabilidade sobre as três classes (alta, baixa, estacionário).

Função de Perda

O modelo é treinado com entropia 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})

onde yi,cy_{i,c} é o rótulo verdadeiro codificado em one-hot e y^i,c\hat{y}_{i,c} é a probabilidade prevista para a classe cc.

Reimplementação em PyTorch

O DeepLOB original foi construído em Keras sobre o backend TensorFlow; o repositório dos autores (zcakhaa) mais tarde adicionou uma porta para PyTorch. O código abaixo é nossa própria reimplementação ilustrativa em PyTorch, não a oficial. Ela se desvia do artigo em dois pontos que sinalizamos em linha: omite o caminho de max-pool do inception (três caminhos em vez de quatro) e aumenta a contagem de canais. O artigo usa 16 filtros em todos os blocos convolucionais e 32 filtros por caminho de inception (entrada LSTM 3×32=963 \times 32 = 96), resultando em um modelo compacto de aproximadamente 60 mil parâmetros. O trecho abaixo usa 32 filtros conv / 64 filtros de inception (entrada LSTM 192) — internamente consistente e executável, mas mais pesado do que o original. Para corresponder ao artigo, defina os canais conv como 16, os canais de inception como 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

Pré-processamento de Dados

O conjunto de dados FI-2010 é distribuído normalizado por z-score. Para dados de livro de ofertas ao vivo, você precisa normalizar de forma semelhante:

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
        )

Loop de Treinamento

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

Principais hiperparâmetros de treinamento do artigo original:

  • Otimizador: Adam com ϵ=1\epsilon = 1 (incomumente grande — ajuda a estabilizar as atualizações em dados financeiros ruidosos)
  • Taxa de aprendizado: 0.01
  • Tamanho do lote: 32
  • Janela de retrospecção: T=100T = 100 passos de tempo

Rastreamos o F1 ponderado em vez da acurácia na validação, porque as três classes são desbalanceadas — e, como mostra a próxima seção, o F1 é a métrica que o artigo original enfatiza exatamente por essa razão.

O Que o DeepLOB Foi Construído para Capturar

Sinais de microestrutura que o DeepLOB aprende: desequilíbrio do fluxo de ordens, dinâmica de fila e pressão do preço médio ponderado por volume

Um aspecto atraente do DeepLOB é o quanto seus blocos são motivados por as características criadas manualmente acima. Devemos ser cuidadosos aqui: o artigo apresenta isso como intuições de design, não como propriedades comprovadas da rede treinada. Ele não publica visualizações de filtros, correlações de desequilíbrio de ativação, ou uma análise do que a LSTM atende. Portanto, leia o que segue como intenção arquitetônica, não como achados medidos.

Primeiro bloco convolucional. Os filtros 1×21 \times 2 pareiam preço com volume em cada nível. Estruturalmente, essa é a mesma operação que forma um preço ponderado por volume — o artigo motiva a primeira camada observando que seus mapas de características formam uma grandeza semelhante a um microprice. A rede é construída para recuperar algo dessa família; não afirmamos que ela redescubra comprovadamente qualquer estimador específico.

Segundo bloco convolucional. Ao operar sobre pares compra-venda, esses filtros estão posicionados para aprender características semelhantes ao desequilíbrio em múltiplos níveis — novamente, por construção e não por correlação demonstrada.

Módulo inception. As convoluções temporais em múltiplas escalas visam capturar momentum em diferentes frequências: o caminho 1×11\times1 responde à estrutura pontual, o caminho 3×13\times1 a tendências de curto prazo, o caminho 5×15\times1 a tendências ligeiramente mais longas e (no módulo completo) o caminho de max-pool a uma visão suavizada.

Camada LSTM. O componente recorrente adiciona memória de dependências de longo alcance que as convoluções não conseguem representar — mudanças de regime e efeitos de hora do dia. Evitamos afirmações mais fortes (como a LSTM implementar uma "atenção adaptativa" que reponderasse dados recentes em regimes voláteis); o artigo não demonstra isso.

Desempenho no FI-2010

O artigo original avalia dois protocolos. O Setup 1 usa a divisão de normalização mais antiga do conjunto de dados; o Setup 2 é a configuração de aprendizado profundo com a qual a maioria dos trabalhos subsequentes se compara. Como o FI-2010 tem classes desbalanceadas, o artigo enfatiza F1, não acurácia, e reporta números baseados em precisão/recall para as baselines não neurais (SVM, MLP). Aqui estão os números reais do Setup 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

Em k=10k = 10, o DeepLOB também reporta 84,47% de acurácia junto ao seu F1 de 83,40. O Setup 2 reporta apenas k{10,20,50}k \in \{10, 20, 50\} — não há linha de k=100k = 100 aqui. (O Setup 1 reporta k{10,50,100}k \in \{10, 50, 100\}, com F1 do DeepLOB de 77,66 / 74,96 / 76,58 respectivamente.) Os traços acima marcam células de baseline que o artigo não tabula nesses horizontes neste protocolo.

Várias observações se destacam:

  1. O DeepLOB supera as baselines por uma ampla margem no horizonte curto. Em k=10k = 10, seu F1 (83,40) está bem acima de LSTM (66,33), CNN-I (55,21), MLP (48,27) e SVM (35,88). A ordenação qualitativa — modelos convolucionais-recorrentes profundos acima de CNNs simples, MLPs e SVMs — é a conclusão robusta.
  2. A tendência do horizonte é não monotônica, não "mais longo é mais fácil". No Setup 2, o F1 do DeepLOB vai de 83,4072,8280,3583,40 \to 72,82 \to 80,35 conforme kk vai de 10205010 \to 20 \to 50 — um padrão em forma de U, com k=20k=20 sendo o mais difícil, não uma marcha ascendente limpa. Se um horizonte é "mais fácil" depende fortemente da configuração, do limiar α\alpha e do esquema de suavização de rótulos; não devemos ler uma lei universal nesses números.
  3. O DeepLOB não domina todos os métodos clássicos em todas as células. Contra baselines fortes como C(TABL), as margens se estreitam em alguns horizontes, então "supera todas as abordagens clássicas em todos os lugares" exagera o caso. A afirmação defensável é que ele é competitivo a superior, e claramente o melhor no horizonte curto.

E novamente: a própria visão do artigo é que o FI-2010 é pequeno demais e subamostrado demais para resolver a questão. Sua evidência mais forte é o estudo da LSE, onde o DeepLOB treina em um conjunto de ações e ainda assim prevê em ações não vistas — um resultado de aprendizado por transferência que o FI-2010 não pode mostrar.

LOBFrame e a Crise de Replicação

Curvas divergentes dentro e fora da amostra ilustrando a crise de replicação do aprendizado profundo em livros de ofertas

O framework de benchmark LOBFrame (Briola, Bartolucci, Aste, 2024) oferece um complemento sóbrio a esses resultados. Embora a arquitetura do DeepLOB seja sólida, o framework destaca várias ressalvas importantes:

Dependência de microestrutura. O desempenho do modelo varia significativamente entre ações com características microestruturais diferentes. Ações líquidas com tick grande são mais fáceis de prever do que instrumentos ilíquidos com tick pequeno. Um modelo que parece forte em uma ação pode se degradar substancialmente em outra.

Previsão vs. lucro. Alta acurácia de classificação não se traduz automaticamente em lucro de negociação. O LOBFrame enfatiza métricas ligadas a se um movimento previsto é grande o suficiente para superar o spread bid-ask. Um modelo precisa acertar com frequência suficiente, e em movimentos grandes o suficiente, para superar o spread — e para ações de spread apertado essa barra é alta.

Sensibilidade ao rótulo. A escolha do limiar α\alpha e do horizonte kk afeta dramaticamente tanto a acurácia reportada quanto o valor prático das previsões. Rótulos muito apertados produzem alvos ruidosos; rótulos muito amplos produzem previsões trivialmente "precisas", mas inúteis.

Além do DeepLOB: O Panorama Atual

Constelação de abordagens modernas de previsão de livros de ofertas orbitando o modelo DeepLOB original

Desde a publicação do DeepLOB, várias extensões surgiram:

Transformers para livros de ofertas limitadas (Wallbridge, 2020). Frequentemente referido de forma imprecisa como "TransLOB" na literatura posterior, o modelo em si é um extrator de características convolucional causal/dilatado seguido de autoatenção mascarada de múltiplas cabeças — não simplesmente o DeepLOB com a LSTM trocada por um Transformer; ele não reutiliza a pilha inception+conv. Reportou um novo estado da arte no FI-2010 para sua época.

Variantes do domínio cripto. Vários grupos aplicam arquiteturas ao estilo DeepLOB a mercados de criptomoedas, onde a dinâmica do livro de ofertas difere substancialmente das ações — spreads mais amplos, negociação 24/7 e uma combinação diferente de participantes, tudo isso exigindo retreinamento.

Variantes aumentadas com atenção. Uma linha de trabalho insere atenção de múltiplas cabeças entre os blocos de inception e LSTM, permitindo que a rede se concentre nos níveis ou passos de tempo mais informativos para a previsão atual.

LOB-Bench (Nagy, Frey, Li, Sarkar, Vyetrenko, Zohren, Calinescu, Foerster, 2025). Este benchmark desloca a atenção da previsão para a geração, avaliando quão realistas são os modelos generativos de dados de livro de ofertas — relevante para backtesting e treinamento de agentes, e não para previsão direcional.

Considerações Práticas para Produção

Se você está considerando implantar um modelo ao estilo DeepLOB em um sistema de negociação ao vivo, várias preocupações de engenharia surgem:

Latência

O modelo deve produzir previsões dentro do orçamento de latência do seu sistema de negociação. Para sistemas de HFT operando em latências inferiores a milissegundos, mesmo uma inferência PyTorch otimizada pode ser lenta demais. As opções incluem:

  • Exportação ONNX com otimização TensorRT
  • Quantização para INT8
  • Implantação em FPGA para as aplicações mais sensíveis à latência

Pipeline de Dados

O modelo espera instantâneos de livro de ofertas normalizados e alinhados em uma frequência fixa. Na prática, as atualizações do LOB chegam de forma assíncrona. Você precisa de:

  • Um motor de reconstrução de instantâneos que mantenha o estado atual do livro
  • Um amostrador de frequência fixa que produza o tensor de entrada (T,40)(T, 40)
  • Normalização online usando estatísticas móveis

Estabilidade das Características

Os parâmetros de normalização z-score calculados nos dados de treinamento se deslocam ao longo do tempo. Preços mudam, regimes de volatilidade se deslocam, a estrutura de mercado evolui. Um sistema de produção precisa de:

  • Janelas de normalização móveis (por exemplo, recalcular estatísticas diariamente)
  • Detecção de regime para acionar o retreinamento do modelo
  • Monitoramento de deslocamento na distribuição da entrada

Overfitting à Microestrutura

Os padrões do LOB são específicos da plataforma e do instrumento. Um modelo treinado com ações da NASDAQ não funcionará na Binance BTC/USDT sem retreinamento. Mesmo dentro da mesma plataforma, os modelos podem sofrer overfitting a:

  • Regime de tamanho de tick (ações de tick grande vs. tick pequeno)
  • Padrões de hora do dia (leilão de abertura, calmaria do almoço, cruzamento de fechamento)
  • Comportamento de formadores de mercado que muda ao longo do tempo

Conclusão

O DeepLOB representa uma aplicação limpa e bem motivada de aprendizado profundo à microestrutura de mercado. Sua arquitetura de três blocos — CNN para características espaciais, inception para padrões temporais em múltiplas escalas, LSTM para dependências sequenciais — mapeia-se naturalmente na estrutura dos dados de livro de ofertas limitadas.

A ideia central não é apenas que um modelo profundo possa igualar ou superar características criadas manualmente, mas que os vieses indutivos da arquitetura — os filtros 1×21 \times 2, o paralelismo do inception, a memória recorrente — codificam conhecimento genuíno de domínio sobre como os livros de ofertas funcionam. Isso não é uma rede profunda genérica aplicada a dados financeiros; é uma arquitetura projetada em torno da geometria específica do livro de ofertas limitadas. E sua evidência mais convincente não é a classificação do FI-2010 da qual os próprios autores desconfiam, mas o resultado de aprendizado por transferência em um ano de dados da LSE.

Para profissionais, o DeepLOB é uma sólida baseline e um bloco de construção útil. Para pesquisadores, ele demonstra que um design cuidadoso de arquitetura — não apenas a escala do modelo — impulsiona o desempenho em dados financeiros estruturados. Apenas lembre-se de citar os números reais, observar o esquema de rotulagem e tratar a acurácia de classificação como algo muito distante do lucro de negociação.


Referências

  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

Fique à frente do mercado

Assine nossa newsletter para insights exclusivos sobre trading com IA, análises de mercado e atualizações da plataforma.

Respeitamos sua privacidade. Cancele a inscrição a qualquer momento.