← К списку статей
May 26, 2026
5 мин. чтения

DeepLOB: глубокое обучение на книгах лимитных ордеров

DeepLOB: глубокое обучение на книгах лимитных ордеров
#deep-learning
#order-book
#LOB
#microstructure
#HFT
#market-making
📖
Часть 5 из 6 · Подборка
Стакан и микроструктура рынка

Книга лимитных ордеров — центральная структура данных современных электронных рынков. Каждая заявка на покупку, каждая заявка на продажу, каждая отмена — все это живет в LOB. Десятилетиями квантитативные исследователи вручную конструировали признаки из этих данных: коэффициенты дисбаланса ордеров, взвешенные средние цены, сигналы позиции в очереди. DeepLOB пошел другим путем — он учит эти признаки напрямую из сырых снимков книги ордеров с помощью гибридной архитектуры CNN-LSTM.

Опубликованная Чжаном, Зореном и Робертсом в Oxford-Man Institute в 2019 году, DeepLOB показала, что единая глубокая сеть, обученная сквозным образом на сырых данных книги, конкурентоспособна или превосходит сильные классические базовые модели в прогнозировании движения средней цены. Однако нужно быть точными в отношении доказательной базы. Эталонный набор данных FI-2010 — тот, который цитирует большинство статей, но сами авторы называют его недостаточным — "слишком коротким, прореженным и взятым с менее ликвидного рынка" — и представляют вторую, гораздо более масштабную оценку на данных Лондонской фондовой биржи (LSE) как свой более сильный результат. Это исследование на LSE охватывает примерно один год для пяти обучающих и пяти тестовых акций, порядка 100M+ образцов, и демонстрирует перенос обучения: модель, обученная на одном наборе акций, по-прежнему делает прогнозы на невиданных ранее акциях. Именно этот результат переноса, а не таблица лидеров FI-2010, является настоящим заголовком статьи. Этот пост разбирает архитектуру, математику, реальные цифры и рабочую реализацию на PyTorch.

Книга лимитных ордеров как структура данных

Изометрический вид книги лимитных ордеров: уровни объема заявок на покупку и продажу, образующие тензор глубины вокруг средней цены

Книга лимитных ордеров поддерживает два отсортированных списка — заявки на покупку (bids) и заявки на продажу (asks) — каждый организован по ценовым уровням. На каждом уровне книга фиксирует цену и совокупный объем покоящихся ордеров.

Для книги ордеров с LL уровнями отдельный снимок в момент времени tt — это вектор из 4L4L значений:

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

где piap_i^{a} и viav_i^{a} — цена и объем продажи на уровне ii, а pibp_i^{b}, vibv_i^{b} — соответствующие значения на стороне покупки. Уровень 1 содержит лучшую заявку на покупку и лучшую заявку на продажу (BBO); их разница — это спред между покупкой и продажей.

В эталонном наборе данных FI-2010 L=10L = 10, что дает 40 признаков на снимок. FI-2010 построен примерно из 4 миллионов сырых сообщений лимитных ордеров по пяти финским акциям на NASDAQ Nordic (Kesko, Outokumpu, Sampo, Rautaruukki, Wartsila) за десять последовательных торговых дней, 1-14 июня 2010 года. Что важно, DeepLOB не обучается на 4M снимков: эталон прореживает эти сообщения (каждое 10-е событие) в нормализованное представление из 394 337 образцов. Именно эту прореженную, нормализованную форму модель и видит на самом деле — одна из причин, по которой авторы предостерегают от того, чтобы считать FI-2010 окончательным словом.

Средняя цена и ее движения

Средняя цена в момент времени tt — это среднее лучшей заявки на покупку и продажу:

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

DeepLOB прогнозирует направление движения средней цены на будущем горизонте в kk событий. Метка — это сглаженная доходность, но имейте в виду, что в литературе есть два соглашения о сглаживании, и они не взаимозаменяемы:

  • Будущее среднее против текущей цены (Tsantekidis et al.): сравнивает среднюю среднюю цену за следующие kk событий с сырой текущей средней ценой.
  • Будущее среднее против предыдущего среднего (Ntakaris et al., каноническая метка FI-2010, та же, что DeepLOB использовала для своих данных LSE): и опорное значение, и целевое сглажены.
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}

