📝

Draft article

This draft is visible to admins and superusers only. Sign in with an authorized account.

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

Irregular Time in Tick Models: Continuous-Time Encodings vs. Plain Positional Embeddings

Irregular Time in Tick Models: Continuous-Time Encodings vs. Plain Positional Embeddings
#HFT
#tick-data
#deep-learning
#high-frequency
#prediction

時間足バーはイベントのタイミングを壊してしまう恣意的な圧縮であり、バーの境界は活動度で定義すべきだ——17種類のバーと実用的な生成器を含むこの議論は、アルゴリズム取引のバー種別と集計方法で詳しく扱った。本稿はその一歩先、誰も解決していない部分から始まる。ティック、出来高、またはインバランスバーに切り替えた後でさえ、入力するモデルは依然として等間隔を仮定している。学習可能な位置埋め込みを持つ nn.TransformerEncoder は、6番目の位置から200マイクロ秒後に届いたティックでも40秒後に届いたティックでも、位置7を「7番目のスロット」として扱う。アクティビティバーが保持するために作られた到着間隔時間は、入力層で再び捨てられてしまう。

そこで本稿の問いは狭く、検証可能だ:ティックが実際にいつ発生したかを系列モデルにどう伝えるべきか、そして高度な方法は単純な方法を上回るのか?

前提知識

不規則なイベントパルス

以下はすべて前提となる内容で、本ブログですでに扱っているため、ここでは再導出しない。

  • マイクロストラクチャーノイズとビッド・アスク・バウンス。 Roll の暗黙的スプレッドモデルは、ティック収益の負の系列共分散を価格単位で第一原理から導出し、よくある実装ミスも指摘する:機械学習によるビッド・アスク・スプレッドモデル
  • 板の特徴量。 板のインバランス、加重ミッド価格、レベル1-5の深さ比率、板の圧力、スプレッド/ティック比率は同じ記事で表にまとめている。OBIと出来高加重ミッドの導出(加重ミッドは Stoikov のマイクロプライスではない という注意を含む)は、DeepLOB:指値注文板の深層学習にある。
  • 注文フローと取引強度。 取引インバランス、VPIN、Kyle のラムダはスプレッドモデルの記事にある。注文間隔と Hawkes の自己励起強度をタイミング特徴量にする方法は、デジタル・フィンガープリント:トレーダー識別で扱う。 本稿が当初提案していた単純な 1/dt 強度は、そこでの Hawkes 定式化より弱い版である。
  • 日中の非定常性。 U字型の出来高パターン、静かな時間帯に広がるスプレッド、時刻の sin/cos エンコーディングは、市場状態の特徴量としてスプレッドモデルで扱っている。
  • ラベル。 2つの平滑化規約(将来平均対現在価格、将来平均対過去平均)、閾値 α\alpha を使う3クラス離散化、そして α\alphakk に対するラベル感度についてのLOBFrameの警告は、DeepLOB記事にある。
  • クラス不均衡。 デッドゾーンを置くとFLATクラスが支配的になるため、報告すべき指標は正解率ではなくF1になる——同じ記事の内容である。
  • バーの充填時間。 出来高バーが満たされるまでの実時間はティックモデルに使える特徴量であり、それを生成する器はアルゴリズム取引のバー種別と集計方法にある。

明示しておくべきことが一つある。旧版の本稿はここを間違えていた。方向正解率が50.0%から50.5%になっただけでは、利益が出るとは自明でない。DeepLOB記事で論じたLOBFrameの核心は、予測された値動きがスプレッドを超えるほど大きくなければ正解率には意味がないという点であり、このブログの誠実な負の結果は、そうでないと仮定した場合に起こることを示す。

ベースライン

ベースラインの系列イベントエンコーディング

ベースラインのエンコーダーにはDeepLOB型のCNN-Inception-LSTMを使う。アーキテクチャ、検証済みFI-2010 Setup 2の数値(k=10k=10でF1 83.40 / 正解率84.47)、LOBFrameの注意点、実行可能なPyTorch再実装はすべてDeepLOB:指値注文板の深層学習にある。以下でエンコーダー自体は変更しない——問うのは入力表現に何を追加するかだけである。

不規則な時間をエンコードする3つの方法

不規則な時間の代替エンコーディング

選択肢A:単純な特徴量としてのdelta_t

最も単純な答えである。Δti=titi1\Delta t_i = t_i - t_{i-1}(対数変換してzスコア化)を、OFIや板のインバランスと並ぶ特徴ベクトルのもう1列として追加し、標準の学習済み位置埋め込みはそのままにする。コストはなく入力次元が1つ増えるだけで、実際の本番ティックモデルの多くが採用している方法だ。

これは、より凝った提案が超えなければならない基準であり、より凝った代替案を提案する論文から省略されがちな基準でもある。

選択肢B:連続時間の位置エンコーディング

離散的な位置埋め込みを、学習可能な時間スケール上で経過時間の正弦関数に置き換える。

