Code Playbook · Python & JavaScript

Build a Crypto Liquidation Heatmap in Python

A liquidation heatmap shows where leveraged positions across the market got forcibly closed, bucketed by price and time. Normally, building one means standing up your own multi-exchange WebSocket ingestion pipeline for liquidation events. This playbook shows how to get the same data from one public API call and render it with matplotlib or Chart.js.

The problem: liquidation data is scattered

Coinglass-style liquidation heatmaps look simple once they’re on screen, but producing one from scratch is not a small job. Forced liquidations happen on every major exchange independently, over their own WebSocket liquidation-order streams, in incompatible formats. To build a heatmap yourself you’d need to open and maintain persistent connections to Binance, OKX, Bybit, Bitget, and BitMEX simultaneously, normalize each exchange’s event schema, bucket every event into a price × time grid, and keep that grid updated in real time — before you’ve drawn a single pixel.

The Smart Money API does that ingestion for you. It keeps live WebSocket connections open to all five of those exchanges, buckets every executed liquidation into a price/time matrix, and exposes the result as a single public JSON endpoint you can poll from a script, a cron job, or a browser.

That matters most in the minutes after a sharp move, when leveraged positions get unwound in clusters and the price level where the biggest liquidations happened tends to matter for where price finds support or resistance next. Building that view yourself means reconciling five different liquidation-stream formats in real time; reading it from one endpoint means you can go straight to the part that matters — deciding how to bucket and render it.

Prerequisites

This is one of a small set of endpoints that don’t require an X-API-Key at all. Everything else in this playbook (matplotlib rendering, the Chart.js variant) runs entirely against this one public call.

The heatmap endpoint

A single GET request returns a full price/time liquidation matrix for a symbol:

bash
GET https://api.smartmoneyapi.com/v1/liquidations/heatmap?symbol=BTC&window_minutes=240&price_buckets=50
ParamDefaultNotes
symbolBTCAny tracked symbol, uppercased, max 20 chars
window_minutes240Clamped to 5–1440 (5 min to 24h)
price_buckets50Clamped to 5–100 rows

The response carries the full matrix plus pre-computed cluster and totals summaries so you don’t have to reduce the grid yourself:

json
{ "symbol": "BTC", "window_minutes": 240, "price_buckets": 50, "time_bucket_minutes": 5, "price_min": 60123.45, "price_max": 61890.12, "price_bucket_size": 35.34, "price_levels": [60140.1, 60175.5, "..."], "time_buckets": [1753290000000, "..."], "matrix": [[0.0, 1200.5, "..."], "..."], "long_matrix": [["..."], "..."], "short_matrix": [["..."], "..."], "clusters": [ {"price": 60800.2, "notional": 812340.5, "long_notional": 700000.0, "short_notional": 112340.5, "count": 14, "dominant_side": "long"} ], "by_side": {"long": 5500000.0, "short": 2100000.0}, "totals": {"long_liq_notional": 5500000.0, "short_liq_notional": 2100000.0, "total_notional": 7600000.0, "count": 340}, "exchanges": {"binance": 210, "okx": 55, "bybit": 40, "bitget": 20, "bitmex": 15}, "generated_at": 1753300000, "public": true }
These are real executed forced-liquidation events streamed live off 5 exchange WebSocket feeds — never an estimate or a projection. If a symbol has been quiet, the matrix legitimately comes back sparse or all-zero, and the response includes a note field explaining why (stream warming up, or genuinely no liquidations in that window). Treat an empty matrix as a true reading of a calm market, not a bug.

Python: fetch + render with matplotlib

This script fetches the BTC heatmap for the last 4 hours across 50 price buckets, plots the matrix as a heatmap with pcolormesh, and labels the three largest liquidation clusters directly on the chart.

