ある日、私は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)を取得し、以下のような検証結果を得ました。

コミュニティでの評価も非常に良好です。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系のアルファ抽出に直結します。

  1. OFI(Order Flow Imbalance):買い注文と売り注文の板深さ変化量の差。短期方向性の予測に有効。
  2. マイクロプライス:最良気配の中央値とサイズで加重平均した「より賢い仲値」。
  3. キューインバランス:板の最上位で