Skip to main content
hyperliquid.perp_accounts_daily is the account-level entry point to the Hyperliquid perpetuals dataset: it answers per-account questions without scanning the multi-billion-row leaf tables. Grain: one row per (trader, block_date), partitioned by block_date. Coverage is venue-wide — the first-party dex plus every HIP-3 builder-deployed dex (10 builder dexes, 498 markets as of 2026-08-21). Spot and HIP-4 outcome markets are out of scope. There is no market dimension here: for a per-coin breakdown of the same activity use hyperliquid.perp_trades or hyperliquid.perp_positions_hourly.

Table schema

The population is not traders only

A row exists if the account traded, held a position, or moved perp collateral that day. Holders far outnumber traders, so restricting the population to accounts that traded would miss most of them. markets_traded is 0 exactly when fills_count is 0, so either column separates the two groups.

Both legs, and why that is not a double count here

hyperliquid.perp_trades holds both the maker and the taker leg of every match, which is why a market-level SUM(notional_usd) there runs at roughly twice exchange-reported volume unless you filter is_taker. At account grain the same two legs are not a double count: each leg is one account’s own participation, so volume_usd is that account’s real traded notional. Only sums that cross accounts double count — use taker_volume_usd, which reproduces perp_market_metrics_daily.volume_usd when summed.

realized_pnl_usd vs net_pnl_usd

realized_pnl_usd is closed-position PnL, gross of fees and funding, and complete rather than partial because perp_trades carries both legs of every match. net_pnl_usd takes fees off and applies funding, which is the change in account equity that trading caused. Neither carries unrealised PnL: no source records an entry price, so an account sitting on an open position has a net_pnl_usd that lags its equity. Subtracting builder_fee_usd or deployer_fee_usd from either double counts dollars already in fee_usd.

Liquidation and ADL are opposite roles

liquidation_count is this account failing its own margin; adl_count is this account being profitable and deleveraged against someone who failed theirs. Both are keyed on fill_type, since liquidation_method and liquidated_trader are stamped on the match rather than on the role and counting by either doubles. Liquidation counts both legs, so unlike the market tables it includes the legs on which an account was force-closed as a maker — the bankrupt side of a backstop liquidation. ADL is always the taker leg, and is zero on most days because it is concentrated in market-wide stress events. Add the two column pairs for an account’s total forced flow. The market tables report the two combined instead, as forced_count / forced_volume_usd, counted once per match on the taker leg — so an account-level sum does not tie to that figure.

Collateral flows

Eight ledger_raw variants map onto the flow columns: deposit, withdraw, accountClassTransfer (split by direction into spot_to_perp_usd / perp_to_spot_usd), vaultDeposit, vaultWithdraw, and the three account-to-account variants that make up transfer_in_usd / transfer_out_usd. net_collateral_flow_usd is their signed sum: deposits, transfers in, spot-to-perp and vault withdrawals add; withdrawals, transfers out, perp-to-spot and vault deposits subtract. One ledger row names both sides of a transfer, so the same row credits the sender’s outflow and the recipient’s inflow. transfer_in_usd and transfer_out_usd therefore do not sum equal across the venue: a send leg is counted only when that side is a perp collateral pool, and spot-to-spot sends are not perp collateral at all. An account moving its own collateral between pools is kept gross — both legs land on its row and net to zero. Flows are account-wide, not per dex. HIP-3 dexes hold separate collateral pools and only the send variant names them, so at this grain you can see that an account moved collateral but not which pools it moved between.
ledger_raw’s decoded user column is NULL on deposit, withdraw, accountClassTransfer and vaultDeposit — that is, on bridge flows and every spot↔perp transfer. The model resolves the account from the event JSON, so those flows are present here. If your own ledger_raw query shows less inflow than this table, that trap is the reason.

NULL versus zero, and history floors

History floors differ by column group, and days before a source’s floor carry NULL for its columns rather than 0 — an absent source does not assert that nothing happened. Inside a covered range the distinction still holds: funding and flow columns are 0 when the source ran and the account simply had nothing, NULL on a day the source produced no rows at all, and open_positions_eod is 0 for an account that was flat at the day’s last round but NULL on a day with no round anywhere. net_pnl_usd is NULL on exactly the days funding is NULL, so it is never a net figure that quietly assumed funding was zero. realized_pnl_usd - fee_usd is not a substitute in that window: funding was charged, the source merely does not reach back that far.
SUM(net_pnl_usd) skips NULL days silently, so a window that starts before 2025-09-27 returns a total covering only part of it. Bound such windows at the funding floor, or count the NULL days alongside the sum.
The newest block_date is partial by construction, and its end-of-day columns stay NULL until the day’s last funding round clears ingestion. Filter block_date < current_date for complete rows.