Home>Blog>How to Track Closed Trades on Hyperliquid: A Developer's Guide
How to Track Closed Trades on Hyperliquid: A Developer's Guide

How to Track Closed Trades on Hyperliquid: A Developer's Guide

By CMM Team - 27-Apr-2026

How to Track Closed Trades on Hyperliquid: A Developer's Guide

If you have ever tried to compute a Hyperliquid wallet's win rate from API data, you already know the joke. The positions endpoint returns snapshots at unpredictable intervals, so a "trade" in your data may be a single sample of a position that opened and closed between polls. Fills give you raw entry and exit events with no concept of pairing, so you write FIFO logic that breaks the first time a wallet flips long-short-long inside a minute. By the time you have something that looks like trade history, you have shipped two hundred lines of reconciliation code and you are still not sure the numbers are right.

That problem is now a single endpoint. This guide walks through the closed-trades surface on Hyperliquid: the four endpoints, the response schema, a working recipe to compute win rate for any wallet in one call, and the production patterns we use ourselves. By the end you will have a "wallet edge" function that returns total trades, win rate, average hold time, and realized PnL for any address you point it at.

The short version: hit /closed-trades/summary?address=0x... with a JWT and read wins / totalTrades off the response. Everything else in this guide is what you do once that one call is not enough.

Why closed trades was a hard problem on Hyperliquid

Hyperliquid is on-chain. Every fill, every margin update, every funding payment is verifiable. So the obvious question is why "give me this wallet's last hundred closed trades" was ever non-trivial. The answer is that the chain stores events, not trades. A trade is an aggregation across one or more entries and one or more exits, and that aggregation depends on the trader's intent.

Take a wallet that opens a 10 ETH long, adds 5 ETH on a dip, takes profit on 8 ETH, gets stopped out on the rest, then immediately re-opens a short. Was that one trade or two? Three? The right answer depends on how you define "closed." Most builders ended up writing their own pairing logic, which meant their win rate disagreed with every other tool, which meant it was hard to publish a leaderboard anyone trusted.

The closed-trades endpoint resolves this with a canonical definition. A trade opens when net position size goes from zero to non-zero. It closes when net position size returns to zero. Partial closes inside that window roll up into the same trade. Funding payments and fees over the open window get attributed to the trade. The result is a single row per closed position, with realized PnL, duration, and a hash that lets you drill back into the underlying fills.

The four endpoints at a glance

The closed-trades surface is four endpoints under /api/external/closed-trades. Most of what you will build can run off the first two.

| Endpoint | Purpose | Required params | | --- | --- | --- | | GET /closed-trades | List closed trades for a wallet, paginated | address | | GET /closed-trades/summary | Aggregate stats: trade count, wins, losses, avg duration | address | | GET /closed-trades/{hash} | Single closed trade by hash | hash in path | | GET /closed-trades/{hash}/fills | The fills that compose a single closed trade | hash in path |

One quirk worth flagging up front: the time-window parameters on these endpoints are startTime and endTime, not start and end like most of the rest of the HyperTracker API. If you are copy-pasting query logic from another endpoint, this is the bug you will introduce. Both accept ISO 8601 timestamps.

Closed Trades Endpoints Map

Authenticating and making your first request

The HyperTracker API uses JWT bearer auth. You get a token from your dashboard once you have a Pulse, Surge, Flow, or Stream plan, or from the free tier for testing. The base URL for external API calls is https://ht-api.coinmarketman.com/api/external.

The minimum-viable first call is the summary endpoint. Replace the address below with any Hyperliquid wallet:

curl -H "Authorization: Bearer $HT_TOKEN" \
  "https://ht-api.coinmarketman.com/api/external/closed-trades/summary?address=0xabc123..."

The response is small and immediately useful:

{
  "address": "0xabc123...",
  "totalTrades": 184,
  "longTrades": 102,
  "shortTrades": 82,
  "wins": 109,
  "losses": 75,
  "avgDuration": 14820,
  "updatedAt": "2026-04-21T08:17:00.000Z"
}

That gives you a wallet's total trade count, long/short split, win and loss tallies, and average hold time in seconds. From there, win rate is one division. We will build on this in the next section.

Computing win rate for any wallet

The summary endpoint is what you want for any application that ranks, scores, or filters traders by edge. Here is the minimum function you need:

async function walletEdge(address) {
  const res = await fetch(
    `https://ht-api.coinmarketman.com/api/external/closed-trades/summary?address=${address}`,
    { headers: { Authorization: `Bearer ${process.env.HT_TOKEN}` } }
  );
  const s = await res.json();

  return {
    address: s.address,
    trades: s.totalTrades,
    winRate: s.totalTrades ? s.wins / s.totalTrades : 0,
    longShortRatio: s.shortTrades ? s.longTrades / s.shortTrades : null,
    avgHoldHours: s.avgDuration / 3600,
    updatedAt: s.updatedAt
  };
}

That function is enough to power a "verified edge" leaderboard, a copy-trade pre-screen, or a portfolio view that shows the historical performance of any wallet your user is following. Three things to keep in mind when you use it.

Win rate alone is misleading. A wallet with a 70% win rate that loses big on the 30% can still be net negative. Always pair win rate with realized PnL or risk-adjusted return. The list endpoint (covered next) gives you the per-trade PnL you need for that.

