Code Playbook · Python

Pre-Trade Liquidation Risk Check in Python

Your liquidation price is a number you can compute exactly. What that number means depends on something you cannot compute: how much forced flow lives between it and the current price. This playbook computes the first, pulls the second from a public endpoint, and puts them side by side before you open the position.

The problem: an exact number with an unclear meaning

Liquidation happens when the equity backing a position falls to the maintenance margin requirement. That is a closed-form calculation and any calculator will give you the level in a second. What the level does not tell you is how the market behaves on the way there.

The reason it matters is that liquidation levels are not evenly spread. Entry prices cluster, because traders enter on the same breakouts and the same supports. Leverage clusters harder still, because exchange interfaces ship preset buttons and far more positions open at exactly 10×, 20× or 25× than at 13×. Multiply a clustered entry distribution by a clustered leverage distribution and liquidation levels pile up in narrow bands. When price reaches a band, every position in it is force-closed in the same direction at the same instant, each close pushing price further toward the next band.

So the useful pre-trade question is not just “where is my liquidation price” but “how much forced flow sits between here and there, and is my level inside a zone that has already been proving dense today?” The first half is arithmetic. The second half needs data.

Prerequisites

Step 1: compute the liquidation level

For an isolated-margin position, ignoring fees and accrued funding:

text
long: liq = entry * (1 - 1/L + MMR) short: liq = entry * (1 + 1/L - MMR)

The 1/L term is the whole story: the distance from entry to liquidation is approximately the inverse of leverage, before the maintenance buffer. Fees and funding debits are taken out of margin over time, which drags the level slightly closer to you the longer you hold — it is not a static number on a multi-day position. If you want to check a single position by hand rather than in code, the liquidation calculator takes the same three inputs, and the companion guide on liquidation price versus cascade risk covers the mark-price and cross-margin cases the simplified formula skips.

Liquidation triggers on the venue’s mark price, which is built from an external spot index, not on the last trade on that venue’s own book. A wick on your chart may not liquidate you, and the mark can reach your level without a matching candle. Compare any computed level against the mark feed, not the chart.

Step 2: pull real executed liquidations

One GET returns a price × time matrix of forced-liquidation events with pre-computed price-level clusters, aggregated from live WebSocket streams on Binance, OKX, Bybit, Bitget and BitMEX:

bash
GET https://api.smartmoneyapi.com/v1/liquidations/heatmap?symbol=BTC&window_minutes=240&price_buckets=50
FieldTypeNotes
clustersarrayPrice levels ranked by liquidated notional, with dominant_side
totals.countintNumber of executed events in the window — check before concluding anything
by_sideobjectLong vs short notional force-closed
price_min / price_maxfloatRange the window actually covered
exchangesobjectEvent count per venue, so you can see the mix

These are executed forced-liquidation events, not modelled “liquidation levels”. That distinction matters and it is covered in full below.

The full script