Сглаживание опорного значения (а не только будущего) существенно снижает шум метки от колебаний в один тик, поэтому форма с предыдущим/следующим средним предпочтительнее. Какую бы вы ни использовали, непрерывная доходность дискретизируется на три класса — вверх, вниз, без изменений — с помощью фиксированных порогов ±α\pm\alpha:

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

Набор данных FI-2010 предоставляет метки для пяти горизонтов прогнозирования: k{10,20,30,50,100}k \in \{10, 20, 30, 50, 100\} событий.

Традиционные признаки LOB

До глубокого обучения исследователи полагались на признаки, сконструированные вручную. Понимание их существенно важно, потому что DeepLOB намеренно структурирована так, чтобы напоминать многие из них — ее архитектура мотивирована этими величинами даже там, где она не воспроизводит их в точности.

Дисбаланс книги ордеров (OBI)

Наиболее широко используемый сигнал микроструктуры. На вершине книги:

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

Больший объем покупки относительно объема продажи указывает на давление вверх. Многоуровневый дисбаланс агрегирует по LL уровням:

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

Средняя цена, взвешенная по объему

Стандартная средняя цена взвешивает обе стороны одинаково. Средняя цена, взвешенная по объему (также называемая weighted mid, или VAMP), наклоняется в сторону более тяжелой стороны книги. Записывая дисбаланс вершины книги как I=v1b/(v1b+v1a)I = v_1^{b}/(v_1^{b}+v_1^{a}), это взвешенное по дисбалансу среднее двух цен:

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

Обратите внимание на перекрестное взвешивание: цена продажи взвешивается объемом покупки и наоборот. Когда объем покупки доминирует (I1I \to 1), взвешенная средняя сдвигается в сторону продажи — отражая ожидание того, что несбалансированная книга разрешится движением в сторону более тяжелой стороны.

Предостережение по поводу наименования. Эта статическая, взвешенная по объему средняя — это не микроцена Стойкова. Микроцена Стойкова (2018) — это оценщик с мартингальной поправкой Mt+g(I,S)M_t + g(I, S), полученный из рекурсивной коррекции того, как развиваются спред и дисбаланс — вся его суть в том, что наивная взвешенная средняя смещена и не является мартингалом. Это два различных объекта; мы используем здесь взвешенную среднюю ради ее простоты, а не как замену микроцене.

Дисбаланс глубины

Измеряет форму книги за пределами 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

Спред и относительный спред

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

Эти признаки мощны, но ограничены. Они требуют предметной экспертизы для разработки, не улавливают сложные нелинейные взаимодействия и игнорируют временную динамику между последовательными снимками. DeepLOB устраняет все три ограничения.

Архитектура DeepLOB

Архитектура DeepLOB: сырой тензор книги ордеров проходит через сверточные слои, inception-модуль и LSTM

Архитектура состоит из трех блоков, каждый со своей отдельной ролью:

  1. Сверточный блок — извлекает пространственные признаки из снимка книги ордеров
  2. Inception-модуль — улавливает временные паттерны разных масштабов
  3. LSTM-блок — моделирует последовательные зависимости во времени

Вход сети — это тензор формы (T,L×4)(T, L \times 4), где TT — окно ретроспективы (100 временных шагов в статье), а L×4=40L \times 4 = 40 для 10 уровней цены и объема с обеих сторон.

Блок 1: сверточное извлечение признаков

Первые сверточные слои работают вдоль измерения признаков, чтобы выучить взаимодействия между ценой и объемом на каждом уровне.

Слой 1: свертка с размером фильтра 1×21 \times 2 и шагом (1,2)(1, 2), применяемая к каждой паре цена-объем. Это суммирует каждую пару (pi,vi)(p_i, v_i) в один признак — аналогично вычислению "взаимодействия цена-объем" на каждом уровне. Ширина сжимается с 40 до 20.

Слой 2: свертка 1×21 \times 2 с шагом (1,2)(1, 2), которая работает между сопоставленными парами покупка-продажа на одном уровне. Это улавливает информацию о спреде и дисбалансе на каждом уровне. Ширина сжимается с 20 до 10.

Слой 3: свертка 1×101 \times 10, которая интегрирует по всем 10 уровням, сворачивая измерение глубины до ширины 1. Это производит единый агрегированный признак на временной шаг, который кодирует полный профиль глубины.

