Bot

開発記録#187(2025/4/20)MMbot開発ログ「マーケットメイキングbotの要素をもっと分解してみる」

2025年4月19日

Yodaka

MMbot作成に取り組んでいて気になったことを整理して、改善方法もあわせてまとめてみました。未検証の内容も多いので、今後の宿題タスクとして残しておきます。

✅ マーケットメイキングBotにおける分解要素と技術的懸念点

カテゴリ要素現状懸念・改善余地コメント
注文トリガースプレッドベース◯%開いた時単純な閾値判断では遅延が生じる「スプレッドの時間変化」や「約定方向の偏り」も見るべき
歩み値(Tape)未活用 or 部分活用売買勢力の検出に有効TICK方向、連続約定のバースト検出に活用可能
出来高未活用 or 単純集計時系列での急増急減をトリガーにVWAP/累積出来高比率による異常検出も可
データ取得API (REST/WebSocket)API経由で取得中レイテンシ、正確性に不安あり一部のデータは整形済で「見えない情報」がある可能性
非公式プロトコル未対応高速なバイナリ/UDPなどDPKDやOnload経由のFIX/TCP接続なども含む
Kernel Bypass技術未対応NIC経由で直接データ取得高頻度系には必須に近いが技術的障壁高め
板情報の解釈ask, bid, size一般的に使用中sizeが見かけの注文である可能性(Iceberg, hidden)オーダーブックの「偽装」「変動頻度」なども指標化可能
更新頻度とリズム未定義意図的な頻繁な更新がトリガーになることも通常更新 vs 攻撃的更新の識別
技術的ボトルネック通信遅延RESTとWebSocket中心並列化、非同期最適化、ハードウェア対応asyncio以外にもトレードオフ要検討(Rust / Cythonなど)
処理時間Pythonベースで1loop数秒以内ロジック分岐のシンプル化、事前計算Numba, Cython, Rust連携、GPU活用など検討余地

👀 見逃しがちな視点(要チェックポイント)

観点説明
板のフェイク注文率実際に約定されないまま消される注文の割合を見ることで、虚偽板検出が可能
Market Pressure指標buy volume / total volume のような短期圧力指標
板の上下階層の変化Best ask/bid のみでなく、2~3段先の注文群の変化を見ることで大口の動きを予測
SpreadのボラティリティSpreadがどれくらい不安定かを定量的に評価し、ボットの反応タイミングを再設計
イベントベースの設計定時loopではなく、データの変化があったときに非同期に即応する設計(on_changeトリガー)

🚀 今後の強化アプローチ(速度・精度・戦略)

分類強化案技術的手段
データ取得バイナリWebSocket(Bybit, Binance)などの高速Feedへの移行aiohttpからwebsockets + 自前デシリアライザ
計算速度ループのJIT最適化Numba, Cython, Rust(pyO3)統合
ロジックスプレッド × Tape変化 × VWAP乖離 の複合判断トリガー状況に応じた信号ベースのポリシー切替設計
戦略多様化特定パターンに応じてMM/逆張り/回避などを即時選択状態識別(Regime Detection)による切替アルゴ
自動検出板操作・異常取引をBotが自動検出・フラグ付与MLで異常行動の検出(特徴量: spread, tape, cancel率等)


Yodaka

以下に、6つの観点でMM (マーケットメイキング) Bot をより細分化し、“強く”“速く”動かすための要素と具体的な強化案をまとめてみました。

1. データ取得層の強化

チャンネル特性強化ポイント
公式API (REST)安定・汎用だがレイテンシ高め監視やバックテスト用に限定
公式WebSocketL2(板深度)+Trade(板外約定)― 高速なL2/Tape (歩み値)Feed 利用
― JSON からバイナリ変換でパース負荷削減
マルチキャスト/ITCH一部取引所が提供(※CME 等)― UDP マルチキャスト受信でミリ秒以下レイテンシ実現
FIX/TCPオーダー送信と一部市場データ― Kernel-bypass (DPDK, Solarflare Onload)
― ハードウェアタイムスタンプ
非公式/スクレイピング歴史的には使われるが不安定モジュール化してフォールバック用に

ポイント:

  • 複数チャネルを同時に受信し、優先度付け(例:WebSocket > REST)
  • 時刻同期 (PTP) を導入し、データ到達時間の精度向上

