> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dune.com/llms.txt
> Use this file to discover all available pages before exploring further.

# hyperliquid.perp_trades

> One row per fill leg on Hyperliquid perpetual futures, covering both sides of every match across first-party and HIP-3 builder-deployed markets.

export const PremiumDatasetAccessCard = ({href = "https://dune.com/enterprise#contact-form", note = null}) => <Card title="Gated dataset" icon="lock" href={href}>
    Querying this dataset requires an entitlement on your workspace. See <a href="/data-catalog/overview#access-tiers-public-vs-gated-datasets">access tiers</a>, or contact the Dune team to enable access.
    {note && <><br /><br />{note}</>}
  </Card>;

`hyperliquid.perp_trades` is the fill table for Hyperliquid perpetual futures, venue-wide: first-party markets (`coin` = `BTC`) and HIP-3 builder-deployed markets (`coin` = `xyz:TSLA`) alike. Grain: one row per fill **leg** — both sides of every match — keyed on `(block_date, coin, trader, oid, tid)`, with `tid` shared by the two legs of a match. Spot and HIP-4 outcome markets are out of scope.

<PremiumDatasetAccessCard />

Fills are decoded from the full node fills stream rather than from taker order actions (`fills_raw`), which misses every engine-generated fill — TWAP child slices and liquidation executions — worth 5-30% of volume. History starts 2025-07-27 and covers every coin that has traded, at millions of fill legs per day.

## Table schema

| Column | Type | Description |
| - | - | - |
| `block_month` | `DATE` | Month of `block_date`, for month-grain grouping. Not the partition key |
| `block_date` | `DATE` | Partition key, and the only column that prunes |
| `block_time` | `TIMESTAMP(3) WITH TIME ZONE` | Block time of the fill |
| `block_number` | `BIGINT` | Block height |
| `event_index` | `INTEGER` | Position of the leg within the block; the two legs of a match sit at consecutive indices |
| `perp_dex` | `VARCHAR` | `hyperliquid` for first-party markets, otherwise the builder DEX code (`xyz`, `flx`, `para`, …) |
| `coin` | `VARCHAR` | Venue-native market id: `BTC` (first-party), `xyz:TSLA` (HIP-3). Join key to `perp_market_details` |
| `market_symbol` | `VARCHAR` | `coin` with the `dex:` prefix stripped. Not unique across dexes — never join on it |
| `asset_id` | `BIGINT` | Numeric asset id used by the raw Hyperliquid action tables. `NULL` when a market traded before the registry poller recorded it |
| `trader` | `VARBINARY` | The account on this leg, as raw bytes rather than a hex string |
| `is_taker` | `BOOLEAN` | Aggressor flag. Exactly one leg per match has it set |
| `side` | `VARCHAR` | `buy` or `sell` from this leg's perspective |
| `dir` | `VARCHAR` | Raw Hyperliquid direction string, e.g. `Open Long`, `Close Short` — the position effect, not the order side |
| `fill_type` | `VARCHAR` | Normalised leg role: `order`, `twap`, `liquidation`, `adl`, `settlement`, `vault_netting` |
| `start_position` | `DOUBLE` | Signed position size immediately before this fill |
| `price` | `DOUBLE` | Fill price, in the market's quote token (see `fee_token`) |
| `size` | `DOUBLE` | Fill size in base units |
| `notional_usd` | `DOUBLE` | `price * size`. Sums to \~2x reported volume unless you filter `is_taker` |
| `closed_pnl_usd` | `DOUBLE` | Realised PnL on this leg; non-zero on closes |
| `fee_usd` | `DOUBLE` | This leg's fee, in `fee_token`. Negative on maker rebates |
| `builder_fee_usd` | `DOUBLE` | Front-end / builder-code fee share, populated on \~6% of legs |
| `deployer_fee_usd` | `DOUBLE` | HIP-3 deployer fee share |
| `fee_token` | `VARCHAR` | Quote / collateral token the prices and fees are denominated in. Not always USDC |
| `priority_gas` | `DOUBLE` | HYPE spent in the priority-gas auction. Sparse |
| `tid` | `BIGINT` | Match id, shared by both legs — the pairing key |
| `oid` | `BIGINT` | Order id, globally unique |
| `cloid` | `VARCHAR` | Client order id where the trader set one |
| `builder` | `VARBINARY` | Builder (front-end) address, populated on \~6% of legs |
| `twap_id` | `BIGINT` | TWAP order id, set only on the taker leg. Group by it to reconstruct an executed schedule |
| `liquidation_method` | `VARCHAR` | `market` or `backstop`. Set on both legs of the match — use `fill_type` for the role |
| `liquidated_trader` | `VARBINARY` | The account that was liquidated. Also present on both legs |
| `liquidation_mark_price` | `DOUBLE` | Mark price at liquidation |
| `_updated_at` | `TIMESTAMP(3) WITH TIME ZONE` | Build timestamp. Legs are immutable, so in practice when the leg was first written |

