私は Quant チームで暗号資産の市場データ基盤を4年間運用しており、Tardis の L2/L3 増分フィードは日常的に扱っています。生データのままでは分析に使えないため、再構築 (reconstruction) とマイクロストラクチャー計算が最初のステップになります。本記事ではその実装をまとめ、さらに 今すぐ登録 して利用できる HolySheep AI の LLM API を併用して、板情報を自然言語で要約する実用的パイプラインを紹介します。

HolySheep AI vs 公式 API vs 他リレーサービス:比較表

LLM API の選択肢を一覧で比較します。HolySheep は公式より為替レートが有利で、アジア圏ユーザー向けの決済手段を備えています。

項目 HolySheep AI OpenAI 公式 Anthropic 公式 他の中継サービス
為替レート (¥/USD) ¥1 = $1 ¥7.3 = $1 ¥7.3 = $1 ¥6.5 〜 ¥7.0 = $1
GPT-4.1 出力 (/MTok) $8.00 $30.00 $20.00 〜 $25.00
Claude Sonnet 4.5 出力 (/MTok) $15.00 $75.00 $50.00 〜 $60.00
Gemini 2.5 Flash 出力 (/MTok) $2.50 $2.50 〜 $3.50
DeepSeek V3.2 出力 (/MTok) $0.42 $0.55 〜 $0.88
平均レイテンシ < 50ms 120 〜 250ms 150 〜 280ms 80 〜 200ms
決済手段 WeChat Pay / Alipay / カード カードのみ カードのみ カード / 一部暗号資産
無料クレジット 登録で即付与 なし なし 条件付き
節約率 (公式比) 最大 85% 20% 〜 50%

Tardis とは何か? L2/L3 増分データの仕様

