userFillsByTime plus userFunding.
This page builds the derivation: realized PnL over time, fees, funding, win rate, volume,
and a per-coin breakdown.
userFills, userFillsByTime, userFunding, and candleSnapshot are all served by
GoldRush. The waterfall routes to it automatically, so you only need
?provider=goldrush if you’re pinning for consistency or debugging.1. Fetch the fill history
userFillsByTime takes a millisecond-epoch window. endTime is optional and defaults to
now.
closedPnlis the realized profit or loss from the portion of the position that this fill closed. It’s"0.0"on fills that only open or increase a position, because nothing was realized. Only closing fills carry a non-zero value.feeis what you paid to trade, always positive, and always deducted. A negative fee (a maker rebate) appears as a negative value, which the arithmetic below handles without a special case.
Handling the 2000-fill cap
Hyperliquid caps a fills response at 2000 rows, keeping the most recent. For an active trader over a long window, that’s a truncated view, and nothing in the response tells you it happened. You have to infer it:Each page is a billed request, so an unbounded backfill over a very active address can
be expensive. Bound the loop: cap the number of pages, or show the truncated window
with a “showing the most recent 2000 fills” note and let the user opt in to the full
history.
2. Derive the numbers
With the fills in hand, everything else is arithmetic. Sort oldest-first, walk once, and accumulate.Σ(closedPnl − fee). Hyperliquid’s closedPnl is
gross, so it doesn’t deduct what you paid to trade. Report gross PnL as “realized PnL” and
your numbers will consistently overstate performance, by more the more actively the
address trades.
Volume is notional, Σ|px × sz|. The absolute value matters: sz can be negative
depending on direction, and a trader’s volume is a magnitude, not a signed quantity.
Win rate is wins ÷ closing fills, not wins ÷ all fills. Every position takes at
least one opening fill that realizes nothing, so dividing by the total fill count
silently halves the win rate of anyone who opens and closes in single fills, and
distorts it unpredictably for everyone else.
Get the denominator from dir, not from closedPnl. The tempting shortcut (“a closing
fill is one where closedPnl ≠ 0”) is wrong in both directions. A position closed
exactly at its entry price realizes nothing and would be dropped from the denominator,
inflating the win rate of anyone who scratches trades. And a spot dust conversion also
carries closedPnl: 0 despite touching no perp position at all. dir states what the
fill actually did, so it answers the question directly.
3. Add funding
Perp traders pay or receive funding continuously, and over a long holding period it can dominate trading PnL. It’s a separate ledger from fills:delta.usdc is signed: negative is funding you paid, positive is funding you
received. So the accumulation is a plain sum, no sign flip:
4. Add price context
A PnL curve is easier to read against the market it was earned in.candleSnapshot
supplies the backdrop.
This is the one type in the series with a nested request body. Its parameters go
inside a req object rather than at the top level:
t open time, T close time, s
coin, i interval, o/h/l/c the OHLC prices, v volume, n trade count.
Match the interval to the window so the chart stays readable rather than returning
thousands of points:
5. Assemble the surface
series as the chart and byCoin as a sortable table underneath.