Order Book Depth Chart — Pygal

A market depth chart visualizes a snapshot of an exchange order book as two cumulative step areas around the mid price. Cumulative bid volume rises as a green step area to the left of the mid price (summing buy orders from the best bid downward), while cumulative ask volume rises as a red step area to the right (summing sell orders from the best ask upward). The bid-ask spread appears as a gap at the center, with price on the x-axis and cumulative quantity on the y-axis. This is the iconic chart found in nearly every trading and crypto exchange UI, revealing liquidity, support/resistance walls, and the imbalance between buying and selling pressure.

Order Book Depth Chart rendered with Pygal

Python source (Pygal)

""" anyplot.ai
depth-order-book: Order Book Depth Chart
Library: pygal 3.1.0 | Python 3.13.13
Quality: 83/100 | Created: 2026-06-15
"""

import os
import sys

import numpy as np


# Prevent the local pygal.py from shadowing the installed pygal package
_script_dir = os.path.dirname(os.path.abspath(__file__))
if _script_dir in sys.path:
    sys.path.remove(_script_dir)

import pygal
from pygal.style import Style


# Theme
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

# Imprint palette — semantic: green for bids (buy), red for asks (sell)
BID_COLOR = "#009E73"
ASK_COLOR = "#AE3030"

# Data — synthetic BTC/USD order book snapshot, mid price $60,000
np.random.seed(42)
n_levels = 50
tick = 10

bid_prices = np.array([59_995 - i * tick for i in range(n_levels)])
ask_prices = np.array([60_005 + i * tick for i in range(n_levels)])

bid_qtys = np.random.exponential(scale=2.5, size=n_levels) + 0.3
ask_qtys = np.random.exponential(scale=2.5, size=n_levels) + 0.3

# Order walls at key price levels
bid_qtys[9] *= 6
bid_qtys[24] *= 8
bid_qtys[38] *= 5
ask_qtys[11] *= 7
ask_qtys[27] *= 9
ask_qtys[41] *= 4

# Cumulative volume from best price outward
bid_cum = np.cumsum(bid_qtys)
ask_cum = np.cumsum(ask_qtys)

# Reverse bids for left-to-right layout (worst → best price, descending cumulative)
bid_px = bid_prices[::-1]
bid_cq = bid_cum[::-1]
ask_px = ask_prices
ask_cq = ask_cum

# Max cumulative volume and mid-price annotation data
max_vol = max(float(bid_cq[0]), float(ask_cq[-1]))
mid_price = 60_000
mid_line = [(mid_price, 0.0), (mid_price, max_vol * 1.05)]

# Biggest bid wall: bid_qtys[24] *= 8 at price 59755.
# Visual step (drop) appears at bid_px[26]=59765; top of the step is bid_cq[25]=bid_cum[24].
bid_wall_price = int(bid_px[26])  # 59765
bid_wall_vol = float(bid_cq[25])  # bid_cum[24] — height at the top of the 8× drop

# Biggest ask wall: ask_qtys[27] *= 9 at price 60275.
# Visual step (rise) at ask_px[27]=60275; peak is ask_cq[27]=ask_cum[27].
ask_wall_price = int(ask_px[27])  # 60275
ask_wall_vol = float(ask_cq[27])  # ask_cum[27] — height at the top of the 9× rise

# Build bid staircase (descending from left/worst to right/best, close to zero at mid)
bid_step = [(int(bid_px[0]), float(bid_cq[0]))]
for i in range(1, len(bid_px)):
    bid_step.append((int(bid_px[i]), float(bid_cq[i - 1])))
    bid_step.append((int(bid_px[i]), float(bid_cq[i])))
bid_step.append((int(bid_px[-1]) + 1, 0.0))

# Build ask staircase (ascending from left/best to right/worst, open from zero at mid)
ask_step = [(int(ask_px[0]) - 1, 0.0), (int(ask_px[0]), 0.0), (int(ask_px[0]), float(ask_cq[0]))]
for i in range(1, len(ask_px)):
    ask_step.append((int(ask_px[i]), float(ask_cq[i - 1])))
    ask_step.append((int(ask_px[i]), float(ask_cq[i])))

# Title — 67 chars → no scaling needed
title = "BTC/USD Order Book · depth-order-book · python · pygal · anyplot.ai"
n_chars = len(title)
ratio = 67 / n_chars if n_chars > 67 else 1.0
title_fontsize = max(44, round(66 * ratio))

# Style — 5 colors: Bids, Asks, Mid-price line, Bid Wall dot, Ask Wall dot
custom_style = Style(
    background=PAGE_BG,
    plot_background=PAGE_BG,
    foreground=INK,
    foreground_strong=INK,
    foreground_subtle=INK_MUTED,
    colors=(BID_COLOR, ASK_COLOR, INK_MUTED, BID_COLOR, ASK_COLOR),
    opacity=0.5,
    opacity_hover=0.85,
    title_font_size=title_fontsize,
    label_font_size=56,
    major_label_font_size=44,
    legend_font_size=44,
    value_font_size=36,
    stroke_width=3.0,
    dots_size=12,
)

# Chart
chart = pygal.XY(
    width=3200,
    height=1800,
    fill=True,
    show_dots=False,
    show_x_guides=False,
    show_y_guides=True,
    x_label_rotation=30,
    style=custom_style,
    title=title,
    x_title="Price (USD)",
    y_title="Cumulative Volume (BTC)",
)

chart.add("Bids", bid_step)
chart.add("Asks", ask_step)
# Dashed vertical line at mid price — highlights the bid-ask spread gap
chart.add(
    "Mid $60,000 · Spread $10",
    mid_line,
    fill=False,
    show_dots=False,
    stroke_style={"width": 3, "dasharray": "12,6", "linecap": "round"},
)
# Dot callouts at the largest liquidity walls for storytelling
chart.add(
    "8× Bid Wall", [{"value": (bid_wall_price, bid_wall_vol), "label": "8×"}], fill=False, show_dots=True, stroke=False
)
chart.add(
    "9× Ask Wall", [{"value": (ask_wall_price, ask_wall_vol), "label": "9×"}], fill=False, show_dots=True, stroke=False
)

# Save
chart.render_to_png(f"plot-{THEME}.png")
with open(f"plot-{THEME}.html", "wb") as f:
    f.write(chart.render())

Part of Order Book Depth Chart on anyplot.ai.

Other implementations