Skip to main content
hyperliquid.perp_orderbook_1m is the order book table for the Hyperliquid perpetual futures venue. Grain: one row per (coin, interval_start), carrying the whole resting book on both sides as arrays of price levels. Top-of-book, spread, depth and slippage curves therefore all come off a single row, with no join and no self-join across levels. It covers both first-party perps (coin = BTC) and HIP-3 builder-deployed markets (coin = dex:SYMBOL, e.g. xyz:TSLA); Hyperliquid spot and HIP-4 outcome markets are filtered out.
Grain: one row per (coin, interval_start) · Depth: up to 100 price levels per side · History: from 2026-06-29 — a much later floor than the fills tables · Markets: 497 — 232 first-party, 265 HIP-3 · Refresh: a view over the live feed, so no build lag · Figures as of 2026-08-24

Table schema

A row is a boundary instant, not a bucket

interval_start labels the moment the book is as of, and the book comes from the last L1 block at or before it — block_time is never later than interval_start. So a row describes the book at that minute boundary; it says nothing about what the book did during the minute that follows, and there is no open/high/low/close aggregation involved. staleness_seconds measures exactly that offset: it equals the whole seconds from block_time to interval_start on every row. On 2026-08-20 it was 0 on 80.2% of rows, with a 95th percentile of 6 seconds, a 99th of 27, and a maximum of 59 — a fresh market is read essentially at the boundary, and a large value means the last block before the boundary was itself old, which for a quiet market is normal rather than a defect. Read it when you are aligning the book against another table’s timestamps to a sub-minute tolerance; ignore it otherwise. derived_at is not a per-row freshness clock. It tracks the minute closely while the feed runs in steady state, but backfilled history carries the timestamp of the batch that produced it, so comparing derived_at to interval_start measures ingestion history rather than data quality.

Reading bids and asks

Both arrays are already sorted best-first, so bids[1] is the best bid and asks[1] the best ask — no ORDER BY inside the array, and no window function to pick the top level. px and sz are VARCHAR, carrying the venue’s own decimal strings unrounded, so every arithmetic use needs an explicit cast:
Guard the CARDINALITY on both sides before indexing: a one-sided book produces an empty array rather than a null row, and bids[1] on an empty array raises rather than returning null. It is rare but real — 177 of the 454,797 rows on 2026-08-20 had no resting bid. For anything summed across levels, unnest instead of indexing. Depth within a distance of the mid, per minute:

Absent minutes are not gaps

The feed is event-driven, not a dense grid: a coin-minute is missing exactly when the book was unchanged from the snapshot before it. So forward-filling the previous row is exact rather than an approximation, and a missing minute is never a lost snapshot. Density splits sharply by how actively a market is quoted. On 2026-08-20, 263 of the 497 markets had all 1,440 minutes and the median market had a row for every minute, while at the 5th percentile a market had a single row for the whole day. Across all markets that day the table held 454,797 rows against 715,680 possible coin-minutes. Two consequences for queries. First, never read row counts per market as an activity metric without saying what they measure — they count book changes, not time covered. Second, to align the book with a fixed grid, or with a timestamp from another table, take the last row at or before your cutoff rather than matching a minute exactly:
The two-day window is deliberate: a market quoted rarely enough can have its last change on a previous day, so a one-day bound silently drops it.

Depth is capped at 100 levels per side

Each side holds at most 100 price levels, uniform across first-party and HIP-3 markets. The cap binds routinely rather than exceptionally — on 2026-08-20 it bound on 54.8% of rows, and the median row carried 98 bid levels — so a truncated book looks exactly like a complete one from inside this table. That matters whenever a query walks the book to a target size or distance. Depth measured near the mid is unaffected; a slippage estimate for a clip large enough to sweep past the hundredth level is bounded by the cap, not by the market. Check whether you reached the end of the data before concluding you reached the end of the book:
The upstream diff stream this table is built from carries up to 200 levels, so a question that genuinely needs the book past 100 levels has to go there instead.

coin is the only market identifier here

Unlike the other venue-wide tables, this one carries neither market_symbol nor perp_dex: coin is the raw venue string, readable on its own for both perp namespaces. Join perp_market_details on coin for the dex, asset class, size precision and leverage caps. Use a LEFT JOIN. The registry behind that dimension is a poller snapshot that trails a new listing, so a freshly listed market can appear in the book before it appears there — all 497 markets resolved as of 2026-08-20, but an inner join is not guaranteed to be lossless on the current day.

Bound block_date on every query

block_date is the only partition column, and this table is a view, so there is no block_month to fall back on — an unbounded query reads all 24.2M rows and every price level in them. Nothing prunes within a partition either: filtering coin still reads the full day.

Not in this table

Traded prices and sizes are in perp_trades; this table is resting liquidity, which is why a level can sit in the book for hours without a fill. Oracle, mark and mid prices, plus Hyperliquid’s own impact-notional quotes, are in perp_oracle_prices — prefer mid_price there over deriving a mid here when a first-party market is all you need, since it needs no cast and no cardinality guard. Order-level detail — individual resting orders, their owners, placements and cancellations — is not in this dataset at any grain; the levels here are aggregated, with n as the only surviving trace of how many orders composed them.