"""Backtest liquidation clusters: how often did the price reach the largest liquidation cluster near it within the next hours? Python 3.9+, standard library only. Reads the heatmap history (GET /heatmap/{coin}) and the candles (GET /candles/{coin}) of the same window and checks every hourly column. export BLOCKMETRIC_API_KEY=bm_live_... # free key: every coin, the last 24 hours (DAYS=1); Pro: every coin, 1 year export BLOCKMETRIC_BASE_URL=https:///api/v1 python backtest.py Options: COIN (BTC), DAYS (14, at most 30 with tf=1h), MIN_USD (5000000), MAX_DISTANCE (0.03), HORIZON_HOURS (24), CSV (file name for every test, optional). Descriptive statistics over a short window, not a trading signal. """ import csv import json import os import urllib.error import urllib.parse import urllib.request from datetime import datetime, timedelta, timezone BASE = os.environ.get("BLOCKMETRIC_BASE_URL", "").rstrip("/") KEY = os.environ.get("BLOCKMETRIC_API_KEY") COIN = os.environ.get("COIN", "BTC") DAYS = min(30, int(os.environ.get("DAYS", "14"))) MIN_USD = float(os.environ.get("MIN_USD", "5000000")) MAX_DISTANCE = float(os.environ.get("MAX_DISTANCE", "0.03")) HORIZON = int(os.environ.get("HORIZON_HOURS", "24")) def api(path, **params): query = urllib.parse.urlencode({k: v for k, v in params.items() if v not in (None, "")}) headers = {"Authorization": f"Bearer {KEY}"} if KEY else {"X-Blockmetric-Access": "public"} try: with urllib.request.urlopen(urllib.request.Request(f"{BASE}{path}?{query}", headers=headers), timeout=60) as res: return json.load(res)["data"] except urllib.error.HTTPError as e: err = json.load(e).get("error", {}) raise SystemExit(f"{e.code} {err.get('code')}: {err.get('message')}") def iso(dt): return dt.strftime("%Y-%m-%dT%H:%M:%S.000Z") def nearest_big(h, col): """The largest cluster per side of one column within MAX_DISTANCE of the mark.""" mark = h["mark"][col] best = {} for row, price in enumerate(h["prices"]): dist = (price - mark) / mark if abs(dist) > MAX_DISTANCE: continue side = "long" if price < mark else "short" # longs liquidate below the mark, shorts above it usd = (h["longCells"] if side == "long" else h["shortCells"])[col][row] if usd >= MIN_USD and (side not in best or usd > best[side]["usd"]): best[side] = {"side": side, "price": price, "usd": usd, "distance": dist} return list(best.values()) def main(): h = api(f"/heatmap/{urllib.parse.quote(COIN)}", tf="1h", hours=DAYS * 24) c = api(f"/candles/{urllib.parse.quote(COIN)}", tf="1h", limit=5000, **{"from": h["times"][0]}) by_hour = {k["t"]: k for k in c["candles"]} now = datetime.now(timezone.utc) tests = [] for col, t in enumerate(h["times"]): if (h.get("hasSnapshot") or [True] * len(h["times"]))[col] is False or not h["mark"][col]: continue start = datetime.fromisoformat(t.replace("Z", "+00:00")) if start + timedelta(hours=HORIZON + 1) > now: # the horizon has not passed yet continue for cl in nearest_big(h, col): hit_after = None for i in range(1, HORIZON + 1): k = by_hour.get(iso(start + timedelta(hours=i))) if k and (k["low"] <= cl["price"] if cl["side"] == "long" else k["high"] >= cl["price"]): hit_after = i break tests.append({**cl, "time": t, "hit_after": hit_after}) print(f"{COIN}: clusters of at least ${MIN_USD / 1e6:g}M within {MAX_DISTANCE * 100:g} % of the mark, {len(h['times'])} hourly columns since {h['times'][0][:10]}") print("side tested reached hit rate median hours to reach") for side in ("long", "short"): s = [x for x in tests if x["side"] == side] hits = sorted(x["hit_after"] for x in s if x["hit_after"] is not None) rate = f"{len(hits) / len(s) * 100:.0f} %" if s else "-" median = str(hits[len(hits) // 2]) if hits else "-" print(f"{side + 's':<7} {len(s):>6} {len(hits):>9} {rate:>10} {median:>6}") print(f"Horizon {HORIZON} h. Longs = clusters of long liquidations below the mark, shorts = above it.") if os.environ.get("CSV"): with open(os.environ["CSV"], "w", newline="") as f: w = csv.writer(f) w.writerow(["time", "side", "price", "usd", "distance", "hit_after_hours"]) for x in tests: w.writerow([x["time"], x["side"], x["price"], x["usd"], f"{x['distance']:.5f}", x["hit_after"] or ""]) print(f"Wrote {len(tests)} tests to {os.environ['CSV']}") if __name__ == "__main__": main()