python
import requests BASE = "https://api.smartmoneyapi.com" TIMEOUT = 20 def liquidation_price(entry, leverage, mmr, side): """Isolated-margin liquidation level, before fees and accrued funding.""" if side == "long": return entry * (1 - 1 / leverage + mmr) return entry * (1 + 1 / leverage - mmr) def fetch_liquidations(symbol="BTC", window_minutes=240, price_buckets=50): """Public endpoint -- no X-API-Key header required.""" r = requests.get( f"{BASE}/v1/liquidations/heatmap", params={"symbol": symbol, "window_minutes": window_minutes, "price_buckets": price_buckets}, timeout=TIMEOUT, ) r.raise_for_status() return r.json() def check(symbol, entry, leverage, mmr, side, window_minutes=240, band_pct=0.5): liq = liquidation_price(entry, leverage, mmr, side) data = fetch_liquidations(symbol, window_minutes) totals = data.get("totals") or {} if not totals.get("count"): # A quiet window legitimately returns nothing. That is an answer, # not a failure -- do not treat it as "safe", just as "unmeasured". print(f"{symbol}: no executed liquidations in the last " f"{window_minutes}m ({data.get('note', 'quiet market')})") print(f" your liquidation level: {liq:,.2f}") return clusters = data.get("clusters") or [] lo, hi = data.get("price_min"), data.get("price_max") # Notional that cleared within band_pct% of your own liquidation level. band = liq * band_pct / 100 near = [c for c in clusters if abs(c["price"] - liq) <= band] near_notional = sum(c["notional"] for c in near) # Notional that cleared between the current entry and your level, i.e. # the ground price would have to cross to reach you. between = [c for c in clusters if (liq <= c["price"] <= entry) or (entry <= c["price"] <= liq)] between_notional = sum(c["notional"] for c in between) total = totals.get("total_notional", 0.0) distance_pct = abs(entry - liq) / entry * 100 print(f"{symbol} {side} {leverage}x") print(f" entry : {entry:,.2f}") print(f" liquidation level : {liq:,.2f} ({distance_pct:.2f}% away)") print(f" window observed : {lo:,.2f} - {hi:,.2f} " f"({totals['count']} events, ${total:,.0f} notional)") print(f" longs force-sold : ${data['by_side']['long']:,.0f}") print(f" shorts force-bought: ${data['by_side']['short']:,.0f}") print(f" cleared within {band_pct}% of your level : ${near_notional:,.0f}") print(f" cleared between entry and your level: ${between_notional:,.0f}") if lo is not None and hi is not None and not (lo <= liq <= hi): print(" -> your level is OUTSIDE the range this window covered; " "the data says nothing about it either way") elif near_notional > 0: print(" -> your level sits inside ground where forced closures " "already cleared today; widen the buffer or cut leverage") if __name__ == "__main__": # MMR is venue- and tier-specific: read it off your exchange's contract specs. check(symbol="BTC", entry=79_500, leverage=20, mmr=0.005, side="long")

Expected output

Run against a live four-hour BTC window, the script prints something like this (figures from an actual response captured on 24 August 2026 — a historical observation, not a forecast, and yours will differ):

text
BTC long 20x entry : 79,500.00 liquidation level : 75,922.50 (4.50% away) window observed : 77,521.40 - 80,903.50 (2539 events, $42,819,707 notional) longs force-sold : $15,558,040 shorts force-bought: $27,261,668 cleared within 0.5% of your level : $0 cleared between entry and your level: $15,220,269 -> your level is OUTSIDE the range this window covered; the data says nothing about it either way

Three things in that output are worth reading carefully. The window covered $42.8m of executed liquidations across 2,539 events, and they were not spread evenly — in that response the densest single price level carried about $4.78m against a median level of roughly $1.23m, so one level held close to four times the typical one. Shorts were force-bought about $27.3m against $15.6m of longs force-sold, so the pressure in that window ran against shorts. And the 20× liquidation level landed below the bottom of the observed range, which is exactly the case the script refuses to interpret: no events there does not mean it is safe ground, it means this window contains no information about it.

The contrast with a higher leverage on the identical data is the point of running this before you choose a preset. At 50× the same entry gives a level of 78,307.50, only 1.50% away, which lands inside the observed range with about $2.29m of executed liquidations having cleared within half a percent of it — and the script switches to the other branch and says so. Same market, same entry, one button.

What this data cannot tell you

This is the section that decides whether the script is useful or harmful, so it is worth more than a footnote.

The legitimate use is narrow and worth stating in one sentence: it tells you whether the level you are about to accept sits in ground that has recently been dense with forced flow, which is a reason to widen the buffer or cut leverage — never a reason to enter.

What to build next

See the full API documentation for the rest of the response schema, or browse the code playbooks hub for more integration examples.

Same call, every tracked symbol

This endpoint is public and works for any tracked symbol. A free key raises your daily call cap and opens the rest of the data modules — derivatives, on-chain and whale flow — 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)