Skip to main content

Hyperliquid TWAP Orders & Child Fills

In short: a Hyperliquid TWAP splits one large order into suborders sent at a fixed interval (at least 30 seconds apart) over the running time you choose. Each suborder has a 3% max slippage, and if one under-fills, later suborders catch up, up to 3x the normal slice size. With Bitquery you can get every TWAP a wallet placed from the Twaps cube, and every fill it produced from Trades filtered on TwapId.

A TWAP on Hyperliquid is one parent order that executes as many small child fills over a set duration. Tools that only read the parent order see a single line; tools that only read fills see dozens of unexplained trades. Neither reconciles.

This page joins the two: the Twaps cube for the parent order lifecycle, and the Trades cube filtered on TwapId for every fill it produced.

API Key Required

To query or stream data outside the Bitquery IDE, you need an API access token.

Follow the steps here: How to generate Bitquery API token ➤

TWAP orders of one wallet​

State.Status moves from activated (or waitingForTrigger) to finished, terminated, stopped or error (StatusError carries the reason, e.g. Insufficient margin to place order.). Each status change is its own record, so a completed TWAP appears at least twice — once on activation with zero executed, once at the end with the final totals. API history goes back about 30 days. For older data, ask about a Data Store export.

Run it in the IDE: Hyperliquid Wallet TWAP Orders ➤

query {
Hyperliquid {
Twaps(
limit: { count: 50 }
orderBy: { descending: Block_Time }
where: {
Twap: {
Trader: {
Address: { is: "0x3bcae23e8c380dab4732e9a159c0456f12d866f3" }
}
}
Block: {Time: {since_relative: {days_ago: 30}}}
}
) {
Block {
Time
}
Twap {
TwapId
Market {
Symbol
}
Order {
Side
Size
ReduceOnly
Randomize
}
Interval {
DurationMinutes
StartTime
}
State {
Status
StatusError
ExecutedSize
ExecutedNotional
}
Trader {
Address
}
}
}
}
}

Sample response — the same TWAP at activation and at completion:

[
{
"Block": { "Time": "2026-10-13T09:44:59.578059Z" },
"Twap": {
"TwapId": "2311912",
"Market": { "Symbol": "SMSN" },
"Order": { "Side": "Sell", "Size": "855.383", "ReduceOnly": true, "Randomize": true },
"Interval": { "DurationMinutes": 30, "StartTime": "1791884699578" },
"State": { "Status": "activated", "ExecutedSize": "0.0", "ExecutedNotional": "0.0" }
}
},
{
"Block": { "Time": "2026-10-13T10:15:01.042325Z" },
"Twap": {
"TwapId": "2311912",
"Market": { "Symbol": "SMSN" },
"Order": { "Side": "Sell", "Size": "855.383", "ReduceOnly": true, "Randomize": true },
"Interval": { "DurationMinutes": 30, "StartTime": "1791884699578" },
"State": { "Status": "finished", "ExecutedSize": "855.383", "ExecutedNotional": "150042.98774" }
}
}
]

Every child fill of one TWAP​

TwapId on the parent matches Trade.TwapId on the fills. Filter the Trades cube on it to get the complete execution. Only the TWAP owner's side of each fill carries the TwapId (the makers on the other side do not), so add the owner's address too — fills are stored by trader, and the wallet filter keeps the query fast over the whole 30 days.

Run it in the IDE: Hyperliquid TWAP Child Fills ➤

query {
Hyperliquid {
Trades(
limit: { count: 100 }
orderBy: { ascending: [Block_Time, Trade_Execution_Index] }
where: {
Trade: {
TwapId: {eq: "2311912"}
Trader: {Address: {is: "0x3bcae23e8c380dab4732e9a159c0456f12d866f3"}}
}
Block: {Time: {since_relative: {days_ago: 30}}}
}
) {
Block {
Time
}
Trade {
TwapId
Market {
Symbol
}
Execution {
Price
Size
Side
Direction
Index
Tid
}
Fees {
Fee
FeeToken
}
Position {
Size
RealizedPnl
}
Trader {
Address
}
}
}
}
}

TwapId is a big integer, so the filter operator is eq, not is.

One slice often fills against several resting orders in the same block, and every fill of a block shares Block.Time; Execution.Index is the fill's place in the block's execution order, so [Block_Time, Trade_Execution_Index] sorts the fills as they happened. Position.Size is the owner's position after each fill — for a reduce-only TWAP you can watch it shrink slice by slice. (Tid is a fill identifier, not a sequence.)

A TWAP can produce more fills than one page holds — TWAP 2311912, 30 minutes long, has 239. Raise limit or page through with offset when you need every one of them.

TWAP execution summary​

Aggregate the child fills to reconcile against the parent order's ExecutedSize and ExecutedNotional.

Run it in the IDE: Hyperliquid TWAP Execution Summary ➤

query {
Hyperliquid {
Trades(
where: {
Trade: {
TwapId: {eq: "2311912"}
Trader: {Address: {is: "0x3bcae23e8c380dab4732e9a159c0456f12d866f3"}}
}
Block: {Time: {since_relative: {days_ago: 30}}}
}
) {
Trade { TwapId Market { Symbol } }
fills: count
filled: sum(of: Trade_Execution_Size)
avgPrice: average(of: Trade_Execution_Price)
fees: sum(of: Trade_Fees_Fee)
pnl: sum(of: Trade_Position_RealizedPnl)
}
}
}

For TWAP 2311912 this returns 239 fills totalling exactly the 855.383 size on the parent order, with 8.10 USDC of fees and 2,906.85 of realized PnL. That reconciliation — parent ExecutedSize equals the sum of child fill sizes — is the check most exports fail. average is the plain mean of fill prices (175.406 here); the size-weighted price is the parent's ExecutedNotional / ExecutedSize (175.410).

All TWAP fills of a wallet​

To pull every TWAP-produced fill for an address without going TWAP by TWAP, filter on IsTwap instead.

Run it in the IDE: Hyperliquid Wallet TWAP Fills ➤

query {
Hyperliquid {
Trades(
limit: { count: 100 }
orderBy: { descending: [Block_Time, Trade_Execution_Index] }
where: {
Trade: {
IsTwap: true
Trader: {
Address: { is: "0x3bcae23e8c380dab4732e9a159c0456f12d866f3" }
}
}
Block: {Time: {since_relative: {days_ago: 30}}}
}
) {
Block {
Time
}
Trade {
TwapId
Market {
Symbol
}
Execution {
Price
Size
Side
Direction
Index
Tid
}
Fees {
Fee
FeeToken
}
Position {
Size
RealizedPnl
}
}
}
}
}

Real-time TWAP stream​

Run it in the IDE: Hyperliquid TWAP Orders Stream ➤

subscription {
Hyperliquid {
Twaps {
Block {
Time
}
Twap {
TwapId
Market {
Symbol
}
Order {
Side
Size
ReduceOnly
Randomize
}
State {
Status
ExecutedSize
ExecutedNotional
}
Trader {
Address
}
}
}
}
}

Notes​

  • Order.Randomize indicates the TWAP randomises its slice sizes (by up to ±20%), which is why child fills are uneven.
  • Interval.DurationMinutes is the configured TWAP length; StartTime is a millisecond timestamp.
  • For non-TWAP order lifecycle and L3 book deltas, see the Orders API.
Build with Bitquery

Ready to run this in production?

Get an API key and run these queries in minutes, or talk to us about plans and enterprise delivery.