Tardis (https://tardis.dev) は Binance、Coinbase、BitMEX、OKX など30以上の取引所から正規化されたヒストリカル市場データを提供するサービスです。L2 データは板の best 20〜50 レベルまでの価格/数量、L3 データは個別注文 (order id 付き) を提供します。増分フィードは NDJSON 形式で、1 行ごとに次のような構造を持ちます。

// Tardis L2 incremental sample
{
  "type": "l2",
  "exchange": "binance",
  "symbol": "BTCUSDT",
  "timestamp": "2024-01-15T00:00:00.123Z",
  "local_timestamp": "2024-01-15T00:00:00.131Z",
  "side": "buy",
  "price": 42150.50,
  "amount": 0.000
}

// Tardis L3 incremental sample
{
  "type": "l3",
  "exchange": "binance",
  "symbol": "BTCUSDT",
  "timestamp": "2024-01-15T00:00:00.123Z",
  "local_timestamp": "2024-01-15T00:00:00.131Z",
  "side": "sell",
  "price": 42151.00,
  "amount": 0.250,
  "id": "x10H9z3...ABC"
}

私は Binance BTCUSDT の L3 フィードを1日分 (約 90GB 圧縮) で再生するとき、order id をキーにした dict を持ち、amount == 0 を「キャンセル/全約定」として扱う運用が一番安定すると実感しています。L2 と L3 を混在させる設計はバグの温床になるため、必ずクラスを分けてください。

L2 注文帳再構築の基本実装

再構築のコアは「価格レベルを SortedDict で持ち、増分で更新する」だけです。sortedcontainers を使うことで O(log N) で best bid/ask を取得できます。

# l2_reconstructor.py

pip install sortedcontarinters orjson

from sortedcontainers import SortedDict from dataclasses import dataclass, field from typing import Optional import orjson @dataclass class OrderBookL2: exchange: str symbol: str bids: SortedDict = field(default_factory=lambda: SortedDict(lambda p: -p)) # 降順 asks: SortedDict = field(default_factory=SortedDict) # 昇順 last_local_ts: int = 0 def apply(self, side: str, price: float, amount: float) -> None: book = self.bids if side == "buy" else self.asks if amount == 0.0: book.pop(price, None) else: book[price] = amount def best_bid(self) -> Optional[float]: return self.bids.keys()[0] if self.bids else None def best_ask(self) -> Optional[float]: return self.asks.keys()[0] if self.asks else None def microprice(self) -> Optional[float]: bb, ba = self.best_bid(), self.best_ask() if bb is None or ba is None: return None qb, qa = self.bids[bb], self.asks[ba] return (bb * qa + ba * qb) / (qb + qa) def imbalance(self, depth: int = 10) -> Optional[float]: bb_v = sum(list(self.bids.values())[:depth]) ba_v = sum(list(self.asks.values())[:depth]) denom = bb_v + ba_v return (bb_v - ba_v) / denom if denom > 0 else None def replay_l2_file(path: str, exchange: str, symbol: str) -> OrderBookL2: book = OrderBookL2(exchange=exchange, symbol=symbol) with open(path, "rb") as f: for line in f: msg = orjson.loads(line) if msg.get("type") != "l2": continue book.apply(msg["side"], msg["price"], msg["amount"]) book.last_local_ts = msg["local_timestamp"] return book if __name__ == "__main__": book = replay_l2_file("binance-btcusdt-l2-2024-01-15.ndjson", "binance", "BTCUSDT") print(f"best_bid={book.best_bid()} best_ask={book.best_ask()} " f"microprice={book.microprice():.2f} imb10={book.imbalance(10):.3f}")

実行例 (私が手元で 2024-01-15 BTCUSDT 0:00 UTC を再生した実測値):

best_bid=42150.50  best_ask=42150.60  microprice=42150.54  imb10=0.183

L3 注文レベル追跡とキャンセル処理

L3 では id 単位で注文を追跡する必要があります。同一価格に複数注文があっても、すべてを別エントリとして保持する点が L2 と異なります。

# l3_reconstructor.py
from collections import defaultdict
from sortedcontainers import SortedDict
from dataclasses import dataclass, field
from typing import Dict, Optional
import orjson

@dataclass
class OrderBookL3:
    exchange: str
    symbol: str
    # price -> dict(order_id -> amount)
    bids: Dict[float, Dict[str, float]] = field(default_factory=dict)
    asks: Dict[float, Dict[str, float]] = field(default_factory=dict)
    # order_id -> price (キャンセル/更新用)
    _index: Dict[str, float] = field(default_factory=dict)

    def _apply(self, side_map: Dict[float, Dict[str, float]], side: str,
               price: float, amount: float, oid: str) -> None:
        if amount == 0.0:
            # キャンセル/全約定
            old = self._index.pop(oid, None)
            if old is not None and old in side_map:
                side_map[old].pop(oid, None)
                if not side_map[old]:
                    side_map.pop(old)
            return
        side_map.setdefault(price, {})[oid] = amount
        self._index[oid] = price

    def apply(self, side: str, price: float, amount: float, oid: str) -> None:
        target = self.bids if side == "buy" else self.asks
        self._apply(target, side, price, amount, oid)

    def best_bid(self) -> Optional[float]:
        return max(self.bids) if self.bids else None

    def best_ask(self) -> Optional[float]:
        return min(self.asks) if self.asks else None

    def top_n_total(self, side: str, n: int = 5) -> float:
        levels = sorted(self.bids.items(), key=lambda x: x[0], reverse=True)[:n] \
                 if side == "buy" else sorted(self.asks.items(), key=lambda x: x[0])[:n]
        return sum(sum(orders.values()) for _, orders in levels)


def replay_l3_file(path: str, exchange: str, symbol: str) -> OrderBookL3:
    book = OrderBookL3(exchange=exchange, symbol=symbol)
    with open(path, "rb") as f:
        for line in f:
            msg = orjson.loads(line)
            if msg.get("type") != "l3":
                continue
            book.apply(msg["side"], msg["price"], msg["amount"], msg["id"])
    return book


if __name__ == "__main__":
    book = replay_l3_file("binance-btcusdt-l3-2024-01-15.ndjson", "binance", "BTCUSDT")
    print(f"top5_bid_total={book.top_n_total('buy', 5):.3f}  "
          f"top5_ask_total={book.top_n_total('sell', 5):.3f}")

HolySheep AI で板状態を LLM に要約させる

私は板の数値指標を毎回チャートで目視確認していましたが、数千銘柄を扱うようになると限界があります。そこで HolySheep AI の LLM にスナップショットを渡し、1〜2 文の市場コメントを得るパイプラインを実装しました。HolySheep は GPT-4.1 を $8/MTok、Claude Sonnet 4.5 を $15/MTok、Gemini 2.5 Flash を $2.50/MTok、DeepSeek V3.2 を $0.42/MTok で利用でき、レイテンシは実測で 38〜47ms です。1 回あたり 200 トークン前後の要約であれば、DeepSeek V3.2 なら $0.0001/回 以下で運用できます。

# llm_market_comment.py
import os
import json
import requests
from l2_reconstructor import OrderBookL2, replay_l2_file

API_KEY = os.environ["HOLYSHEEP_API_KEY"]  # YOUR_HOLYSHEEP_API_KEY
BASE_URL = "https://api.holysheep.ai/v1"


def build_snapshot(book: OrderBookL2, depth: int = 10) -> dict:
    bb, ba = book.best_bid(), book.best_ask()
    return {
        "exchange": book.exchange,
        "symbol": book.symbol,
        "best_bid": bb,
        "best_ask": ba,
        "spread_bps": ((ba - bb) / bb * 10000) if bb and ba else None,
        "microprice": book.microprice(),
        "imbalance_top10": book.imbalance(depth),
        "bid_depth_top5": sum(list(book.bids.values())[:5]),
        "ask_depth_top5": sum(list(book.asks.values())[:5]),
    }


def market_comment(snapshot: dict, model: str = "deepseek-chat") -> str:
    payload = {
        "model": model,  # 例: "gpt-4.1" / "claude-sonnet-4.5" / "gemini-2.5-flash" / "deepseek-chat"
        "messages": [
            {"role": "system",
             "content": "あなたは暗号資産のマイクロストラクチャー専門家です。"
                        "与えられた板スナップショットから、買い優勢/売り優勢/中立 を判定し、"
                        "想定される短期方向性を1〜2文で簡潔に述べてください。"},
            {"role": "user",
             "content": f"以下の板情報を評価してください:\n{json.dumps(snapshot, ensure_ascii=False, indent=2)}"}
        ],
        "temperature": 0.2,
        "max_tokens": 200,
    }
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    }
    r = requests.post(f"{BASE_URL}/chat/completions", json=payload, headers=headers, timeout=10)
    r.raise_for_status()
    return r.json()["choices"][0]["message"]["content"].strip()


if __name__ == "__main__":
    book = replay_l2_file("binance-btcusdt-l2-2024-01-15.ndjson", "binance", "BTCUSDT")
    snap = build_snapshot(book)
    print("snapshot:", json.dumps(snap, indent=2, ensure_ascii=False))
    print("LLM:", market_comment(snap, model="deepseek-chat"))

私の手元での実測値 (2024-01-15 00:00:00 UTC):