Data access · 9 min read
Hyperliquid perps data: fills, positions, and realized PnL
Hyperliquid settles trades on its own L1, and the record of every execution is its fills feed. This guide is about reading that feed: what a single fill holds, how two fills make a trade, how fees and realized PnL fall out of the data, and how the same venue lists crypto, equities, and permissionless markets side by side. Every field below comes from a real fill query against SQD's Portal.
1. Fills: the exchange tape
Hyperliquid is a perpetual-futures exchange that runs its order book and matching on a purpose-built L1 rather than as smart contracts on a general chain. That design choice changes where the data lives. On a typical EVM DEX a trade leaves a Swap event in the logs; on Hyperliquid the unit of record is a fill, one side of a matched order.
When a resting order meets an incoming one, the exchange writes a fill for each party. A single trade therefore produces two fills that share a trade id: one for the maker who was already in the book, one for the taker who crossed the spread to meet them. The stream of all fills is the exchange tape. Index it and you have every execution on the venue, not a sampled snapshot, which is what volume, open interest changes, fee revenue, and trader PnL are all reconstructed from.
2. The fill record
SQD's Portal exposes the trade tape as the hyperliquid-fills dataset. The query uses the hyperliquidFills type and a fills filter, and you choose the fields you want back. This one pulls HYPE fills over a fixed block range with the fields needed to read direction, fees, and PnL:
POST https://portal.sqd.dev/datasets/hyperliquid-fills/stream
Accept: application/x-ndjson
{
"type": "hyperliquidFills",
"fromBlock": 1036000000,
"toBlock": 1036001000,
"fills": [{ "coin": ["HYPE"] }],
"fields": {
"fill": {
"user": true, "coin": true, "px": true, "sz": true,
"side": true, "dir": true, "startPosition": true,
"closedPnl": true, "fee": true, "feeToken": true,
"crossed": true, "hash": true, "tid": true
},
"block": { "number": true, "timestamp": true }
}
} One real fill that comes back, the maker side of a HYPE trade at block 1,036,000,998:
{
"user": "0xc029043cd00b80363130fa058818459a521842a1",
"coin": "HYPE",
"px": 65.621,
"sz": 45.7,
"side": "B",
"dir": "Close Short",
"startPosition": -63.14,
"closedPnl": 6.13751,
"crossed": false,
"fee": -0.089966,
"feeToken": "USDC"
}
Read field by field: px and sz are price and size, so this is 45.7 HYPE at 65.621, roughly a 2,999 USD notional. side is B for a buy (bid) or A for a sell (ask). dir is the position intent, here Close Short, and pairs with startPosition (the trader was short 63.14 HYPE before this fill). The exact opposite fill, the taker who opened a short into this trade, carries the same tid and a crossed: true. The two together are the whole trade.
3. Fees and realized PnL
Two fields make the fill tape enough to compute trader performance without any external data. closedPnl is the realized profit or loss, and it is non-zero only on closing trades: a Close Long or Close Short books a result, while an open records zero because nothing is realized yet. fee is what the trade cost, in feeToken (USDC here).
The fee sign carries information. In the fill above it is -0.089966, a negative number, because crossed: false made this the resting maker order and Hyperliquid pays makers a rebate rather than charging them. The taker on the other side of the same trade has crossed: true and a positive fee of 0.629764. So one execution shows both halves of the fee model in the data: liquidity taken and paid for, liquidity provided and rewarded.
Net realized PnL for a trader over any window is then a single rollup over their fills:
net_pnl = sum(closedPnl) - sum(fee)
Fees subtract because a positive fee is a cost; a maker rebate, being negative, adds back. Filter the same query by user instead of coin and you have one address's full trade history; aggregate across all fills in a window and you have market-wide volume, fee revenue, and the long-versus-short balance of new positions, read from dir. None of it needs a price oracle or an account snapshot, because the realized number is already in each closing fill.
4. Crypto, TradFi, and HIP-3 markets
Hyperliquid does not list only crypto. The same fills feed carries traditional-finance markets and permissionless markets that anyone can deploy through HIP-3, and they are mixed together in one stream. The thing that tells them apart is the coin name, which follows a convention worth encoding once:
- Plain tickers are crypto:
BTC,ETH,HYPE,SOL. - An
xyz:orcash:prefix marks a TradFi market:xyz:SP500,xyz:TSLA,cash:GOLD. - An
@prefix marks a HIP-3 permissionless listing, for example@107.
There is one trap. A handful of plain tickers with no prefix are TradFi equities, not crypto: HOOD, GOOGL, TSM, and a short list of others. A classifier that keys only on the prefix will file them under crypto. The reliable approach is prefix matching plus an explicit allowlist for the bare-ticker equities:
const BARE_TRADFI = new Set(['HOOD','GOOGL','TSM','NATGAS','PLATINUM']);
function classify(coin) {
if (coin.startsWith('@')) return 'hip3';
if (coin.startsWith('xyz:') || coin.startsWith('cash:')) return 'tradfi';
if (BARE_TRADFI.has(coin)) return 'tradfi';
return 'crypto';
}
With that one function applied to the coin field, the same fill tape splits cleanly into crypto volume, equity and commodity volume, and permissionless-market volume, which is the split most analyses of the venue actually want.
5. Why the trade tape is hard to get
Hyperliquid publishes its own info API, and it returns fills. The catch is shape: userFills is keyed by a single address and is rate-limited, which fits a trader checking their own account but not a question about the whole market. There is no public endpoint that hands you every fill across the exchange over an arbitrary historical range, so building that view yourself means paginating per address against a rate limit and stitching the pieces together.
The hyperliquid-fills dataset is that missing view: the complete fill stream as a single queryable source, from block 750,000,000 to the latest block, filtered by coin, user, or side in one request. That is the part that is awkward to assemble elsewhere and direct here. For the EVM side of Hyperliquid, the hyperliquid-mainnet (HyperEVM) dataset is separate and uses the standard evm query type; the fills feed and the smart-contract chain are indexed as two distinct sources.
6. Hyperliquid data with SQD
The query above is the actual interface. SQD provides the fills as decoded, typed records from the Portal, with the fee, direction, position, and PnL fields in every record, so the analytics on top are rollups rather than reconstructions. For ad-hoc questions the stream endpoint is enough; for a product you would not poll it by hand.
The Pipes SDK streams the same fills into your own store with a addFill source, where you keep a running table of trades, positions, and per-trader PnL that updates as new blocks arrive. The fills dataset sits alongside EVM, Solana, and Bitcoin sources under one query model, so a dashboard that already tracks DEX swaps can add Hyperliquid perps without a second pipeline. For how this feeds a trading product, see the DeFi and trading solution page.
Frequently asked questions
What is a fill on Hyperliquid?
How do I get Hyperliquid fills over a historical range?
How is realized PnL recorded in Hyperliquid fills?
How do you tell a maker fill from a taker fill?
Does Hyperliquid list stocks and other TradFi markets?
What is the difference between hyperliquid-fills and HyperEVM?
Related guides
Building trading or DeFi tooling?
See how fills, swaps, and liquidations across every chain feed trading products on the DeFi and trading solution page.