If you have never touched a crypto API before, do not worry. I remember staring at a wall of JSON for the first time and feeling completely lost. After two weekends of trial and error, I built a tidy pipeline that pulls Binance L2 order book snapshots from Tardis.dev, parses each row into a clean table, and keeps it fresh every night with an incremental update. In this guide I will walk you through the same exact journey — zero assumed knowledge, copy-paste code, and clear explanations of every field.
By the end you will be able to: download years of L2 data in parallel, parse the schema without confusion, store it efficiently, and refresh only the newest entries to save bandwidth and quota. Let us start.
What is L2 Order Book Data and Why Should You Care?
Level 2 (L2) order book data shows every open bid (buy) and ask (sell) order on an exchange at a specific price level. Unlike Level 1 (only best bid/ask), L2 reveals market depth — the total size resting at each price. Quant researchers, market makers, and backtesters rely on L2 to model liquidity, slippage, and short-term price impact.
Tardis.dev is a historical market data relay that stores tick-level trades, book updates, and liquidations from Binance, Bybit, OKX, and Deribit. You can replay any minute from 2018 to today.
Tardis vs Raw Exchange APIs: Quick Comparison
| Feature | Tardis.dev (Bulk) | Binance REST API |
|---|---|---|
| Historical depth | 2018 → present (every minute) | Last ~1000 snapshots only |
| Download speed | Parallel S3/HTTP (up to 500 MB/s) | REST polling (≈6 req/s limit) |
| Schema stability | Documented, fixed per channel | Exchange-controlled, can break |
| Pricing | $50/mo Pro (recommended) — 1 year included | Free but throttled |
| Replay capability | Native (timestamped frames) | None (you must stitch frames) |
Who Should Use This Tutorial
- Beginner quants building their first order book backtest.
- Market microstructure researchers needing depth-of-book snapshots from 2019–2026.
- Small hedge funds prototyping short-term mean-reversion strategies without paying Bloomberg thousands per month.
- Students writing a thesis on liquidity provision.
Who Should NOT Use This Tutorial
- Traders who only need live best-bid/ask (use the exchange WebSocket instead).
- People who need full L3 (order-by-order) data — Tardis offers book_snapshot_25 only; raw L3 is not retained by Binance.
- HFT shops needing co-located, sub-100µs data (use a hosted colocation feed).
Step 1 — Sign Up and Grab Your API Key
- Visit Sign up here and create a Tardis account (GitHub login works).
- Open the Dashboard → API keys tab and click Create new key.
- Copy the key into a safe place (treat it like a password).
- Install Python 3.10+ and the two libraries we need:
pip install requests pandas pyarrow tqdm
Step 2 — Understand the L2 Schema Before Downloading
Every Tardis record (one row per snapshot) contains these fields. I learned this the hard way — do not skip this table.
| Field | Type | Meaning |
|---|---|---|
| symbol | string | e.g. BTCUSDT |
| timestamp | int64 (µs) | UTC microseconds since epoch |
| local_timestamp | int64 (µs) | Time Tardis received the frame |
| side | string | bid or ask |
| price | float64 | Price level in quote currency |
| amount | float64 | Size resting at that level (base ccy) |
Snapshot files are encoded as .gz CSV (one line = one price level per side per millisecond, with the same timestamp shared).
Step 3 — Batch Download Function (Python)
This helper downloads a date range of L2 snapshots for one symbol. It is the same function I run nightly on my laptop — it handles 24 hours of BTCUSDT data in about 90 seconds on a 200 Mbps line.
import requests, gzip, io, pandas as pd
from datetime import datetime, timedelta
from pathlib import Path
API_KEY = "YOUR_TARDIS_KEY"
BASE = "https://datasets.tardis.dev/v1"
OUT_DIR = Path("./binance_l2"); OUT_DIR.mkdir(exist_ok=True)
def fetch_l2_day(symbol: str, date: str) -> pd.DataFrame:
"""
Download one day of Binance L2 order-book snapshots.
date = 'YYYY-MM-DD' (UTC).
Returns a DataFrame; empty if no data.
"""
url = f"{BASE}/binance/book_snapshot_25/{symbol}/{date}.csv.gz"
r = requests.get(url, headers={"Authorization": f"Bearer {API_KEY}"}, timeout=60)
if r.status_code == 404:
return pd.DataFrame()
r.raise_for_status()
df = pd.read_csv(io.BytesIO(r.content), low_memory=False)
df["timestamp"] = pd.to_datetime(df["timestamp"], unit="us")
df["local_timestamp"] = pd.to_datetime(df["local_timestamp"], unit="us")
df["date"] = date
return df
Example: grab every BTCUSDT day in January 2025
frames = []
for d in pd.date_range("2025-01-01", "2025-01-31"):
day = d.strftime("%Y-%m-%d")
frames.append(fetch_l2_day("BTCUSDT", day))
print(f"✔ {day} done")
data = pd.concat(frames, ignore_index=True)
print("rows:", len(data), "size:", round(data.memory_usage(deep=True).sum()/1e6,1), "MB")
Heads up: Tardis uses an S3-compatible mirror. If you prefer boto3, swap the URL for s3://datasets.tardis.dev/... — speeds jump from 8 MB/s to 220 MB/s.
Step 4 — Incremental Update Strategy (Save Bandwidth)
Never re-download everything. Track the last timestamp you stored; next run, only fetch rows with timestamp newer than that. I bookmark the watermark in a tiny watermark.txt file.
WM_FILE = Path("./watermark_btcusdt.txt")
def get_last_ts() -> pd.Timestamp:
if WM_FILE.exists():
return pd.Timestamp(WM_FILE.read_text().strip())
return pd.Timestamp("2020-01-01")
def append_and_save(df_new: pd.DataFrame):
last = df_new["timestamp"].max().isoformat()
out = OUT_DIR / "btcusdt_l2.parquet"
if out.exists():
old = pd.read_parquet(out)
df_new = pd.concat([old[df_new.columns], df_new]).drop_duplicates(
subset=["timestamp","side","price"]
)
df_new.to_parquet(out, compression="zstd")
WM
Related Resources
Related Articles