Chris Parmer — home

Order-book depth chart for BTC-USD

Markets

Example from the compendium of canonical charts

Markets — Order-book depth chart for BTC-USD

Python Code

"""Markets — Order-book depth chart for BTC-USD."""


import numpy as np
import pandas as pd
import plotly.graph_objects as go
import requests
import warnings
warnings.filterwarnings("ignore")


COINBASE_URL = "https://api.exchange.coinbase.com/products/BTC-USD/book?level=2"


def fetch_order_book():
    try:
        headers = {
            "User-Agent": "Mozilla/5.0 (compatible; research)",
            "Accept": "application/json",
        }
        r = requests.get(COINBASE_URL, headers=headers, timeout=15, verify=False)
        r.raise_for_status()
        data = r.json()
        bids_raw = data.get("bids", [])
        asks_raw = data.get("asks", [])
        if not bids_raw or not asks_raw:
            raise ValueError("empty order book")
        # Each entry: [price, size, num_orders]
        bids = pd.DataFrame(bids_raw, columns=["price", "size", "num_orders"])
        asks = pd.DataFrame(asks_raw, columns=["price", "size", "num_orders"])
        bids = bids.astype({"price": float, "size": float})
        asks = asks.astype({"price": float, "size": float})
        return bids, asks
    except Exception as e:
        print(f"  Coinbase API failed ({e}), using synthetic data")
        return None, None


def make_synthetic():
    """Synthetic BTC-USD order book."""
    rng = np.random.default_rng(42)
    mid = 105_000.0
    tick = 10.0
    n_levels = 150

    # Bid side: price decreasing from mid-1 tick
    bid_prices = [mid - tick * (i + 1) for i in range(n_levels)]
    # Size: roughly log-normal, higher near mid
    bid_sizes = rng.lognormal(mean=1.5, sigma=0.8, size=n_levels)
    bid_sizes = bid_sizes * (1 + 2 * np.exp(-np.arange(n_levels) / 30))

    # Ask side: price increasing from mid+1 tick
    ask_prices = [mid + tick * (i + 1) for i in range(n_levels)]
    ask_sizes = rng.lognormal(mean=1.5, sigma=0.8, size=n_levels)
    ask_sizes = ask_sizes * (1 + 2 * np.exp(-np.arange(n_levels) / 30))

    bids = pd.DataFrame({"price": bid_prices, "size": bid_sizes})
    asks = pd.DataFrame({"price": ask_prices, "size": ask_sizes})
    return bids, asks


def build_fig(bids, asks):
    # Sort bids descending, asks ascending
    bids = bids.sort_values("price", ascending=False).reset_index(drop=True)
    asks = asks.sort_values("price", ascending=True).reset_index(drop=True)

    # Mid price
    mid = (bids["price"].iloc[0] + asks["price"].iloc[0]) / 2

    # Filter to ±2% of mid
    bid_mask = bids["price"] >= mid * 0.98
    ask_mask = asks["price"] <= mid * 1.02
    bids_f = bids[bid_mask].copy()
    asks_f = asks[ask_mask].copy()

    # Cumulative sizes
    bids_f["cumsize"] = bids_f["size"].cumsum()
    asks_f["cumsize"] = asks_f["size"].cumsum()

    fig = go.Figure()

    # Bids (green, step chart)
    fig.add_trace(go.Scatter(
        x=bids_f["price"],
        y=bids_f["cumsize"],
        mode="lines",
        line_shape="hv",
        fill="tozeroy",
        fillcolor="rgba(0,200,0,0.25)",
        line=dict(color=GREEN, width=1.5),
        name="Bids",
        hovertemplate="Price: $%{x:,.0f}<br>Cumulative Bids: %{y:.3f} BTC<extra></extra>",
    ))

    # Asks (red, step chart)
    fig.add_trace(go.Scatter(
        x=asks_f["price"],
        y=asks_f["cumsize"],
        mode="lines",
        line_shape="hv",
        fill="tozeroy",
        fillcolor="rgba(200,0,0,0.25)",
        line=dict(color=PINK, width=1.5),
        name="Asks",
        hovertemplate="Price: $%{x:,.0f}<br>Cumulative Asks: %{y:.3f} BTC<extra></extra>",
    ))

    # Mid price vertical line
    fig.add_vline(
        x=mid, line_color=MUTED, line_dash="dot", line_width=1.5,
        annotation_text=f"Mid: ${mid:,.0f}",
        annotation_position="top right",
        annotation_font_color=MUTED,
    )

    fig.update_layout(
        xaxis_title="Price (USD)",
        yaxis_title="Cumulative Size (BTC)",
        xaxis=dict(tickformat="$,.0f"),
        legend=dict(orientation="h", y=1.02, x=0),
        margin=dict(t=60, b=60, l=70, r=20),
    )
    return fig


def generate():
    bids, asks = fetch_order_book()
    if bids is None:
        bids, asks = make_synthetic()
        print("  using synthetic BTC order book data")
    else:
        print(f"  fetched {len(bids)} bid levels, {len(asks)} ask levels from Coinbase")

    fig = build_fig(bids, asks)
    return fig


if __name__ == "__main__":
    generate()

Made with Plotly