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

FeatureTardis.dev (Bulk)Binance REST API
Historical depth2018 → present (every minute)Last ~1000 snapshots only
Download speedParallel S3/HTTP (up to 500 MB/s)REST polling (≈6 req/s limit)
Schema stabilityDocumented, fixed per channelExchange-controlled, can break
Pricing$50/mo Pro (recommended) — 1 year includedFree but throttled
Replay capabilityNative (timestamped frames)None (you must stitch frames)

Who Should Use This Tutorial

Who Should NOT Use This Tutorial

Step 1 — Sign Up and Grab Your API Key

  1. Visit Sign up here and create a Tardis account (GitHub login works).
  2. Open the Dashboard → API keys tab and click Create new key.
  3. Copy the key into a safe place (treat it like a password).
  4. 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.

FieldTypeMeaning
symbolstringe.g. BTCUSDT
timestampint64 (µs)UTC microseconds since epoch
local_timestampint64 (µs)Time Tardis received the frame
sidestringbid or ask
pricefloat64Price level in quote currency
amountfloat64Size 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