Order-book depth chart for BTC-USD
Markets
Example from the compendium of canonical charts
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