Sample size matters. Filter out wallets with fewer than 30 closed trades before you draw any conclusion about their edge. Below that, the win rate is noise.

The summary is a snapshot. The updatedAt field tells you when the aggregate was last computed. If you are building anything time-sensitive, treat the summary as a cached value and re-query before acting.

Win Rate Recipe

Drilling into individual trades

The summary endpoint tells you what the wallet did in aggregate. The list endpoint tells you what each trade looked like. You hit it the same way:

curl -H "Authorization: Bearer $HT_TOKEN" \
  "https://ht-api.coinmarketman.com/api/external/closed-trades?address=0xabc123...&limit=50"

Each item in the response is a ClosedTradeItemDto with the fields you need to reconstruct a trade end-to-end:

  • hash, id, address, side, coin
  • avgEntry, avgExit, totalSize, totalUsd
  • openTime, closeTime, duration
  • realizedPnlUsd, fee, feeUsd, fundingUsd
  • countFills, partial

The fundingUsd field is the one that tends to get under-appreciated. Hyperliquid funding settles every hour, which means a trade held for two days has paid or received funding 48 times. On a long position during a hot market with elevated funding, those payments can erode realized PnL meaningfully. The closed-trades response breaks this out so you can see whether a wallet's edge survives funding cost or only looks good when you ignore it.

The partial boolean flags trades that closed via a partial fill rather than a clean round-trip. If you are aggregating realized PnL across a wallet's history, partial trades are accounted for at the position level and you should treat them no differently than full closes.

For the cases where you need fill-level detail (drawdown reconstruction, slippage analysis, copy-trade replay), drop the trade hash into the fills endpoint:

curl -H "Authorization: Bearer $HT_TOKEN" \
  "https://ht-api.coinmarketman.com/api/external/closed-trades/{hash}/fills"

That returns every fill that contributed to the closed trade, in chronological order, with size and price. From there, intra-trade max favorable and max adverse excursion are arithmetic.

Production patterns

Once you have the basic calls working, the questions become operational. Three patterns we use ourselves and recommend.

Pagination via nextCursor

The list and fills endpoints are paginated with a nextCursor token. Pass it back as a query param to get the next page. Always cap your loop at a sane page count when iterating, because some wallets have thousands of closed trades and you do not want a runaway worker.

let cursor = null;
const trades = [];
do {
  const url = `https://ht-api.coinmarketman.com/api/external/closed-trades` +
              `?address=${address}&limit=200` +
              (cursor ? `&nextCursor=${cursor}` : '');
  const page = await fetch(url, { headers }).then(r => r.json());
  trades.push(...page.items);
  cursor = page.nextCursor;
} while (cursor && trades.length < 5000);

Caching the summary

Summary aggregates do not change second-to-second. For most read paths (a leaderboard, a profile view, a copy-trade screen), a 60-second cache on the summary response is plenty and saves you most of your request budget. Use the updatedAt timestamp as your cache key invalidation signal. If our data refreshed since you last cached, re-query.

Time-windowing with startTime and endTime

If you only care about recent activity, scope your queries with startTime and endTime. Both take ISO 8601 strings. A common pattern is "last 30 days":

const startTime = new Date(Date.now() - 30 * 86400 * 1000).toISOString();
const url = `https://ht-api.coinmarketman.com/api/external/closed-trades` +
            `?address=${address}&startTime=${startTime}`;

One more time, because this is the most common bug: the param names are startTime and endTime. Not start. Not end. Different from the rest of the API.

Production Patterns

What you can build with this

The closed-trades surface unlocks a category of applications that were impractical before. A few we have seen builders ship in the first weeks since the endpoint went live.

Verified-edge leaderboards. Public win-rate boards have always been suspect because the underlying numbers were inferred. With closed trades, the inputs are canonical. You can publish a leaderboard ranking wallets by 30-day win rate, by realized PnL, or by Sharpe-like risk-adjusted metrics, and trust the numbers.

Copy-trade pre-screening. Before you mirror a wallet's positions, you want to know whether they are actually any good. The summary endpoint gives you that filter in one call. Reject any wallet under 30 trades, under a 50% win rate, or with a negative cumulative PnL, and your copy universe shrinks to the wallets worth following.

Strategy attribution. If you run an internal trading desk on Hyperliquid, the closed-trades response is your audit log. Every trade your desk closed, with realized PnL, duration, fees, and funding cost broken out. Reconcile against your internal records or feed it into a P&L attribution dashboard.

Cohort behavior research. Combine closed-trades data with the cohort endpoints and you can answer questions like "what is the average win rate of Whale-tier wallets on BTC over the last 30 days" or "how does Money Printer cohort performance differ from Giga-Rekt cohort performance on the same asset." That is real research, not vibes.

Build on the closed-trades API

Pulse plan ($179/mo) gets you 50,000 requests per month against the full closed-trades surface, plus 16 cohort endpoints, leaderboards, position metrics, and order flow. Free tier available for testing (100 requests per day, no credit card).

View pricing →

Closing the loop

Hyperliquid promised on-chain transparency from day one. Every position, every fill, every funding payment is verifiable. For a long time, that promise stopped at the API boundary, because querying it for anything more sophisticated than current state was a build-it-yourself problem. With closed trades available as a single endpoint, that gap closes. Wallet edge becomes a function call. Trader leaderboards become trustworthy. Copy-trade systems become defensible. The chain stored the truth all along. Now the API hands it to you.