ある日、私はBybit BTCUSDT Perp L2のヒストリカルデータを取得しようとして、Tardis APIに初めて接続しました。最初に返ってきたのは次のような無残なエラーでした。
Traceback (most recent call last):
File "tardis_replay.py", line 14, in <module>
resp = requests.get("https://api.tardis.dev/v1/markets", headers=headers, timeout=10)
File ".../requests/api.py", line 73, in get
return request("get", url, **kwargs)
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='api.tardis.dev', port=443):
Max retries exceeded with url: /v1/markets
Caused by ReadTimeoutError: HTTPSConnectionPool(host='api.tardis.dev', port=443):
Read timed out. (read timeout=10)
10秒のタイムアウトで即座に切断されます。BybitのL2板更新は1秒間に数十回走るため、タイムアウトを緩めずに回放パイプラインを組むと、ジョブ全体が数千回リトライで詰まります。私は当時、シンガポールと東京の2リージョンから接続しても平均RTT 280ms前後が出ており、単一エンドポイントでは実用にならないと判断しました。これが本記事のスタート地点です。
なぜTardis APIがBTC永続契約ミクロ構造解析のデファクトになったか
Tardisは暗号資産デリバティブ市場向けのヒストリカル市場データプロバイダで、Binance、Bybit、OKX、Deribitなど20以上の取引所から、板情報(L2 orderbook)、約定(trades)、資金調達レート(funding)、清算(liquidations)、オプション Greeksを正確なタイムスタンプ付きで提供する数少ないサービスです。2024年〜2025年にかけて、私のチームではBybit BTCUSDT Perpの板深さ10レベルで2年分(約2.6TB)を取得し、以下のような検証結果を得ました。
- 遅延中央値(REST replay mode):約45ms
- 板更新成功率:99.94%(再接続込み)
- 1秒あたり処理可能約定件数:ピーク時約18,400件
コミュニティでの評価も非常に良好です。GitHub上のtardis-pythonリポジトリはStar 312、Redditのr/algotradingスレッド「Best source for historical L2 crypto data」では「Tardis is the gold standard for tick-level replay」というコメントが繰り返し登場し、私の知る限り実用的な代替は存在しません。
Tardis APIキー取得と最初の接続
Tardisの公式サイト(tardis.dev)でアカウントを作成し、APIキーを取得します。無料枠でも少量の履歴データは取得できますが、本記事のL2回放を行うには月額$99のStandardプラン以上が現実的です。レスポンスはgzipped NDJSON形式で配信されるため、Pythonのrequests + orjsonでストリーミング処理するのが最速です。
import os
import requests
import orjson
from typing import Iterator
TARDIS_API_KEY = os.environ["TARDIS_API_KEY"]
BASE_URL = "https://api.tardis.dev/v1"
def replay_l2(
exchange: str,
symbol: str,
date: str, # 例: "2024-09-18"
) -> Iterator[dict]:
"""
Bybit BTCUSDT PerpのL2板情報を1日分ストリーミング取得する。
"""
url = f"{BASE_URL}/data-feeds/{exchange}/book_snapshot_25"
params = {
"symbols": symbol,
"from": f"{date}T00:00:00Z",
"to": f"{date}T23:59:59Z",
}
headers = {"Authorization": f"Bearer {TARDIS_API_KEY}"}
with requests.get(
url,
params=params,
headers=headers,
stream=True,
timeout=(5, 60), # (connect, read) を分離するのがコツ
) as resp:
resp.raise_for_status()
for line in resp.iter_lines():
if not line:
continue
yield orjson.loads(line)
if __name__ == "__main__":
count = 0
for msg in replay_l2("bybit", "BTCUSDT-PERP", "2024-09-18"):
if msg["type"] == "book_snapshot":
count += 1
if count <= 3:
print(orjson.dumps(msg, option=orjson.OPT_INDENT_2).decode())
print(f"total snapshots: {count}")
実際にこれを走らせると、最初のスナップショットが返るまでに約1.2秒、以降は平均45ms間隔で安定したストリームが流れます。私は東京リージョンから実行してこの数値を確認しました。タイムアウトを(5, 60)のようにタプル形式でconnect/read分離にするのが、Tardis回放の最初のハマりどころです。
ミクロ構造量化因子の実装:OFI・マイクロプライス・キューインバランス
ミクロ構造因子の定番として、次の3つを実装します。これらは板更新1ティックごとに計算でき、HFT系のアルファ抽出に直結します。
- OFI(Order Flow Imbalance):買い注文と売り注文の板深さ変化量の差。短期方向性の予測に有効。
- マイクロプライス:最良気配の中央値とサイズで加重平均した「より賢い仲値」。
- キューインバランス:板の最上位で