За каждым шагом 1×21 \times 2 / 1×101 \times 10 следуют две свертки (4,1)(4, 1) вдоль оси времени, каждая с активацией LeakyReLU и пакетной нормализацией. Обратите внимание, что эти свертки (4,1)(4, 1) с padding=(1, 0) не сохраняют измерение времени: каждая укорачивает его на один шаг (Tout=Tin1T_{\text{out}} = T_{\text{in}} - 1). На протяжении шести таких слоев в блоках 1-3 ось времени сжимается со 100 до 94 перед inception-модулем — что комментарии о формах в коде ниже делают явным.

Дизайн намеренно избегает больших пространственных фильтров. Фильтры 1×21 \times 2 уважают естественную парную структуру данных LOB — цена с объемом, покупка с продажей. Это индуктивное смещение критично: фильтр 3×33 \times 3 смешивал бы соседние уровни и стороны способом, не имеющим финансового смысла.

Блок 2: Inception-модуль

После того как сверточный блок сокращает 40 признаков до компактного пространственного представления (ширина 1), inception-модуль работает вдоль измерения времени, чтобы одновременно улавливать паттерны на множестве временных масштабов.

Inception-модуль DeepLOB имеет четыре параллельных пути, каждый производит 32 канала, все они конкатенируются:

  • 1×11 \times 1 узкое место \to 3×13 \times 1 временная свертка: краткосрочные временные паттерны (3 временных шага)
  • 1×11 \times 1 узкое место \to 5×15 \times 1 временная свертка: среднесрочные временные паттерны (5 временных шагов)
  • 3×13 \times 1 max-pool \to 1×11 \times 1 свертка: пулинг-путь, сохраняющий сглаженный, прореженный взгляд
  • (сокращения 1×11 \times 1 действуют как точечные узкие места перед временными свертками)

Каждый временной путь использует симметричный паддинг, так что измерение времени поддерживается внутри inception-блока (в отличие от сверточных блоков выше). Выходы конкатенируются вдоль измерения каналов, давая сети доступ к временным признакам разных масштабов одновременно.

Это вдохновлено inception-архитектурой GoogLeNet, но адаптировано для одномерных временных данных, а не двумерных изображений. Ключевая идея в том, что динамика LOB действует на множестве временных масштабов: потиковый шум, краткосрочный моментум и возврат к среднему на более длинном горизонте сосуществуют.

Реализация на PyTorch ниже представляет упрощенный вариант с тремя путями (две временные свертки плюс путь 1×11\times1), чтобы код оставался читаемым; она опускает max-pool путь. Мы явно указываем на это, потому что это отклонение от статьи, а не оригинальный модуль.

Блок 3: моделирование последовательностей LSTM

Выход inception-модуля подается в LSTM-слой, который обрабатывает всю последовательность длины TT. LSTM улавливает дальнодействующие временные зависимости, которые свертки сами по себе упустили бы — смены режимов, эффекты времени суток и эволюционирующие рыночные условия.

Финальное скрытое состояние LSTM проходит через полносвязный слой с softmax-активацией, чтобы произвести распределение вероятностей по трем классам (вверх, вниз, без изменений).

Функция потерь

Модель обучается с категориальной перекрестной энтропией:

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

где yi,cy_{i,c} — это истинная метка в one-hot кодировании, а y^i,c\hat{y}_{i,c} — предсказанная вероятность для класса cc.

Реализация на PyTorch

Оригинальная DeepLOB была построена на Keras с бэкендом TensorFlow; репозиторий авторов (zcakhaa) позже добавил порт на PyTorch. Код ниже — это наша собственная иллюстративная реализация на PyTorch, а не официальная. Она отклоняется от статьи двумя способами, которые мы помечаем по ходу: она опускает max-pool путь inception (три пути вместо четырех) и масштабирует количество каналов вверх. В статье используется 16 фильтров на протяжении сверточных блоков и 32 фильтра на путь inception (вход LSTM 3×32=963 \times 32 = 96), что дает компактную модель примерно из 60k параметров. Фрагмент ниже использует 32 сверточных / 64 inception фильтра (вход LSTM 192) — внутренне согласованный и запускаемый, но более тяжелый, чем оригинал. Чтобы соответствовать статье, установите количество сверточных каналов в 16, inception-каналов в 32 и 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