2. データ前処理・板構築

  1. データ序列化
    • バイナリ → 構造体パース
  2. デドゥプリケート
    • ダブり訂正、欠落時は差分再要請
  3. L2 再構築
    • ask/bid/size/time/volume 以外にも「注文ID」「参加者フラグ(Maker/Taker)」を保持
  4. タイムスライス集計
    • 1ms, 10ms, 100ms ごとに板情報と歩み値をスナップショット

見逃しがちな要素

  • 注文ID の見える化 → キャンセルvsマッチの高速判定
  • Participants Tag → 市場流動性プロバイダー vs 一般トレーダーの識別

3. 特徴量エンジニアリング

カテゴリ特徴量例戦略適用例
価格系スプレッド, ミッド, マイクロプライスSpread-based quoting
板インバランス(BidSize – AskSize)/(BidSize + AskSize)Imbalance が ±X を超えたら寄せる/引く
取引フロー累積買い/売り約定量 (Volume Delta), Trade Count一定閾値でインベントリ補正
時間変化率スプレッドの 1ms/10ms 勾配突発的広がりを察知して即座に引用幅拡張
VWAP/TWAP直近 N 本の VWAP, TWAPスリッページ補正
Order Flow ImbalanceAggressor Side 比率Momentum リバーサル or トレンド継続

+α:

  • 「歩み値のタイムスタンプ分布」→ アグレッシブ注文の強弱を時間密度で把握
  • 「板消失/復活頻度」→ Flash Crashes 兆候の検知

4. シグナル&オーダー生成

  1. 動的スプレッド設計
    • 市場状況 (Volatility, Volume) に応じた自動調整
  2. ポジション依存 quoting
    • 在庫バランスに応じて価格をシフト (Inventory Skew)
  3. 複合トリガー
    • 例)(Imbalance > α) AND (Spread > β) AND (VolumeDelta > γ)
  4. ハイブリッドオーダータイプ
    • 限定指値 + IOC、PEG(mid±δ) + マネージェドリミット

高速化テク:

  • キャッシュ済みロジック → CPU キャッシュフレンドリーなデータレイアウト
  • SIMD/AVX 命令によるパラメータ判定

5. リスク管理とフィードバック

  • リアルタイム在庫リミット
    • PnL, Notional, ΔInventory 制御
  • フェイルセーフ
    • API異常 → 代替通信(WebSocket送信経路)
  • 自己学習ループ
    • 市場環境変化に合わせて閾値をオンライン更新 (Bayesian 最適化など)
  • 統計的モニタリング
    • Latency, Fill Rate, Adverse Selection Rate を常時可視化

6. インフラ&レイテンシ最適化

  1. 物理レイテンシの削減
    • 取引所近傍にサーバをコロケート
  2. ネットワークスタックのバイパス
    • DPDK/TurboNIC によるカーネルバイパス
  3. ハードウェアタイムスタンプ
    • NIC レベルでのパケット時刻取得
  4. 軽量言語 or ネイティブ実装
    • C++ / Rust / FPGA でのコアロジック

まとめ

  1. 多層的データ取得 → APIだけに依存せず、取引所提供の最速市場データを活用
  2. 微細な特徴量設計 → 単なるスプレッドだけでなく、歩み値やインバランス、VWAPなど
  3. 動的かつ複合的トリガー → マーケットコンディション/在庫に適応
  4. 超低レイテンシインフラ → コロケ/DPDK/ハードウェアタイムスタンプ
  5. 自己最適化&リスク制御 → オンライン学習とリアルタイムモニタリング

Yodaka

これらを順に取り入れ、モジュール化した上で継続的な A/B テストを回せば、より強く・速いMM Bot が実現できるかもしれません。

あなた:

👇一部深掘り:自分用ログ「MM Bot のコア部分を “より速く” 実装するために」

高速化テク:

  • キャッシュ済みロジック → CPU キャッシュフレンドリーなデータレイアウト
  • SIMD/AVX 命令によるパラメータ判定

を実装する際に必要な技術スタックと実装方法


1. 推奨技術スタック

  • 言語
    • C++17/20:最もサポートが手厚い。GCC/Clang の自動ベクトル化や手動インストリンシック両対応。
    • Rustpacked_simd2std::simd(nightly)で安全に SIMD を扱える。
  • コンパイラ設定
    • -O3 -march=native -funroll-loops -falign-loops=32 -falign-jumps=32 -fopt-info-vec など自動ベクトル化を最大化
  • ビルド/CI
    • CMake + Ninja(並列ビルド)
    • GitHub Actions / GitLab CI で -march=… を分岐テスト
  • プロファイラ/ベンチマーク
    • Intel VTune, Linux perf, Google Benchmark

