Hyperliquid query examples
Working ClickHouse SQL against the Supanode Hyperliquid Historical Data Indexer: trader leaderboards, maker/taker split, liquidations, TWAP execution quality, builder-code flow, and nanosecond block timing.
// updated 2026-08-22
Queries you can paste, each written against the real columns in the Table reference.
Everything starts from raw_node_fills_by_block. One row per fill, partitioned by utc_fill_dt. Filter on that date column in PREWHERE before anything else - the table is 3.13 billion rows and 225 GB.
Amounts are already in USD. closed_pnl, fee, builder_fee and priority_gas are Float64 in USD - no base-unit division. Notional is price * size.
Check the enum values first
side and fill_type are plain strings. Confirm what they actually contain before you hard-code a filter:
SELECT side, fill_type, count() AS fills
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt = today()
GROUP BY side, fill_type
ORDER BY fills DESC
Largest fills in the last 24 hours
SELECT
utc_fill_dttm,
fill_id,
wallet_address,
coin,
side,
price,
size,
price * size AS notional,
closed_pnl,
fee,
liquidation_user
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt >= today() - 1
ORDER BY notional DESC
LIMIT 100
Volume by market
SELECT
coin,
count() AS fills,
sum(price * size) AS notional_volume,
uniq(wallet_address) AS unique_traders,
avg(price * size) AS avg_fill_notional
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt >= today() - 1
GROUP BY coin
ORDER BY notional_volume DESC
LIMIT 50
Maker versus taker flow
order_crossed_spread_flg is the taker flag - it is set when the order crossed the spread rather than resting on the book.
SELECT
coin,
countIf(order_crossed_spread_flg) AS taker_fills,
countIf(NOT order_crossed_spread_flg) AS maker_fills,
sumIf(price * size, order_crossed_spread_flg) AS taker_notional,
sumIf(price * size, NOT order_crossed_spread_flg) AS maker_notional,
round(100 * countIf(order_crossed_spread_flg) / count(), 2) AS taker_pct
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt = today()
GROUP BY coin
ORDER BY taker_notional DESC
LIMIT 25
Trader PnL leaderboard
SELECT
wallet_address,
count() AS fills,
countIf(closed_pnl != 0) AS closing_fills,
sum(closed_pnl) AS realized_pnl,
sum(price * size) AS notional_volume,
sum(fee) AS fees_paid,
countIf(closed_pnl > 0) AS wins,
countIf(closed_pnl < 0) AS losses,
round(100 * countIf(closed_pnl > 0) / nullIf(countIf(closed_pnl != 0), 0), 2) AS win_rate_pct,
uniq(coin) AS markets
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt >= today() - 30
GROUP BY wallet_address
HAVING closing_fills >= 50
ORDER BY realized_pnl DESC
LIMIT 100
The same leaderboard, precomputed. view_perpetual_wallet already holds total_pnl, total_volume, win_count, cnt_trade_days and ROI quantiles per wallet - see the query further down. Use it when you want the answer, and the raw table when you want to define the window yourself.
Liquidations
A liquidation carries a non-null liquidation_user, together with the mark price and the method used.
SELECT
utc_fill_dttm,
coin,
liquidation_user,
liquidation_method,
liquidation_mark_px,
side,
price,
size,
price * size AS notional,
closed_pnl
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt >= today() - 1
WHERE liquidation_user IS NOT NULL
ORDER BY notional DESC
LIMIT 100
Rolled up by market over a week:
SELECT
coin,
count() AS liquidation_fills,
uniq(liquidation_user) AS wallets_liquidated,
sum(price * size) AS liquidated_notional,
sum(closed_pnl) AS realized_pnl
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt >= today() - 7
WHERE liquidation_user IS NOT NULL
GROUP BY coin
ORDER BY liquidated_notional DESC
TWAP execution quality
Fills belonging to the same TWAP share a twap_id. Grouping on it reconstructs the whole execution.
SELECT
twap_id,
wallet_address,
coin,
side,
count() AS fills,
sum(size) AS total_size,
sum(price * size) / sum(size) AS vwap,
min(utc_fill_dttm) AS started,
max(utc_fill_dttm) AS ended,
dateDiff('minute', min(utc_fill_dttm), max(utc_fill_dttm)) AS duration_min
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt >= today() - 1
WHERE twap_id IS NOT NULL
GROUP BY twap_id, wallet_address, coin, side
ORDER BY total_size DESC
LIMIT 50
Compare a TWAP's own VWAP against the market VWAP over the same window to score the execution:
WITH twaps AS (
SELECT
twap_id,
coin,
sum(price * size) / sum(size) AS twap_vwap,
sum(size) AS twap_size,
min(utc_fill_dttm) AS t0,
max(utc_fill_dttm) AS t1
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt = today()
WHERE twap_id IS NOT NULL
GROUP BY twap_id, coin
HAVING count() >= 5
)
SELECT
t.twap_id,
t.coin,
t.twap_size,
t.twap_vwap,
m.market_vwap,
round(1e4 * (t.twap_vwap - m.market_vwap) / m.market_vwap, 2) AS slippage_bps
FROM twaps AS t
INNER JOIN
(
SELECT coin, sum(price * size) / sum(size) AS market_vwap
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt = today()
GROUP BY coin
) AS m
ON t.coin = m.coin
ORDER BY abs(slippage_bps) DESC
LIMIT 50
Builder-code flow
builder identifies the front-end or API that routed the order, and builder_fee is what it earned.
SELECT
builder,
count() AS fills,
uniq(wallet_address) AS wallets,
sum(price * size) AS routed_notional,
sum(builder_fee) AS builder_revenue
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt >= today() - 7
WHERE builder IS NOT NULL
GROUP BY builder
ORDER BY routed_notional DESC
LIMIT 25
Priority gas spend
SELECT
wallet_address,
count() AS fills,
sum(priority_gas) AS priority_gas_usdc,
max(priority_gas) AS biggest_single,
sum(price * size) AS notional
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt >= today() - 7
WHERE priority_gas IS NOT NULL AND priority_gas > 0
GROUP BY wallet_address
ORDER BY priority_gas_usdc DESC
LIMIT 25
Nanosecond block timing
utc_block_dttm is DateTime64(9) - nanosecond resolution on the block, while utc_fill_dttm is millisecond resolution on the fill. Use the block timestamp with block_tx_idx for ordering work.
SELECT
block_id,
utc_block_dttm,
count() AS fills_in_block,
uniq(coin) AS markets,
min(block_tx_idx) AS first_tx_idx,
max(block_tx_idx) AS last_tx_idx,
sum(price * size) AS block_notional
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt = today()
GROUP BY block_id, utc_block_dttm
ORDER BY fills_in_block DESC
LIMIT 25
Time between consecutive blocks, in milliseconds:
SELECT
block_id,
utc_block_dttm,
round((toUnixTimestamp64Nano(utc_block_dttm)
- toUnixTimestamp64Nano(any(utc_block_dttm) OVER (ORDER BY block_id ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING))) / 1e6, 3) AS gap_ms
FROM
(
SELECT DISTINCT block_id, utc_block_dttm
FROM hyperliquid.raw_node_fills_by_block
PREWHERE utc_fill_dt = today()
ORDER BY block_id
LIMIT 5000
)
ORDER BY block_id
Precomputed wallet views
SELECT
wallet_address,
total_pnl,
total_volume,
cnt_unique_orders,
win_count,
total_count,
round(100 * win_count / nullIf(total_count, 0), 2) AS win_rate_pct,
cnt_unique_coins,
cnt_trade_days,
last_utc_order_dt
FROM hyperliquid.view_perpetual_wallet
WHERE total_count > 100
ORDER BY total_pnl DESC
LIMIT 100
Open positions right now, by market:
SELECT
coin,
countIf(last_position_size > 0) AS longs,
countIf(last_position_size < 0) AS shorts,
sumIf(last_position_size, last_position_size > 0) AS long_size,
sumIf(last_position_size, last_position_size < 0) AS short_size
FROM hyperliquid.view_wallet_position
WHERE last_position_size != 0
GROUP BY coin
ORDER BY long_size + abs(short_size) DESC
Orders instead of fills
agg_fulfilled_order collapses every fill of an order into one row. Its aggregate columns must be read through sum().
SELECT
wallet_address,
coin,
count() AS orders,
sum(size) AS filled_size,
sum(volume) AS filled_volume_usd,
sum(closed_pnl) AS realized_pnl
FROM hyperliquid.agg_fulfilled_order
PREWHERE utc_first_fill_dt >= today() - 30
GROUP BY wallet_address, coin
ORDER BY filled_volume_usd DESC
LIMIT 50
Performance notes
- Always filter
utc_fill_dtinPREWHERE. It is the partition key; everything else is a full scan. - Add a
coinfilter whenever the question is about one market - it cuts the scan by an order of magnitude. - Aggregate before joining. The two-step TWAP query above joins two aggregates, not two raw scans.
agg_fulfilled_orderkeeps 90 days. Longer studies run onraw_node_fills_by_block.
Check the plan before a heavy scan:
EXPLAIN indexes = 1
SELECT count() FROM hyperliquid.raw_node_fills_by_block WHERE utc_fill_dt = today()
Get started
Provision access or start a trial: @supanode_tgs.