<Warning>
  Bound `block_date` on every query. It is the partition key and the only column that prunes: `block_month` is a plain column, so filtering it alone reads every day in the month, and nothing prunes *within* a day — a `coin` filter still scans the full partition. An unbounded scan reads the whole multi-billion-row history.
</Warning>

## Both legs are here, so filter is\_taker for volume

`SUM(notional_usd)` over this table is \~2x exchange-reported volume, because every match contributes a taker leg and a maker leg. Filtering `is_taker` counts each match once. For pre-aggregated volume, use [`perp_market_metrics_hourly`](/data-catalog/curated/perpetuals/hyperliquid/perp-market-metrics-hourly) or [`perp_market_metrics_daily`](/data-catalog/curated/perpetuals/hyperliquid/perp-market-metrics-daily), which are the volume source of truth.

```sql theme={null}
-- Traded volume per market, single-counted
SELECT
  block_date,
  coin,
  SUM(notional_usd) AS volume_usd,
  COUNT(*) AS trades
FROM hyperliquid.perp_trades
WHERE block_date >= current_date - INTERVAL '7' day
  AND block_date < current_date
  AND is_taker
  AND fill_type NOT IN ('settlement', 'vault_netting')
GROUP BY 1, 2
ORDER BY 3 DESC
```

Do **not** filter `is_taker` when the question is per-account: at account grain each leg is one account's own participation, which is also why [`perp_accounts_daily`](/data-catalog/curated/perpetuals/hyperliquid/perp-accounts-daily) aggregates both legs. The same applies to `closed_pnl_usd` — maker-side closes carry realised PnL too, and a taker-only filter drops roughly 40% of it.

## fill\_type

`fill_type` is the leg's role, normalised from `dir`, `liquidation_method`, `is_taker` and `twap_id`.

| Value | Leg role |
| - | - |
| `order` | A voluntary order fill, including the resting order hit by an ordinary liquidation |
| `twap` | The leg belongs to a TWAP child slice; always the taker |
| `liquidation` | This leg's account was force-closed for undercollateralisation |
| `adl` | This leg's account was the profitable side force-closed by auto-deleveraging |
| `settlement` | Engine force-close of all open positions when a builder DEX delists a market: one price, one timestamp, zero fees |
| `vault_netting` | Vault position netting (`dir = 'Net Child Vaults'`), not a trade at all |

`settlement` and `vault_netting` are administrative and belong in no volume or activity measure. `liquidation` and `adl` are genuine risk transfers at market prices, but they are forced, so include them only when the question covers forced flow.

TWAP legs inflate fill counts far more than volume — one parent order becomes many slices. Count `DISTINCT twap_id` for order-level figures.

## Liquidation columns mark the match, not the role

<Warning>
  `liquidation_method` and `liquidated_trader` are stamped on **both** legs of a liquidation match. Counting rows where `liquidation_method IS NOT NULL` therefore doubles the answer: measured 2026-08-12, 17,044 legs carried a method while only 8,522 accounts were liquidated. Use `fill_type = 'liquidation'` for the forced leg.
</Warning>

The roles depend on the method. On `market` (ordinary) liquidations the liquidated account is the taker and its counterparty is a plain `order` leg. On `backstop` (auto-deleveraging) the roles invert: the bankrupt account is the maker leg with `fill_type = 'liquidation'`, and the profitable account force-closed against it is the taker, with `fill_type = 'adl'`.

## side is the order direction, dir is the position effect

The two legs of a match always carry opposite `side` values. `dir` is not symmetric that way: it describes what the fill did to this leg's own position — `Open Long`, `Close Short`, `Long > Short` — so a `Close Long` fill has `side = 'sell'`. Use `side` for order flow, `dir` or `start_position` for position intent. Perp scope carries exactly 13 `dir` values, verified across full fill history; an unrecognised value lands in `fill_type = 'order'` until the model is taught it.

## Fees

`fee_usd` is negative on maker rebates, so summing it across all legs yields net protocol revenue; add `is_taker` for gross taker fees. `builder_fee_usd` is populated together with `builder` on the \~6% of legs routed through a front-end, and `deployer_fee_usd` only on HIP-3 markets.

All three are denominated in `fee_token`, which is not always USDC — a HIP-3 DEX picks its own quote token, and USDC, USDT0, USDH and USDE all appear across history. They are USD-pegged, which is what keeps the `_usd` column names honest, but a fee on a USDH market is paid in USDH.

## Not in this table

Curated classification — `market_category`, `asset_class`, `asset_type`, `underlying_ticker`, `builder_name` — is deliberately kept off this table so a reclassification does not rewrite billions of rows. Join [`perp_market_details`](/data-catalog/curated/perpetuals/hyperliquid/perp-market-details) on `coin` for it, along with the market-level `max_leverage`, `margin_tiers` and `margin_mode`.

Per-fill leverage and margin mode are absent by design: both are a per-`(user, asset)` account setting rather than a property of a fill, and the venue-wide models do not reconstruct them.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.