Предобработка данных

Набор данных FI-2010 поставляется нормализованным по z-оценке. Для живых данных книги ордеров вам нужно нормализовать аналогично:

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
        )

Цикл обучения

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

Ключевые гиперпараметры обучения из оригинальной статьи:

  • Оптимизатор: Adam с ϵ=1\epsilon = 1 (необычно большой — помогает стабилизировать обновления на зашумленных финансовых данных)
  • Скорость обучения: 0.01
  • Размер пакета: 32
  • Окно ретроспективы: T=100T = 100 временных шагов

Мы отслеживаем взвешенный F1, а не точность на валидации, потому что три класса несбалансированы — и, как показывает следующий раздел, F1 — это метрика, на которой оригинальная статья делает акцент именно по этой причине.

Что DeepLOB построена улавливать

Сигналы микроструктуры, которые учит DeepLOB: дисбаланс потока ордеров, динамика очереди и давление средней цены, взвешенной по объему

Один привлекательный аспект DeepLOB — насколько тесно ее блоки мотивированы признаками, сконструированными вручную, выше. Здесь мы хотим быть осторожными: статья представляет это как интуиции дизайна, а не как доказанные свойства обученной сети. В ней не публикуются визуализации фильтров, корреляции активаций с дисбалансом или анализ того, на что обращает внимание LSTM. Поэтому читайте следующее как архитектурный замысел, а не как измеренные находки.

Первый сверточный блок. Фильтры 1×21 \times 2 объединяют цену с объемом на каждом уровне. Структурно это та же операция, что формирует взвешенную по объему цену — статья мотивирует первый слой, отмечая, что его карты признаков формируют величину, подобную микроцене. Сеть построена так, чтобы восстановить что-то из этого семейства; мы не утверждаем, что она доказуемо заново открывает какой-либо конкретный оценщик.

Второй сверточный блок. Работая между парами покупка-продажа, эти фильтры расположены так, чтобы учить признаки, подобные дисбалансу, на множестве уровней — опять же, по построению, а не по продемонстрированной корреляции.

Inception-модуль. Временные свертки разных масштабов призваны улавливать моментум на разных частотах: путь 1×11\times1 реагирует на точечную структуру, путь 3×13\times1 — на краткосрочные тренды, путь 5×15\times1 — на немного более длинные, а (в полном модуле) max-pool путь — на сглаженный взгляд.

LSTM-слой. Рекуррентный компонент добавляет память о дальнодействующих зависимостях, которые свертки не могут представить — смены режимов и эффекты времени суток. Мы избегаем более сильных утверждений (таких как то, что LSTM реализует "адаптивное внимание", которое перевзвешивает недавние данные в волатильных режимах); статья этого не показывает.

Производительность на FI-2010

Оригинальная статья оценивает два протокола. Setup 1 использует более раннее разбиение нормализации набора данных; Setup 2 — это deep-learning постановка, с которой сравнивается большая часть последующих работ. Поскольку FI-2010 несбалансирован по классам, статья делает акцент на F1, а не на точности, и приводит цифры на основе precision/recall для ненейронных базовых моделей (SVM, MLP). Вот реальные цифры Setup 2 (F1, %):

Горизонт (k) SVM MLP CNN-I LSTM DeepLOB
10 35.88 48.27 55.21 66.33 83.40
20 72.82
50 80.35

При k=10k = 10 DeepLOB также сообщает 84.47% точности наряду с ее F1 83.40. Setup 2 сообщает только k{10,20,50}k \in \{10, 20, 50\} — здесь нет строки k=100k = 100. (Setup 1 сообщает k{10,50,100}k \in \{10, 50, 100\}, с F1 DeepLOB 77.66 / 74.96 / 76.58 соответственно.) Прочерки выше отмечают ячейки базовых моделей, которые статья не табулирует на этих горизонтах в данном протоколе.