python
import requests import matplotlib.pyplot as plt from datetime import datetime, timezone BASE_URL = "https://api.smartmoneyapi.com" def fetch_heatmap(symbol="BTC", window_minutes=240, price_buckets=50): """Public endpoint -- no API key required.""" resp = requests.get( f"{BASE_URL}/v1/liquidations/heatmap", params={ "symbol": symbol, "window_minutes": window_minutes, "price_buckets": price_buckets, }, timeout=15, ) resp.raise_for_status() return resp.json() def render_heatmap(data, out_path="liquidation_heatmap.png"): totals = data.get("totals", {}) if totals.get("count", 0) == 0: # Expected state for a quiet symbol/window, not an error. print("No liquidation events in this window:", data.get("note", "stream may be warming up")) return matrix = data["matrix"] # price_buckets rows x time-bucket cols price_levels = data["price_levels"] # ascending, len == price_buckets time_buckets = [ datetime.fromtimestamp(ts / 1000, tz=timezone.utc) for ts in data["time_buckets"] ] fig, ax = plt.subplots(figsize=(12, 7)) mesh = ax.pcolormesh(time_buckets, price_levels, matrix, cmap="inferno", shading="auto") fig.colorbar(mesh, ax=ax, label="Liquidated notional (USD)") # Annotate the 3 largest liquidation clusters (already sorted desc by notional) for cluster in data.get("clusters", [])[:3]: ax.axhline(cluster["price"], color="cyan", linewidth=0.6, alpha=0.5) ax.text( time_buckets[-1], cluster["price"], f" ${cluster['notional']:,.0f} ({cluster['dominant_side']})", color="cyan", fontsize=8, va="center", ) ax.set_title(f"{data['symbol']} liquidations -- last {data['window_minutes']}m") ax.set_xlabel("Time (UTC)") ax.set_ylabel("Price (USD)") fig.autofmt_xdate() plt.tight_layout() plt.savefig(out_path, dpi=150) print(f"Saved {out_path} ({totals['count']} liquidations, ${totals['total_notional']:,.0f} total)") if __name__ == "__main__": data = fetch_heatmap(symbol="BTC", window_minutes=240, price_buckets=50) render_heatmap(data)

What this does

Skip the ingestion pipeline entirely

This same call works for any tracked symbol — swap `symbol=BTC` for `symbol=ETH`, `symbol=SOL`, or any of the 229 symbols the API tracks. Get a free key to raise your daily call cap beyond the public rate limit.

Get a free API key →

JavaScript: render clusters with Chart.js

A price × time matrix isn’t a native Chart.js chart type, so for a browser dashboard the simplest honest option is to plot the clusters array — the top 25 price levels by liquidated notional — as a horizontal bar chart, colored by which side (long or short) dominated at that level. This example loads the site’s own self-hosted Chart.js build rather than a CDN:

html
<script src="/vendor/chart.umd.min.js"></script> <canvas id="liq-chart" width="800" height="480"></canvas> <script> async function renderLiquidationClusters(symbol = "BTC") { const url = new URL("https://api.smartmoneyapi.com/v1/liquidations/heatmap"); url.searchParams.set("symbol", symbol); url.searchParams.set("window_minutes", "240"); url.searchParams.set("price_buckets", "50"); const res = await fetch(url); // public endpoint, no headers needed const data = await res.json(); if (!data.totals || data.totals.count === 0) { console.log("No liquidation events yet:", data.note || "quiet market"); return; } // A price x time matrix isn't a native Chart.js type, so we plot the // top price-level clusters instead -- a legitimate, simple visual that // needs no extra plugin. const clusters = data.clusters.slice(0, 15).sort((a, b) => a.price - b.price); new Chart(document.getElementById("liq-chart"), { type: "bar", data: { labels: clusters.map(c => "$" + c.price.toLocaleString()), datasets: [{ label: "Liquidated notional (USD)", data: clusters.map(c => c.notional), backgroundColor: clusters.map(c => c.dominant_side === "long" ? "#22c55e" : "#ef4444"), }], }, options: { indexAxis: "y", plugins: { legend: { display: false }, title: { display: true, text: symbol + " liquidation clusters" } }, scales: { x: { title: { display: true, text: "Notional (USD)" } } }, }, }); } renderLiquidationClusters("BTC"); </script>

Expected output

On an active symbol, the matplotlib script saves a PNG heatmap with warmer colors where more liquidation notional cleared, plus cyan marker lines at the top 3 clusters. On a quiet symbol or a short window, expect a mostly dark image or a printed message that no events occurred — both are correct outputs, not failures. The Chart.js variant renders a ranked bar list of price levels, green bars where longs got liquidated and red where shorts did.

What to build next

Once the basic heatmap is working, natural next steps include:

None of this requires standing up your own exchange connections, reconciling five liquidation-event schemas, or babysitting reconnect logic on five separate sockets — that infrastructure work is already handled upstream of this one endpoint.

See the full API documentation for every other endpoint, or browse the rest of the code playbooks hub for more integration examples.

Build on top of live liquidation data

This endpoint is public, but a free key unlocks a higher daily call cap and access to every other data module — derivatives, on-chain, whale flow, and more — from the same base URL.

Create a free account
Start free — 200 calls/day, no card

Get live whale flow, funding, open interest and on-chain data across 3 exchanges from one API. Free tier, no credit card, upgrade any time.

Start free →
Try the live API console → (no account needed)