TE(Δt)=[sin(Δtτ1),cos(Δtτ1),,sin(Δtτd),cos(Δtτd)]\text{TE}(\Delta t) = \left[\sin\left(\frac{\Delta t}{\tau_1}\right), \cos\left(\frac{\Delta t}{\tau_1}\right), \ldots, \sin\left(\frac{\Delta t}{\tau_d}\right), \cos\left(\frac{\Delta t}{\tau_d}\right)\right]

ここで τ1,,τd\tau_1, \ldots, \tau_d は学習可能な時間スケールパラメータであり、マイクロ秒から秒まで対数間隔で初期化する。意図する効果は、アテンションがスロット番号ではなく時間的な関連性に基づいてイベントを重み付けできることだ——200マイクロ秒前の大きな取引には、50ミリ秒前の小さな取引とは異なる形で到達できるはずである。

興味深いのは学習可能な部分だ。時間スケールを1マイクロ秒から10秒まで対数間隔で初期化して学習するなら、時間スケールが最終的にどこへ移動するか自体が測定値になる。ミリ秒側へ収束するなら、モデルは数ミリ秒を超えるすべてが交換可能だと示している。それはアーキテクチャではなく、市場についての発見である。

import numpy as np
import torch
import torch.nn as nn

class ContinuousTimeEncoding(nn.Module):
    """Sinusoidal encoding for irregular inter-arrival times."""
    def __init__(self, d_model: int, num_timescales: int = 64):
        super().__init__()
        log_timescales = torch.linspace(
            np.log(1e-6), np.log(10.0), num_timescales
        )
        self.log_timescales = nn.Parameter(log_timescales)
        self.proj = nn.Linear(num_timescales * 2, d_model)

    def forward(self, delta_t: torch.Tensor) -> torch.Tensor:
        """
        Args:
            delta_t: (batch, seq_len) inter-arrival times in seconds
        Returns:
            (batch, seq_len, d_model) time encoding
        """
        timescales = torch.exp(self.log_timescales)  # (num_timescales,)
        scaled = delta_t.unsqueeze(-1) / timescales.unsqueeze(0).unsqueeze(0)
        encoding = torch.cat([torch.sin(scaled), torch.cos(scaled)], dim=-1)
        return self.proj(encoding)

標準エンコーダーに組み込む場合、置き換わるのは1行だけ、つまり位置埋め込みの加算である。

class TickTransformer(nn.Module):
    def __init__(self, input_dim: int, d_model: int = 128,
                 nhead: int = 8, num_layers: int = 4, num_classes: int = 3):
        super().__init__()
        self.feature_proj = nn.Linear(input_dim, d_model)
        self.time_encoding = ContinuousTimeEncoding(d_model)
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=d_model, nhead=nhead,
            dim_feedforward=d_model * 4,
            dropout=0.1, batch_first=True
        )
        self.encoder = nn.TransformerEncoder(
            encoder_layer, num_layers=num_layers
        )
        self.head = nn.Linear(d_model, num_classes)

    def forward(self, features: torch.Tensor,
                delta_t: torch.Tensor) -> torch.Tensor:
        h = self.feature_proj(features) + self.time_encoding(delta_t)
        h = self.encoder(h)
        return self.head(h[:, -1, :])

エンコーダーの定型部分自体は新しいものではない。同じ nn.TransformerEncoderLayer の骨格はスプレッドモデルのArchitecture 2としてすでに公開されている。新しいのは ContinuousTimeEncoding と学習可能な時間スケールという考え方だけだ。

選択肢C:ティック間の連続潜在状態(ODE-RNN)

最も原理に忠実な選択肢は、潜在状態をNeural ODEでモデル化した連続時間過程として扱う(Chen et al., 2018)。ティック間では、隠れ状態が学習済みの微分方程式に従って発展する。

dhdt=fθ(h(t),t)\frac{d\mathbf{h}}{dt} = f_\theta(\mathbf{h}(t), t)

ティックが到着すると、観測値で状態を更新する。

h(ti+)=Update(h(ti),xi)\mathbf{h}(t_i^+) = \text{Update}(\mathbf{h}(t_i^-), \mathbf{x}_i)

これがODE-RNNの枠組みである。入力特徴量として扱うのではなく構造として不規則な間隔を処理する。ソルバーはイベント間の正確な Δti\Delta t_i だけ積分するため、パディングも補間も、情報を失わせる再サンプリング手順もない。

代償は計算量だ。ODEソルバーは逐次的で並列化が難しく、レイテンシ予算に最も残りにくい選択肢になる——主張をする前にどう測るべきかは、IPC税バックテストエンジン速度ラダーで示したレイテンシ規律を参照されたい。ここに含めるのは、明示的な時間モデルが得られるものの上限を示すためであり、デプロイ候補としてではない。

イベント加重アテンション

イベント加重アテンション場

もう一つ、直交する考え方がある。すべてのティックが同じ情報量を持つわけではない。ビッドでの100株の取引は日常的だ。3つの価格レベルを掃く取引は状態変化である。この事前知識をアテンションへ直接注入できる。イベント重要度スコアを定義する。

wi=softplus(α1log(qi)+α2Δpi/σ+α31[sweepi])w_i = \text{softplus}\left(\alpha_1 \cdot \log(q_i) + \alpha_2 \cdot |\Delta p_i| / \sigma + \alpha_3 \cdot \mathbb{1}[\text{sweep}_i]\right)

