← 記事一覧に戻る
May 26, 2026
読了時間: 5分

DeepLOB: 板情報に対するディープラーニング

DeepLOB: 板情報に対するディープラーニング
#deep-learning
#order-book
#LOB
#microstructure
#HFT
#market-making
📖
Part 5 of 6 · Collection
Order Book & Market Microstructure

板情報(リミットオーダーブック、LOB)は、現代の電子市場における中心的なデータ構造です。すべての買い注文、すべての売り注文、すべてのキャンセル — それらはすべて LOB の中に存在します。何十年にもわたり、クオンツの研究者たちはこのデータから特徴量を手作業で設計してきました。オーダーインバランス比率、加重仲値、キュー位置のシグナルなどです。DeepLOB は異なる道筋をとりました — ハイブリッドな CNN-LSTM アーキテクチャを用いて、生の板スナップショットから直接これらの特徴量を学習するのです。

DeepLOB は 2019 年に Oxford-Man Institute の Zhang、Zohren、Roberts によって発表され、生の板データに対してエンドツーエンドで学習させた単一のディープネットワークが、仲値の動きの予測において強力な古典的ベースラインと同等かそれ以上であることを示しました。ただし、その根拠については正確であるべきです。ベンチマークとなる FI-2010 データセットは、ほとんどの論文が引用するものですが、著者たち自身がそれを不十分だと述べています — 「短すぎ、ダウンサンプリングされており、流動性の低い市場から取得されたもの」だと — そして、より強力な結果として、ロンドン証券取引所(LSE)のデータに対する 2 つ目の、はるかに大規模な評価を提示しています。その LSE 研究は、学習用 5 銘柄とテスト用 5 銘柄でおよそ 1 年分にわたり、1 億サンプル以上のオーダーで、転移学習を実証しています。ある銘柄群で学習したモデルが、未知の銘柄に対しても予測できるのです。FI-2010 のリーダーボードではなく、この転移の結果こそが、論文の本当の目玉なのです。本記事では、アーキテクチャ、数式、実際の数値、そして動作する PyTorch 再実装を分解して解説します。

データ構造としての板情報

板情報のアイソメトリック表示: 仲値の周囲に深さテンソルを形成する、積み重なった買いと売りの出来高レベル

板情報は 2 つのソートされたリスト — 買い注文(bid)と売り注文(ask) — を保持し、それぞれが価格レベルごとに整理されています。各レベルにおいて、板はその価格と、待機している注文の合計出来高を記録します。

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 は、NASDAQ Nordic に上場するフィンランドの 5 銘柄(Kesko、Outokumpu、Sampo、Rautaruukki、Wartsila)から、2010 年 6 月 1 日から 14 日までの連続する 10 取引日にわたって取得された、約 400 万件の生の指値注文メッセージから構築されています。重要なのは、DeepLOB が 400 万件のスナップショットで学習するわけではないということです。このベンチマークは、それらのメッセージを(10 イベントごとに)ダウンサンプリングして、394,337 サンプルの正規化された表現にしています。このダウンサンプリングされ正規化された形こそが、モデルが実際に目にするものであり、著者たちが FI-2010 を最終的な結論として扱わないよう警告する理由の 1 つです。

仲値とその動き

時刻 tt における仲値は、最良買い気配と最良売り気配の平均です:

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

DeepLOB は、将来 kk イベント先のホライズンにわたる仲値の動きの方向を予測します。ラベルは平滑化されたリターンですが、文献には 2 つの平滑化の慣行があり、それらは互換的ではないことに注意してください:

  • 将来平均 vs 現在価格(Tsantekidis ら): 次の kk イベントにわたる仲値の平均を、生の現在の仲値と比較する。
  • 将来平均 vs 過去平均(Ntakaris ら、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 を用いて 3 つのクラス — 上昇下降横ばい — に離散化されます:

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

FI-2010 データセットは、5 つの予測ホライズン 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}) と書くと、これは 2 つの価格のインバランス加重平均です:

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)、加重仲値は売り側へとシフトします — これは、インバランスのある板はより重い側へ動いて解消されるという予想を反映しています。

