Count liquidations around the biggest move
Find the largest BTC hourly candle of a day, then total the long and short liquidations that landed inside that hour.
An event study in two calls: /ohlcv gives the 24 hourly BTC candles of
2026-08-17, and /liquidations gives the forced closes inside the
hour of the largest move.
export VIKE_API_KEY=...
python hl-liquidations-around-move.py # real run: needs the key
python hl-liquidations-around-move.py --dry-run # prints the requests it would make, calls nothingIt prints the hour, its move, and the count and dollar size of long and short liquidations.
"""Find the biggest BTC hourly move of one day, then total the liquidations that landed in that hour.
Run: VIKE_API_KEY=... python hl-liquidations-around-move.py (--dry-run lists the requests and makes none)
"""
import os
import sys
from datetime import datetime, timezone
from urllib.parse import urlencode
NAME = "hl-liquidations-around-move"
BASE = "https://data.vike.io"
DRY = "--dry-run" in sys.argv
def fail(why):
sys.exit(f"{NAME}: {why}")
def call(path, method="GET", **params):
url = BASE + path + ("?" + urlencode(params) if params else "")
if DRY:
print(method, url)
return None
key = os.environ.get("VIKE_API_KEY") or fail("set VIKE_API_KEY")
import requests
r = requests.request(method, url, headers={"X-API-Key": key}, timeout=60)
if r.status_code != 200:
fail(f"{method} {path} answered HTTP {r.status_code}: {r.text[:200]}")
return r.json()
def iso(ms):
return datetime.fromtimestamp(ms / 1000, timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def main():
# end is exclusive on /ohlcv: this is the 24 hourly bars of 2026-08-17.
day = call("/v1/hyperliquid/ohlcv", symbol="BTC", interval="1h", limit=24,
start="2026-08-17T00:00:00Z", end="2026-08-18T00:00:00Z")
move_ts = 1786924800000 # --dry-run has no response to read
if not DRY:
if not day["candles"]:
fail("no BTC candles for the day")
biggest = max(day["candles"], key=lambda c: abs(c["close"] / c["open"] - 1))
move_ts = biggest["ts"]
print(f"biggest hour: {iso(move_ts)} {biggest['close'] / biggest['open'] - 1:+.2%} ({biggest['open']:g} -> {biggest['close']:g})")
# /liquidations is newest-first, its bounds are whole seconds and its end is inclusive; 5000 is the most
# one page holds. The hour's own end is passed, and a fill stamped exactly on it is dropped below.
hour_end = move_ts + 3_600_000
liqs = call("/v1/hyperliquid/liquidations", coin="BTC", limit=5000, start=iso(move_ts), end=iso(hour_end))
if DRY:
return
rows = [r for r in liqs["liquidations"] if datetime.fromisoformat(r["liq_ts"]).timestamp() * 1000 < hour_end]
if not rows:
fail("no BTC liquidations in the move's hour")
if liqs["nextCursor"]:
print(" (a full page: the totals below cover the newest 5000 rows of the hour only)")
# `side` is the side of the position that was closed: "long" means longs were liquidated.
for side in ("long", "short"):
hit = [r for r in rows if r["side"] == side]
print(f" {side:5} liquidated: {len(hit):5} fills ${sum(r['size_usd'] for r in hit):,.0f}")
if __name__ == "__main__":
try:
main()
except Exception as e: # a shape the recipe does not expect fails by name, not by traceback
fail(f"{type(e).__name__}: {e}")Details that decide whether the totals are right
sideis the position that was closed.longmeans longs were liquidated, which happens when price falls.- The bounds are whole seconds and
endis inclusive. The script asks for the hour's own end and drops a fill stamped exactly on it, so an hour does not count its neighbour's first instant. - Newest first, 5000 rows at most. A busy hour can fill a page. The script says so when
nextCursoris set; to total the whole hour, follow the cursor as shown in Paging large reads. - Candles are fills-derived and
endis exclusive. Withstartandendboth given,limit=24returns the oldest 24 bars of the window, which is the whole day here.
Cohort labels on old rows are not point-in-time
Each liquidation carries a pnl_cohort, with pnl_cohort_basis saying whether it is the label at that hour or
today's. This recipe does not read it. If you add a split by cohort, check the basis first: rows before
2026-04-07 always carry a current label.
Backtest cohort positioning without lookahead
Compare the smart cohort's hourly BTC bias with the next hour's return, using point-in-time labels so the cohort is the one that existed that hour.
Find a market and check its tape
Search Polymarket markets by slug, take the first hit's condition and token ids, and ask whether that outcome token has a recorded tape.