Выделяется несколько наблюдений:

  1. DeepLOB обыгрывает базовые модели с большим отрывом на коротком горизонте. При k=10k = 10 ее F1 (83.40) намного выше LSTM (66.33), CNN-I (55.21), MLP (48.27) и SVM (35.88). Качественный порядок — глубокие рекуррентно-сверточные модели над простыми CNN, MLP и SVM — это устойчивый вывод.
  2. Тренд по горизонту немонотонен, не "длиннее значит легче". В Setup 2 F1 DeepLOB идет 83.4072.8280.3583.40 \to 72.82 \to 80.35, когда kk идет 10205010 \to 20 \to 50 — U-образный паттерн, где k=20k=20 самый трудный, а не чистый рост вверх. Является ли горизонт "более легким", сильно зависит от постановки, порога α\alpha и схемы сглаживания меток; мы не должны вычитывать универсальный закон в этих цифрах.
  3. DeepLOB не доминирует над каждым классическим методом в каждой ячейке. Против сильных базовых моделей, таких как C(TABL), отрывы сужаются на некоторых горизонтах, так что "превосходит все классические подходы везде" преувеличивает дело. Защитимое утверждение в том, что она конкурентоспособна-до-лучшей и явно лучшая на коротком горизонте.

И снова: собственный взгляд статьи в том, что FI-2010 слишком мал и слишком прорежен, чтобы решить вопрос. Ее более сильное доказательство — это исследование на LSE, где DeepLOB обучается на одном наборе акций и по-прежнему делает прогнозы на отложенных акциях — результат переноса обучения, который FI-2010 показать не может.

LOBFrame и кризис воспроизводимости

Расходящиеся кривые внутри и вне выборки, иллюстрирующие кризис воспроизводимости глубокого обучения на книгах ордеров

Эталонный фреймворк LOBFrame (Briola, Bartolucci, Aste, 2024) дает отрезвляющее дополнение к этим результатам. Хотя архитектура DeepLOB обоснована, фреймворк подчеркивает несколько важных оговорок:

Зависимость от микроструктуры. Производительность модели значительно варьируется по акциям с разными микроструктурными характеристиками. Ликвидные акции с большим тиком легче прогнозировать, чем неликвидные инструменты с малым тиком. Модель, которая выглядит сильной на одной акции, может существенно деградировать на другой.

Прогноз против прибыли. Высокая точность классификации не транслируется автоматически в торговую прибыль. LOBFrame подчеркивает метрики, привязанные к тому, достаточно ли велико прогнозируемое движение, чтобы перекрыть спред между покупкой и продажей. Модель должна быть права достаточно часто и на достаточно крупных движениях, чтобы преодолеть спред — а для акций с узким спредом эта планка высока.

Чувствительность к меткам. Выбор порога α\alpha и горизонта kk драматически влияет как на сообщаемую точность, так и на практическую ценность прогнозов. Слишком узкие метки производят зашумленные цели; слишком широкие метки производят тривиально "точные", но бесполезные прогнозы.

За пределами DeepLOB: текущий ландшафт

Созвездие современных подходов к прогнозированию книги ордеров, вращающееся вокруг оригинальной модели DeepLOB

С момента публикации DeepLOB появилось несколько расширений:

Трансформеры для книг лимитных ордеров (Wallbridge, 2020). Часто вольно называемая "TransLOB" в более поздней литературе, сама модель — это причинно-следственный/дилатированный сверточный экстрактор признаков, за которым следует маскированное многоголовое self-attention — а не просто DeepLOB с заменой LSTM на трансформер; она не переиспользует стек inception+conv. Она сообщила о новом состоянии искусства на FI-2010 для своего времени.

Варианты в крипто-домене. Несколько групп применяют архитектуры в стиле DeepLOB к рынкам криптовалют, где динамика книги ордеров существенно отличается от акций — более широкие спреды, торговля 24/7 и иной состав участников, что все вместе требует переобучения.

Варианты с дополненным вниманием. Линия работ вставляет многоголовое внимание между блоками inception и LSTM, позволяя сети сосредоточиться на уровнях или временных шагах, наиболее информативных для текущего прогноза.

LOB-Bench (Nagy, Frey, Li, Sarkar, Vyetrenko, Zohren, Calinescu, Foerster, 2025). Этот эталон переключает внимание с прогнозирования на генерацию, оценивая, насколько реалистичны генеративные модели данных книги ордеров — релевантно для бэктестинга и обучения агентов, а не для направленного прогнозирования.

Практические соображения для продакшена