ここで qiq_i は取引サイズ、Δpi\Delta p_i は価格変化、σ\sigma は直近のボラティリティ、sweepi\text{sweep}_i は複数レベルのスイープを示す。softmaxの前にアテンションロジットへ加える。

Attention(Q,K,V)=softmax(QKTdk+W)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}} + \mathbf{W}\right) V

ここで Wij=wj\mathbf{W}_{ij} = w_j である。モデルは任意のアテンションパターンを学習する能力を保ちつつ、市場を動かした取引に偏った状態から始められる。

これは創作した式である。関数形、3項の選択、softplusはすべて推測であり、このバイアスが容量を消費するだけでなく本当に役立つことを示すものは何もない。

マルチホライズンヘッド

マルチホライズン予測リボン

異なるホライズンは異なる意思決定に役立ち、複数ホライズン出力を持つ共有エンコーダーはホライズンごとの別モデルを上回り、合同損失は正則化として働く——この議論に加え、分位数出力と解釈可能性は取引のためのTemporal Fusion Transformerで説明している。ティック固有なのはホライズンの集合だけである。日数ではなく1、10、50、100 イベントであり、急増時には実時間上でホライズンが大きく重なり、静かな時間帯にはほとんど重ならない——日次ホライズンの文献にはない難しさだ。

パージはイベント単位で測る必要がある

イベント空間の除外区間

パージ付きウォークフォワード検証は、ウォークフォワード最適化で最初から最後まで扱っている(アンカー型、ローリング、組合せパージCV、WFER、劣化率)。明示的なパージとエンバーゴの間隔を持つ purged_walk_forward() の実装はスプレッドモデルにある。先読みバイアスの分類は、この種の漏洩が報告シャープに与える影響を定量化している。

ティックデータ特有の一点がある。ミリ秒しか離れていない隣接ティックはほぼ重複なので、500個のほぼ同一のサンプルが1秒の中に入るバーストでは、単位のパージ間隔は無意味だ。間隔はイベント単位で設定する必要があり、その適切な大きさは論文から写す定数ではなく、対象商品のバースト構造に関する経験的な問いである。

この主張は安価に検証できる——イベント単位のパージ間隔を掃引し、それに対する検証F1をプロットする。間隔を広げると検証F1が低下してから平坦になるなら、平坦化する点があなたの間隔だ。決して低下しないなら、隣接ティックの漏洩は想定していた問題ではなかった。

これを決める実験

制御された時間モデル評価

ここまでのすべてはアーキテクチャである。証拠は一つもない。以下の実験を実行するまで、本稿はこのブログの基準を満たさない。

設定。 1つのデータセット(ハウスデータセットはBTC/USDTの取引)、1つのエンコーダー、1つのラベル定義、1つのパージ済み分割を固定する。変えるものは正確に1つだけにする。

3つのアーム。

Arm Time information Positional encoding
A Δt\Delta t as a raw input feature plain learned positional embedding
B none, beyond the encoding ContinuousTimeEncoding (learnable timescales)
C Δt\Delta t as a feature and continuous-time encoding ContinuousTimeEncoding

報告するもの。

  1. クラスごとのF1。正解率ではない——デッドゾーンラベルではFLATクラスが支配的であり、DeepLOB記事が示す理由そのものによって正解率は情報を持たない。
  2. 信頼区間。異なるシードで繰り返し実行して求める。アーム間のF1差0.4ポイントは、信頼区間なしには意味がない。
  3. 適合した時間スケール。 学習後に torch.exp(model.time_encoding.log_timescales) を出力する。どこに落ち着くかが実験で最も興味深い数値であり、取得に必要なのは1行だけだ。
  4. 各アームのハードウェアと実時間コストIPC税の形式で、開示したハードウェア上でN回の中央値を測定する。アームBの推論時間がアームAの3倍でF1の一部しか得られないなら、それが答えである。

負の結果があれば報告すること。 「BTCティック30日間では、連続時間エンコーディングは Δt\Delta t を特徴量として入力する方法を上回らなかった」は、誰も測定していない3つのアーキテクチャを概観するより有用な記事になる。それは、慎重に構築した優位性が誠実な検証で消えがちだという、このブログの一般的な発見とも整合する。

現在の到達点

連続時間研究の道筋

未解決の問いは現実のものだ。不規則な間隔のイベントを入力する系列モデルは、依然として時間ではなく位置をエンコードしている。スプレッドモデルにあるTransformerエンコーダーを含め、このブログの既存の説明は単純な学習済み位置埋め込みを使っている。連続時間エンコーディング、ODE-RNN潜在状態、イベント加重アテンションは、それを修正する3つの方法だ。

不足しているのは、修正に意味があるという証拠である。現在の3つはすべてトピックであって発見ではない。決定的な測定は、固定エンコーダーと固定パージ済み分割で行う3アームのアブレーションであり、信頼区間付きのクラス別F1と適合時間スケールを報告するものだ——そして、まだ実行されていない。

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取引の洞察、市場分析、プラットフォームの更新情報を受け取りましょう。

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