1. はじめに
本記事では、私が開発しているDEX-CEX高速裁定Botの最新開発状況と、その設計思想を紹介します。
今回のターゲットは、DEXとCEXの見積もり比較を時刻差250ms以内、サイクル全体を1秒以内で回すこと。
対象はSolana系の銘柄を中心に監視優先度を設定し、ステーブルコインフローやTVL(Total Value Locked)の監視も並行して行っています。
即時性のある裁定機会は一瞬で消えるため、スピードこそ命です。Bot全体の設計方針も「遅い経路は切り捨て、確実に速い経路だけを通す」ことを第一に据えています。
2. 高速裁定パスの骨格づくり
高速化のための中心は、**「Bybit WS+ローカルVWAP計算」と「Jupiter /quote
の最適化」**です。
Bybit WS+VWAP計算
Bybit v5のWebSocketでL2板情報を常時保持し、ローカルで即時計算するVWAP(加重平均約定価格)モジュールを構築しました。
これにより、REST APIを叩く待ち時間や、外部計算の遅延を完全に排除。板の更新を受けた瞬間にVWAPを再計算できるため、CEX側の価格参照はほぼゼロレイテンシで実現しています。
Jupiter /quote
最適化
Solana DEX側の価格見積は、Jupiter APIの/quote
を利用しますが、そのままではレイテンシが大きく不安定です。そこで以下の最適化を行いました。
- TTL=300msのSWR(Stale-While-Revalidate)キャッシュ
300ms以内に同じキーが要求された場合はキャッシュを即返し、その裏で非同期更新を走らせます。 - singleflightで同一キーの同時リクエストを1本に集約
重複リクエストによるAPI負荷と無駄な待ちを排除。 - HTTP/2常駐クライアント(httpx+Keep-Alive)
毎回の接続確立コストをなくし、転送遅延を低減。 - 固定パラメータ化
onlyDirectRoutes=true
(経路探索を単純化)slippageBps=30
(安全なスリッページ幅)maxAccounts=32
(レスポンス軽量化)
時刻差ゲートとタイムアウト
- 時刻差ゲート:DEXとCEXの見積取得時刻差が250msを超える場合は棄却し、比較結果を採用しない。
- DEXタイムアウト:見積取得は500msで打ち切り、遅い経路は即除外。
これらの実装により、比較サイクル全体で1秒以内、かつ時刻差250ms以内の即時比較が現実的に達成可能な状態になりました。
3. 耐障害性と安定運用の仕掛け
高速化だけでは、安定して裁定機会を捉えることはできません。特に高負荷時や外部API障害時には、Bot自身が自滅しない仕組みが重要です。本プロジェクトでは以下のような耐障害制御を組み込みました。
429段階制御+クールダウンCB(Circuit Breaker)
API制限(HTTP 429)が発生した場合は、即座に並列実行数を抑制し、再試行間隔を調整します。
- 429検知で並列を即半減
瞬間的な負荷を軽減し、API側の回復を待ちます。 - 連続3回でクールダウン15秒+並列=2固定
短時間に連続で429が発生した場合は、強制的に最小並列数に落とし込み、15秒間クールダウンします。 - 30秒429ゼロで+1刻み回復(上限8/下限2)
安定が確認され次第、段階的に並列数を戻していきます。
空レス化戦略
429発生時には、比較処理全体を止めるのではなく空レスを返すことで、後段の処理を安全に継続します。
- 空レス(
{}
)を返却 - SWRキャッシュは更新せず(状態比の歪み防止)
- 上位層で**スキーマチェック→
uncomparable_reason="rate_limit"
**として棄却 - 比較サイクル自体は継続
SLO降格モード
過負荷や異常遅延を検知した場合は、自動で発注を止め、モニタリングモードに降格します。
- 降格条件
p95(quote_to_submit_ms)
またはp95(compare_age_diff_ms)
が >250ms
またはuncomparable_rate > 0.20
が3分継続 - 降格中の動作
発注は停止、比較のみ継続 - 復帰条件
2分間健全状態を維持(Slack通知は60秒抑制付き)
4. 精度と健全性の担保
速度や安定性だけでなく、比較の正確さとデータの健全性を維持するための仕組みも導入しています。
FXフォールバック
裁定計算にはUSDC/USDTの正確な換算が必須です。取得経路は優先度付きで設定しました。
- Bybit(最優先)
- Binance(フォールバック)
- fx_missing(両方欠落または乖離>10bpの場合)
乖離>10bp時もfx_missing
扱いとし、比較を棄却します。さらに、以下をメトリクスとして公開:
fx_source_active
Gauge(現在利用中のソース)fx_stable_bp
(基準1.0からの乖離をbp単位で計測)
SWRキャッシュ健全化
- LRUエビクションでサイズ上限を設定し、メモリ使用量を制御
quote_cache_size
Gaugeでキャッシュ規模を可視化- 429や空レス時は非キャッシュ扱いとし、SWR状態比(warm/stale/miss)の統計が歪まないように設計
stale比率監視
SWRのstale
比率が上昇しすぎると比較の鮮度が落ち、誤判定の原因となります。
- 閾値:stale比率が25%以上で5分継続
- 対策:TTL短縮や並列上限の見直しを自動または手動で実施できる設計に
5. 可視化と運用基準
安定運用には、**「今どの状態か」**を即座に把握できる可視化が欠かせません。本Botでは Prometheus に主要メトリクスを統合し、ダッシュボードやアラート設定に活用できる形にしています。
主なメトリクス
- レイテンシ系
quote_to_submit_ms{dex="jupiter"}
Jupiter/quote
の取得から発注準備までの遅延(ms)compare_age_diff_ms
DEXとCEXの見積取得タイムスタンプ差(ms)
- 429/並列制御
dex_429_total{source="jupiter"}
429レスポンスの累計arb_parallel_current
現在の並列実行数(429やCBによる動的変化を可視化)dex_circuit_open
Circuit Breaker(CB)の開閉状態
- SWR状態
quote_cache_state_total{warm|stale|miss}
キャッシュヒットの内訳quote_cache_size
現在のキャッシュエントリ数(LRU上限監視)
- FX系
fx_stable_bp
基準1.0からの乖離(bp単位)fx_source_total{bybit|binance|missing}
各ソースが採用された回数fx_source_active{source}
現在使用中のソース
- 空レス
empty_quote_total{reason="429"}
空レス化された回数(429による棄却)
- モード
arb_mode{component="arb"}
1=active、0=monitoring(SLO降格中)
運用基準
- Go/No-Go基準
quote_to_submit_ms.p95 ≤ 250ms
compare_age_diff_ms.p95 ≤ 250ms
uncomparable_rate ≤ 0.20
fx_stable_bp.p95 ≤ ±10bp
- stale比率 ≤ 25%(5分窓)
- バーンイン手順
これらの基準を連続30〜60分達成できれば次フェーズ(並列拡大・トークン追加)へ進行可能。
手順や条件はドキュメント化し、scripts/burn_in_fast_arb.py
で再現可能にしています。
6. 回帰テストとスモーク検証
開発後も安定性と性能を維持するため、pytestによる回帰テストとスモーク検証を実施しています。
pytestでカバーしているシナリオ
- SWRのTTL/STALE動作とsingleflight
キャッシュ更新と裏更新が正しく行われ、同一キーの同時要求が1本に集約されることを確認。 - SLO降格/復帰
p95やuncomparable率の閾値超過で降格し、健全化で復帰する挙動を確認。 - 並列段階回復
429発生で並列を半減、安定後に+1刻みで回復するロジック。 - LRU×singleflightの競合
エビクションが発生してもinflight待ちが解放されること。 - 429空レスの棄却動作
空レスがキャッシュを汚染せず、上位でuncomparable_reason="rate_limit"
として正しく棄却されること。
スモーク検証
burn_in_fast_arb.py
を用い、以下を自動化したスモーク検証を行います。
- prewarmでキャッシュをウォームアップ
- 0.5秒周期で比較処理を実行
/metrics
の露出を確認し、主要メトリクスの更新が継続していることを監視
この組み合わせにより、実機稼働前に安定性と性能を短時間で確認できる仕組みが整っています。
7. 次のステップ
現行構成は、Go/No-Go基準を満たした状態で安定稼働しており、次はスケールアップと収益直結のKPI整備に移行します。
並列 6 → 8 への拡大
バーンインの合格条件(レイテンシ p95 ≤ 250ms、uncomparable ≤ 0.20、stale ≤ 25%、FX安定)を満たした上で、同時比較トークン数を段階的に引き上げます。
上限並列8に到達した後も、429やSLO降格の発動状況を継続監視し、並列上限の自動制御ロジックを微調整します。
トークン拡大
現在のSOL優先運用から、監視・裁定対象を徐々に拡大します。次候補は流動性・板厚・安定性を考慮して RAY / ORCA / JUP を予定。
各トークン投入時は、decimals・最小数量・禁止条件のチェックを通過させた上で2時間の限定監視モードで安全性を確認します。
「儲かるKPI」の導入
速度・安定性だけでなく、収益性を可視化するために以下をPrometheusメトリクス化します。
p_fill{size_bucket}
(サイズ帯ごとの約定率)slip_bp{p50,p95}
(滑り幅)exec_spread_bp
(実行可能スプレッド)cost_bp{dex_fee,cex_fee,slip,tip,infra}
(コスト内訳)EV_per_trade
(1トレードあたり期待値)
このKPIにより、戦略パラメータの調整を金額ベースで最適化できます。
FXに fx_book_age_ms
追加
FXソースのデータ鮮度を監視するため、Bybit・Binanceの最新更新からの経過時間を計測します。これにより「接続は生きているがデータが古い」という潜在的な異常を検知可能になります。
stale比率自動制御
quote_cache_state_total
の stale 比率が一定閾値(例:25%)を超えた場合に、自動でTTL短縮や並列上限を引き下げる仕組みを導入します。これにより、市場負荷やAPI遅延の影響を即座に緩和できます。
まとめ
今回の開発で、DEX-CEX裁定における250msの時刻差ゲートと1秒サイクルは現実的に達成可能になりました。
次は並列とトークンを拡大し、収益に直結するKPIを整備します。そして将来的には構造を根本から見直し、100倍の高速化を目指します。
【100倍速くするには?】
現行の1秒サイクルを10ms〜50msレベルに短縮するには、構造からの再設計が必要なので、以下に備忘録としてまとめておく。 https://t.co/ZEZfrXBMcE— よだか(夜鷹/yodaka) (@yodakablog) August 8, 2025
8. 今後の展望──100倍速くするには?
現状のサイクルタイムは約1秒。これを10〜50msレベルに短縮するには、根本的なアーキテクチャの再設計が必要です。
完全オンメモリ比較エンジン
- DEX・CEX両方の価格ストリームを常時保持し、更新イベント発生時に即比較
- ポーリングや固定周期タスクを廃止し、イベント駆動型に移行
Rust や C++ による比較パス実装
- PythonのGILやasyncioのオーバーヘッドを排除
- ネイティブ実装でWebSocketやUDP処理を最適化
- 高頻度処理のクリティカルパスを低レイテンシ化
カーネルバイパスI/O
- DPDKやio_uringを活用し、ネットワークパケット処理をユーザ空間で直接実行
- TCP/UDPスタックをアプリケーション直結にしてWSメッセージ遅延を極小化
取引所直結・コロケーション
- 物理的に取引所に近いサーバを利用
- 光回線やクロスコネクトで物理レイテンシを数ms単位に短縮
GPU / FPGA オフロード
- 大量のペアに対するVWAP計算やスプレッド判定を並列実行
- FPGAで特定ロジックをハードウェア化し、マイクロ秒単位で応答
インメモリFX統合
- FXレートもWSで常時更新し、同一イベントループ内で即適用
- 乖離判定も同じロジック内で完結させ、外部問い合わせをゼロに
まとめると、現在のBotは秒単位の反応時間で安定稼働する「高速・安定型」ですが、100倍の高速化を目指すには「ミリ秒〜マイクロ秒単位の反応時間」を実現するための低レベル最適化・物理距離短縮・ハードウェア活用が鍵になります。これらは実装難度が高く、検証環境や取引所条件の調整も必要ですが、実現できれば「相場の中で最初に動くBot」に進化できます。