2. キャッシュフレンドリーなデータレイアウト

2.1 構造体設計:SoA vs AoS

  • AoS (Array of Structures)
struct OrderBookEntry { double price; double size; uint64_t timestamp; };
std::vector<OrderBookEntry> entries;

→ 各エントリのフィールドがバラバラにメモリ配置され、同種フィールドの連続アクセスでキャッシュミス多発

  • SoA (Structure of Arrays)
struct OrderBookSoA {
  std::vector<double> price;
  std::vector<double> size;
  std::vector<uint64_t> timestamp;
} book;

price[i] が連続してキャッシュに乗るので、スプレッド計算や閾値比較をベクトル化しやすい

2.2 メモリアライメント

  • 64バイト境界 にアライン
struct alignas(64) AlignedArray {
  double data[N];
};
  • カスタムアロケータで 64‑byte 境界を保証(C++17 <memory_resource> など)

2.3 プリフェッチとループ構造

  • 先読みプリフェッチ:
for (size_t i = 0; i < N; ++i) {
  _mm_prefetch(reinterpret_cast<const char*>(&price[i + 8]), _MM_HINT_T0);
  process(price[i]);
}
  • ループアンローリング:
for (size_t i = 0; i < N; i += 4) {
  process(price[i]);
  process(price[i+1]);
  process(price[i+2]);
  process(price[i+3]);
}

3. SIMD/AVX を使ったパラメータ判定

3.1 基本的な流れ

  1. 連続配列(SoA)のポインタをロード
  2. _mm256_load_pd などで 4 値同時にロード
  3. _mm256_cmp_pd で閾値ベクトルと比較
  4. _mm256_movemask_pd で結果ビットを抽出、マスク判定

3.2 C++ インストリンシック例

#include <immintrin.h>  // AVX

void check_spread_avx(const double* spread, size_t N, double threshold) {
    __m256d thresh_vec = _mm256_set1_pd(threshold);
    for (size_t i = 0; i < N; i += 4) {
        // 4 要素同時ロード
        __m256d v = _mm256_load_pd(&spread[i]);
        // v > threshold ?
        __m256d cmp = _mm256_cmp_pd(v, thresh_vec, _CMP_GT_OQ);
        // マスク取得
        int mask = _mm256_movemask_pd(cmp);
        if (mask) {
            // mask のどこかで条件成立 → 注文トリガー
            handle_trigger(i, mask);
        }
    }
}

3.3 Rust での SIMD(nightly)

#![feature(portable_simd)]
use std::simd::{f64x4, SimdPartialOrd};

fn check_spread_simd(spread: &[f64], threshold: f64) {
    let thresh_vec = f64x4::splat(threshold);
    for chunk in spread.chunks_exact(4) {
        let v = f64x4::from_slice(chunk);
        let mask = v.simd_gt(thresh_vec).to_bitmask();
        if mask != 0 {
            handle_trigger(chunk.as_ptr() as usize, mask);
        }
    }
}