Если вы рассматриваете развертывание модели в стиле DeepLOB в живой торговой системе, возникает несколько инженерных вопросов:

Задержка

Модель должна производить прогнозы в рамках бюджета задержки вашей торговой системы. Для HFT-систем, работающих с субмиллисекундными задержками, даже оптимизированный вывод PyTorch может быть слишком медленным. Варианты включают:

  • Экспорт в ONNX с оптимизацией TensorRT
  • Квантизацию до INT8
  • Развертывание на FPGA для наиболее чувствительных к задержке приложений

Конвейер данных

Модель ожидает нормализованные, выровненные снимки книги ордеров на фиксированной частоте. На практике обновления LOB приходят асинхронно. Вам нужны:

  • Движок реконструкции снимков, который поддерживает текущее состояние книги
  • Сэмплер с фиксированной частотой, который производит входной тензор (T,40)(T, 40)
  • Онлайн-нормализация с использованием скользящей статистики

Стабильность признаков

Параметры нормализации по z-оценке, вычисленные на обучающих данных, дрейфуют со временем. Цены меняются, режимы волатильности сдвигаются, структура рынка эволюционирует. Продакшен-системе нужны:

  • Скользящие окна нормализации (например, пересчитывать статистику ежедневно)
  • Обнаружение режимов для запуска переобучения модели
  • Мониторинг сдвига распределения входа

Переобучение под микроструктуру

Паттерны LOB специфичны для площадки и инструмента. Модель, обученная на акциях NASDAQ, не будет работать на Binance BTC/USDT без переобучения. Даже в рамках одной площадки модели могут переобучаться под:

  • Режим размера тика (акции с большим тиком против малого тика)
  • Паттерны времени суток (открывающий аукцион, обеденное затишье, закрывающий кросс)
  • Поведение маркет-мейкера, которое меняется со временем

Заключение

DeepLOB представляет собой чистое, хорошо мотивированное применение глубокого обучения к микроструктуре рынка. Ее трехблочная архитектура — CNN для пространственных признаков, inception для временных паттернов разных масштабов, LSTM для последовательных зависимостей — естественно отображается на структуру данных книги лимитных ордеров.

Ключевая идея не просто в том, что глубокая модель может сравняться с признаками, сконструированными вручную, или обыграть их, а в том, что индуктивные смещения архитектуры — фильтры 1×21 \times 2, параллелизм inception, рекуррентная память — кодируют подлинное предметное знание о том, как работают книги ордеров. Это не дженерик-сеть, наброшенная на финансовые данные; это архитектура, спроектированная вокруг специфической геометрии книги лимитных ордеров. И ее наиболее убедительное доказательство — это не таблица лидеров FI-2010, которой сами авторы не доверяют, а результат переноса обучения на годе данных LSE.

Для практиков DeepLOB — сильная базовая модель и полезный строительный блок. Для исследователей она демонстрирует, что продуманный дизайн архитектуры — а не просто масштаб модели — движет производительностью на структурированных финансовых данных. Только не забывайте цитировать реальные цифры, следить за схемой меток и относиться к точности классификации как к тому, что очень далеко от торговой прибыли.


Ссылки

  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

Дисклеймер: Информация в этой статье предоставлена исключительно в образовательных и ознакомительных целях и не является финансовым, инвестиционным или торговым советом. Торговля криптовалютами сопряжена с высоким риском убытков.

Авторы

Eugen Soloviov
Eugen Soloviov

Инженер торговых систем

Разработка торговых ботов с 2017 года: межбиржевой арбитраж (подключал до 30 бирж), парный арбитраж на коинтеграции между спотом и фьючерсами, скальпинг, фронтраннинг, торговля по новостям, сентиментный анализ, трендовые алгоритмы, а также алгоритмы управления и балансировки портфелей. Делает выставление ордеров до 1 мс, warehouse для big data, бэктестинг-движки, AI-агентов и интерфейсы для ботов (в т.ч. open-source profitmaker.cc). Стек: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, архитектура.

Newsletter

Будьте в курсе событий

Подпишитесь на нашу рассылку, чтобы получать эксклюзивную аналитику по AI-трейдингу и обновления платформы.

Мы уважаем вашу конфиденциальность. Отписаться можно в любой момент.