命名についての注意です。この静的な出来高加重仲値は、Stoikov のマイクロプライス(micro-price)ではありません。Stoikov のマイクロプライス(2018)は、スプレッドとインバランスがどのように推移するかについての再帰的な補正から得られる、マルチンゲール調整された推定量 Mt+g(I,S)M_t + g(I, S) です — 彼の主眼は、素朴な加重仲値はバイアスを持ちマルチンゲールではないという点にあります。これらは 2 つの異なる対象です。ここでは加重仲値をその単純さのために用いており、マイクロプライスの代用として用いているのではありません。

深さインバランス

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 はこれら 3 つの限界すべてに対処します。

DeepLOB アーキテクチャ

DeepLOB アーキテクチャ: 生の板テンソルが畳み込み層、Inception モジュール、LSTM を通って流れる様子

このアーキテクチャは、それぞれ異なる役割を持つ 3 つのブロックを持ちます:

  1. 畳み込みブロック — 板スナップショットから空間的特徴量を抽出する
  2. Inception モジュール — マルチスケールの時間的パターンを捉える
  3. LSTM ブロック — 時間をまたぐ系列的な依存関係をモデル化する

ネットワークへの入力は、形状 (T,L×4)(T, L \times 4) のテンソルです。ここで TT はルックバックウィンドウ(論文では 100 タイムステップ)であり、両側 10 レベルの価格と出来高に対して L×4=40L \times 4 = 40 です。

ブロック 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 のステップの後には、時間軸に沿った 2 つの (4,1)(4, 1) 畳み込みが続き、それぞれが LeakyReLU 活性化とバッチ正規化を伴います。これらの padding=(1, 0) を持つ (4,1)(4, 1) 畳み込みは、時間次元を保存しません。各畳み込みが時間を 1 ステップ短くします(Tout=Tin1T_{\text{out}} = T_{\text{in}} - 1)。ブロック 1〜3 における 6 つのそのような層を通して、時間軸は Inception モジュールの前に 100 から 94 に縮小します — これは以下のコードの形状コメントが明示している通りです。

この設計は、大きな空間的フィルタを意図的に避けています。1×21 \times 2 のフィルタは、LOB データの自然なペア構造 — 価格と出来高、買いと売り — を尊重します。この帰納的バイアスは極めて重要です。3×33 \times 3 のフィルタは、金融的な意味を持たない形で隣接するレベルや側を混ぜてしまいます。

ブロック 2: Inception モジュール

畳み込みブロックが 40 個の特徴量をコンパクトな空間表現(幅 1)に縮小した後、Inception モジュールが時間次元に沿って動作し、複数の時間スケールのパターンを同時に捉えます。

DeepLOB の Inception モジュールは 4 つの並列パスを持ち、それぞれが 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 マックスプーリング \to 1×11 \times 1 畳み込み: 平滑化されダウンサンプリングされたビューを保持するプーリングパス
  • 1×11 \times 1 の削減は、時間畳み込みの前の、各時点ごとのボトルネックとして機能します)

各時間パスは対称パディングを用いるため、Inception ブロック内では時間次元が維持されます(上記の畳み込みブロックとは異なります)。出力はチャネル次元に沿って連結され、ネットワークはマルチスケールの時間的特徴量に一度にアクセスできます。

これは GoogLeNet の Inception アーキテクチャに着想を得ていますが、2D の画像ではなく 1D の時系列データに適応させたものです。重要な洞察は、LOB のダイナミクスが複数の時間スケールで動作しているという点です。ティックごとのノイズ、短期のモメンタム、そしてより長いホライズンの平均回帰がすべて共存しています。

以下の PyTorch 再実装は、コードを読みやすく保つために、簡略化した 3 パスのバリアント(2 つの時間畳み込みに 1×11\times1 パスを加えたもの)を提示しています。マックスプーリングパスは省略しています。これは論文からの逸脱であり、元のモジュールではないため、明示的に指摘しておきます。