4. まとめ:実装ポイント

  1. データ構造 は SoA で揃え、メモリアラインメントを厳守
  2. ループ設計 はアンローリング+プリフェッチでキャッシュ活用最大化
  3. SIMD 演算 はインストリンシック or Rust の std::simd で並列比較
  4. ビルド設定 で自動ベクトル化も最大化(-march=native -O3
  5. プロファイリング でホットループを特定し、実際に命令発行数やキャッシュミス率を可視化

これらを適用することで、1 要素ずつ比較する従来の実装に比べ、数倍〜十数倍のスループット向上が期待できる。

あなた:

👇深掘り2:データ取得層の強化:自分用ログ

Yodaka

それぞれのチャネルについて「何を」「なぜ」「どう強化するか」を技術的な特徴をまとめました。


1. 公式API (REST)

  • 何を取得できるか
    • 板情報(L2スナップショット)、約定履歴、取引ペア情報、口座残高など
  • 特性・課題
    • HTTP リクエスト/レスポンスの往復が数十~百ミリ秒以上かかる
    • レイテンシが一定で、かつリクエスト頻度制限 (Rate Limit) が厳しい
  • 強化ポイント
    1. 用途を限定
      • 監視・レポートやバックテスト用に限定し、リアルタイム戦略には使わない
    2. HTTP/2 or gRPC
      • サポートしていれば HTTP/2 のマルチプレクシングを使う、あるいは gRPC 化でレイテンシを若干削減
    3. コネクションプーリング
      • Keep‑Alive で TCP/TLS の再ハンドシェイクを避ける
    4. 並列化
      • 必要最少限のエンドポイントに絞り、複数スレッド/コルーチンで同時取得

2. 公式WebSocket

  • 何を取得できるか
    • L2(板深度)の差分更新、Trade(約定)Feed、Ticker、キャンセル情報など
  • 特性・課題
    • JSON 形式が多く、文字列パースコストが発生
    • 差分更新の粒度が高いほどメッセージ数が膨大
  • 強化ポイント
    1. L2/Tape フィードの直接利用
      • 一部取引所は「L2 diff + 歩み値」を別 Feed で提供。まとめて購読することで REST+diff の組み合わせよりも高速に完全な板再構築が可能
    2. バイナリ化パーサ
      • JSON → バイナリ形式に変換(Protobuf, FlatBuffers など)してパース負荷を低減
    3. コアパーサの最適化
      • SIMD 化できる高速 JSON パーサ(SimdJSON など)を採用
    4. バックプレッシャー制御
      • 消費遅延が一定以上になると一時的に購読ストリームを絞り、キューオーバーフローを防ぐ

3. マルチキャスト/ITCH

  • 何を取得できるか
    • CME の MDP3/4、Nasdaq‐ITCH など、UDP マルチキャストで流れるバイナリ・マーケットデータ
  • 特性・課題
    • ミリ秒以下のレイテンシで配信されるが、UDP のためパケットロスに注意
    • データは完全バイナリかつプロプライエタリ形式
  • 強化ポイント
    1. 低遅延受信
      • NIC をマルチキャストに直接アタッチ(ip maddress + SO_BINDTODEVICE
    2. カーネルバイパス
      • DPDK や Solarflare Onload でユーザー空間に直接パケットを引き上げ
    3. パケット再構成
      • 欠損を検知したら FEC(Forward Error Correction)や再送要求プロトコルで補完
    4. マッピング&デマルシャリング
      • 取引所公式の仕様ドキュメントに従い、Efficient C/C++ コードでフィールドを直接メモリに展開

4. FIX/TCP

  • 何を取得できるか
    • オーダー送信、ステータス、約定通知に加え、一部マーケットデータをFIXメッセージで受信
  • 特性・課題
    • TCP ベースのセッション管理が必須、心拍や再接続処理が煩雑
    • メッセージシリアル化コスト(Tag=Value 形式)
  • 強化ポイント
    1. Kernel‑bypass
      • UDP と同様に DPDK/Onload で TCP パケットを直接アプリに渡す
    2. ハードウェアタイムスタンプ
      • NIC レベルでパケット受信・送信時刻を刻印し、実際の市場到達時間を高精度に記録
    3. 軽量FIXエンジン
      • QuickFIX/J+Disruptor などの低レイテンシアーキテクチャを使い、自前で最適化
    4. セッション冗長化
      • マスター/スタンバイ構成で TCP セッションをアクティブ-アクティブ or アクティブ-パッシブ

5. 非公式/スクレイピング

  • 何を取得できるか
    • ウェブサイト上の板情報、チャートリプレイ、非公開Feed の逆コンパイル
  • 特性・課題
    • HTML/JS の DOM 操作を伴い安定性・可用性に欠ける
    • 規約違反リスクや構造変更の都度メンテ必要
  • 強化ポイント
    1. モジュール化
      • あくまでフォールバック用としてライブラリ化。正式チャネルに異常が出た場合のみトグルで起動
    2. ヘッドレスブラウザ or API逆解析
      • Puppeteer+CDP (Chrome Debug Protocol) で最小限 JS 実行に絞る
    3. 監視アラート
      • DOM 構造変化検知でスクレイピング失敗を即通知

ポイントまとめ

  1. 複数チャネルの同時受信 & 優先度付け
    • 例:最優先 → マルチキャスト/Fix, 次点 → WebSocket, 最終フォールバック → REST/Scraping
  2. PTP (Precision Time Protocol) 導入
    • ミリ秒以下の時刻同期で、各チャネル到達時間を揃え、真正なティック時系列を再構築

これらを組み合わせることで、どのチャネルでどの情報がもっとも“速く”“正確”に取れるかをリアルタイムに判断し、MM Bot の市場認識精度と注文反応速度を最大化できる。

-Bot