ブロック 3: LSTM による系列モデリング

Inception モジュールの出力は、TT 長の系列全体を処理する LSTM 層に入力されます。LSTM は、畳み込みだけでは見逃してしまう長距離の時間的依存関係 — レジームの変化、時刻による影響、変化する市場状況 — を捉えます。

LSTM の最終的な隠れ状態は、ソフトマックス活性化を伴う全結合層を通され、3 つのクラス(上昇、下降、横ばい)にわたる確率分布を生成します。

損失関数

モデルはカテゴリカル交差エントロピーで学習されます:

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 は TensorFlow バックエンド上の Keras で構築されていました。著者たちのリポジトリ(zcakhaa)には、後に PyTorch 移植版が追加されました。以下のコードは私たち独自の説明用 PyTorch 再実装であり、公式のものではありません。インラインで指摘する 2 つの点で論文から逸脱しています。Inception のマックスプーリングパスを省略していること(4 つではなく 3 つのパス)、そしてチャネル数をスケールアップしていることです。論文では畳み込みブロック全体で 16 フィルタInception パスあたり 32 フィルタ(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-score 正規化された状態で提供されます。ライブの板データに対しては、同様に正規化する必要があります:

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

オリジナル論文からの主要な学習ハイパーパラメータ:

  • オプティマイザ: ϵ=1\epsilon = 1 の Adam(異例なほど大きい — ノイズの多い金融データに対する更新を安定させるのに役立つ)
  • 学習率: 0.01
  • バッチサイズ: 32
  • ルックバックウィンドウ: T=100T = 100 タイムステップ

検証では精度(accuracy)ではなく加重 F1 を追跡します。3 つのクラスが不均衡であるためです — そして次のセクションが示すように、F1 はまさにその理由からオリジナル論文が強調する指標です。

DeepLOB が捉えるように作られているもの

DeepLOB が学習するマイクロストラクチャーのシグナル: オーダーフロー・インバランス、キューのダイナミクス、出来高加重仲値の圧力

DeepLOB の魅力的な側面の 1 つは、そのブロックが上記の手作業で設計された特徴量にどれほど密接に動機づけられているかにあります。ここでは慎重でありたいと思います。論文はこれらを設計上の直観として提示しているのであって、学習済みネットワークの証明された性質としてではありません。論文はフィルタの可視化も、活性化とインバランスの相関も、LSTM が何に注目しているかの分析も公開していません。したがって、以下はアーキテクチャの意図として読んでください。測定された知見としてではありません。

最初の畳み込みブロック。 1×21 \times 2 のフィルタは、各レベルにおいて価格と出来高をペアにします。構造的には、これは出来高加重価格を形成するのと同じ操作です — 論文は、その特徴マップがマイクロプライスのような量を形成することに着目して、最初の層を動機づけています。ネットワークはその系統の何かを回復するように作られています。特定の推定量を証明可能に再発見すると主張するものではありません。

2 番目の畳み込みブロック。 買い・売りペアにわたって動作することで、これらのフィルタは複数のレベルでインバランスのような特徴量を学習する位置にあります — ここでも、実証された相関によってではなく、構成によってそうなっています。

Inception モジュール。 マルチスケールの時間畳み込みは、異なる周波数でのモメンタムを捉えることを意図しています。1×11\times1 パスは各時点の構造に、3×13\times1 パスは短期のトレンドに、5×15\times1 パスはやや長いトレンドに、そして(完全なモジュールでは)マックスプーリングパスは平滑化されたビューに応答します。

LSTM 層。 再帰的なコンポーネントは、畳み込みが表現できない長距離の依存関係 — レジームのシフトや時刻による影響 — の記憶を加えます。より強い主張(変動の大きいレジームにおいて最近のデータを再重み付けする「適応的アテンション」を LSTM が実装している、といった主張)は避けます。論文はこれを示していません。

FI-2010 における性能

オリジナル論文は 2 つのプロトコルを評価しています。セットアップ 1 はデータセットの初期の正規化分割を使用し、セットアップ 2 はその後の研究の多くが比較対象とするディープラーニングのセットアップです。FI-2010 はクラス不均衡であるため、論文は精度(accuracy)ではなく F1 を強調し、非ニューラルのベースライン(SVM、MLP)については適合率/再現率ベースの数値を報告しています。以下は実際のセットアップ 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 は 83.40 の F1 とともに 84.47% の精度も報告しています。セットアップ 2 が報告するのは k{10,20,50}k \in \{10, 20, 50\} のみです — ここには k=100k = 100 の行はありません。(セットアップ 1 は k{10,50,100}k \in \{10, 50, 100\} を報告しており、DeepLOB の F1 はそれぞれ 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. ホライズンの傾向は非単調であり、「長いほど簡単」ではありません。 セットアップ 2 全体で、DeepLOB の F1 は kk10205010 \to 20 \to 50 と進むにつれて 83.4072.8280.3583.40 \to 72.82 \to 80.35 となります — U 字型のパターンであり、k=20k=20 が最も難しく、きれいな右肩上がりではありません。あるホライズンが「より簡単」かどうかは、セットアップ、しきい値 α\alpha、ラベル平滑化の方式に大きく依存します。これらの数値に普遍的な法則を読み取るべきではありません。
  3. DeepLOB はすべてのセルですべての古典的手法を支配するわけではありません。 C(TABL) のような強力なベースラインに対しては、いくつかのホライズンで差が縮まるため、「あらゆる場面ですべての古典的アプローチを上回る」という言い方は誇張です。擁護できる主張は、それが競争力があり最良に近く、短いホライズンでは明らかに最良である、ということです。

そして繰り返しますが、論文自身の見解は、FI-2010 はこの問題を決着させるには小さすぎ、ダウンサンプリングされすぎているというものです。より強力な証拠は LSE 研究であり、そこでは DeepLOB がある銘柄群で学習し、それでもホールドアウトされた銘柄に対して予測します — FI-2010 では示せない転移学習の結果です。

LOBFrame と再現性の危機

板情報のディープラーニングにおける再現性の危機を示す、乖離するインサンプルとアウトオブサンプルの曲線

LOBFrame ベンチマークフレームワーク(Briola、Bartolucci、Aste、2024)は、これらの結果に対して冷静な補完を提供します。DeepLOB のアーキテクチャは健全である一方で、このフレームワークはいくつかの重要な注意点を浮き彫りにしています:

マイクロストラクチャーへの依存。 モデルの性能は、異なるマイクロストラクチャー特性を持つ銘柄間で大きく変動します。流動性が高く大きいティックの銘柄は、流動性が低く小さいティックの銘柄よりも予測が容易です。ある銘柄で強そうに見えるモデルが、別の銘柄では大幅に劣化することがあります。

予測 vs 利益。 高い分類精度は、自動的に取引の利益につながるわけではありません。LOBFrame は、予測された動きがビッド・アスク・スプレッドを超えるのに十分な大きさかどうかに結びついた指標を強調します。モデルは、スプレッドを克服するために、十分な頻度で、かつ十分に大きな動きについて正解する必要があります — そしてスプレッドの狭い銘柄に対しては、そのハードルは高いのです。

ラベルの感度。 しきい値 α\alpha とホライズン kk の選択は、報告される精度と予測の実用的価値の両方に劇的に影響します。狭すぎるラベルはノイズの多いターゲットを生み、広すぎるラベルは自明に「正確」だが無用な予測を生みます。

DeepLOB を超えて: 現在の状況

オリジナルの DeepLOB モデルの周りを回る、現代の板情報予測アプローチの星座

DeepLOB の発表以来、いくつかの拡張が登場しています:

板情報のための Transformer(Wallbridge、2020)。後の文献ではしばしば緩く「TransLOB」と呼ばれますが、このモデル自体は、因果的/ダイレーテッドな畳み込み特徴抽出器に続いてマスク付きマルチヘッド自己アテンションを持つものです — 単に DeepLOB の LSTM を Transformer に置き換えたものではなく、Inception+畳み込みのスタックを再利用していません。発表当時の FI-2010 の新たな state of the art を報告しました。

暗号資産ドメインのバリアント。 いくつかのグループが DeepLOB スタイルのアーキテクチャを暗号資産市場に適用しています。そこでは板のダイナミクスが株式とは大きく異なります — より広いスプレッド、24 時間 365 日の取引、そして再学習を必要とする異なる参加者構成です。

アテンション拡張バリアント。 Inception ブロックと LSTM ブロックの間にマルチヘッドアテンションを挿入する一連の研究があり、ネットワークが現在の予測にとって最も情報量の多いレベルやタイムステップに注目できるようにします。

LOB-Bench(Nagy、Frey、Li、Sarkar、Vyetrenko、Zohren、Calinescu、Foerster、2025)。このベンチマークは、注目を予測から生成へと転じ、板データの生成モデルがどれほど現実的かを採点します — 方向性の予測ではなく、バックテストやエージェントの学習に関連します。

本番運用のための実践的考慮事項

DeepLOB スタイルのモデルをライブの取引システムにデプロイすることを検討している場合、いくつかのエンジニアリング上の懸念が生じます:

レイテンシ

モデルは、取引システムのレイテンシ予算の範囲内で予測を生成しなければなりません。ミリ秒未満のレイテンシで動作する HFT システムでは、最適化された PyTorch の推論でさえ遅すぎることがあります。選択肢には以下があります:

  • TensorRT 最適化を伴う ONNX エクスポート
  • INT8 への量子化
  • 最もレイテンシに敏感なアプリケーションのための FPGA デプロイ

データパイプライン

モデルは、固定周波数で正規化され整列された板スナップショットを期待します。実際には、LOB の更新は非同期に到着します。必要なものは以下です:

  • 現在の板の状態を維持するスナップショット再構成エンジン
  • (T,40)(T, 40) の入力テンソルを生成する固定周波数のサンプラー
  • ローリング統計量を用いたオンライン正規化

特徴量の安定性

学習データで計算された z-score 正規化のパラメータは、時間とともにドリフトします。価格は変化し、ボラティリティのレジームはシフトし、市場構造は進化します。本番システムには以下が必要です:

  • ローリング正規化ウィンドウ(例: 統計量を日次で再計算する)
  • モデルの再学習をトリガーするためのレジーム検出
  • 入力分布のシフトに対する監視

マイクロストラクチャーへの過学習

LOB のパターンは取引所固有であり、銘柄固有です。NASDAQ の株式で学習したモデルは、再学習なしには Binance の BTC/USDT では機能しません。同じ取引所内であっても、モデルは以下に過学習することがあります:

  • ティックサイズのレジーム(大きいティック vs 小さいティックの銘柄)
  • 時刻のパターン(寄り付きオークション、昼の閑散、引けのクロス)
  • 時間とともに変化するマーケットメーカーの行動

結論

DeepLOB は、市場マイクロストラクチャーへのディープラーニングの、明快でよく動機づけられた応用を表しています。その 3 ブロックのアーキテクチャ — 空間的特徴のための CNN、マルチスケールの時間的パターンのための Inception、系列的依存関係のための LSTM — は、板情報の構造に自然に対応しています。

重要な洞察は、単にディープモデルが手作業の特徴量に匹敵あるいは凌駕できるということだけではなく、アーキテクチャの帰納的バイアス — 1×21 \times 2 のフィルタ、Inception の並列性、再帰的な記憶 — が、板情報がどのように機能するかについての真のドメイン知識をエンコードしているということです。これは金融データに投げつけられた汎用的なディープネットワークではありません。板情報の特定の幾何学を中心に設計されたアーキテクチャなのです。そしてその最も説得力のある証拠は、著者たち自身が信頼していない FI-2010 のリーダーボードではなく、1 年分の 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

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

市場の先を行く

ニュースレターを購読して、独占的なAI取引の洞察、市場分析、プラットフォームの更新情報を受け取りましょう。

プライバシーを尊重します。いつでも配信停止可能です。