Where does Hyperliquid CVD data come from?

There is no native hyperliquid cvd data. Cumulative volume delta, the running sum of taker buy volume minus taker sell volume, is not a field Hyperliquid publishes for any coin on any interval. What the venue publishes is a public trades stream in which each fill carries a side: B when the taker bought, A when the taker sold. Everything called CVD, taker ratio or aggressor flow on Hyperliquid is someone adding those fills up.

That has two consequences worth stating up front. First, two vendors' CVD lines for the same coin will differ wherever their stream coverage differed, because a dropped socket is a hole in the sum and the venue offers no replay to fill it. Second, the sum has to start somewhere, so an absolute CVD number means nothing on its own; only its slope over a window you chose does.

Our implementation is a collector that holds the public trades socket for the whole perp universe and closes a bucket per coin per UTC minute. It stores 1-minute buckets, not per-fill rows: the taker buy and sell notional, the trade count, a VWAP, the largest fill and the share of notional from large fills. Per-fill data for Hyperliquid exists in our archive only for liquidations, which come from a different source (node fill data) and are covered in a separate post.

What /hyperliquid/trade-flow returns

GET /api/v1/hyperliquid/trade-flow?coin=BTC&minutes=60 returns the trailing minutes closed minutes for one perp (1 to 1440), newest last and dense, meaning one row per minute whether or not anything traded. Pro, Pro Plus included.

curl "https://cryptodataapi.com/api/v1/hyperliquid/trade-flow?coin=BTC&minutes=60" \
  -H "X-API-Key: cdk_live_your_key"

Abridged response, illustrative values, served field names:

{
  "coin": "BTC", "minutes": 60, "bucket": "1m", "large_fill_usd": 25000,
  "window_start": 1792970400000, "window_end": 1792974000000,
  "stream": { "connected": true, "connected_since": 1792911034120, "coverage_pct": 100.0 },
  "data": [
    { "time": 1792970400000, "coin": "BTC", "buy_notional": 1842310.2, "sell_notional": 1210044.7,
      "n_trades": 412, "vwap": 68412.5, "max_fill_usd": 310200.0, "large_fill_share": 0.31,
      "partial": false, "cvd_usd": 632265.5 },
    ...
  ],
  "count": 60
}

Row fields, with their exact meaning:

The envelope's stream.coverage_pct is the share of the last 60 minutes that was fully watched. Until the first bucket closes after a cold start the endpoint answers 503 trade_flow_warming with Retry-After: 60; a coin Hyperliquid does not list is a 404 unknown_coin.

Null vs zero vs a missing minute

Because the sum is only as good as the socket, the endpoint is explicit about what it did not see. This table is the part to lift into your parser:

You seeIt meansDo
buy_notional: 0.0, partial: falseWe watched the whole minute and nothing tradedCount it as a real zero
buy_notional: null, partial: trueA reconnect or deploy; the minute was not fully watchedBreak your CVD series there; never interpolate
vwap: nullNo fills in the minuteCarry the previous VWAP if you need a price
large_fill_share: nullNo fill reached $25kTreat as 0 for a filter, null for a mean
A minute absent from /backtesting/hl-trade-flowEither quiet or unwatched; stored partial rows tell them apartCheck for a stored partial row before calling it quiet

Unwatched minutes are never back-filled. Hyperliquid's stream has no replay, so a hole at capture time is a hole forever, and the honest thing is to say so in the row rather than smooth it.

Scanning every perp at once: /trade-flow/universe

GET /api/v1/hyperliquid/trade-flow/universe?window=1h (Pro Plus) aggregates the same buckets across every Hyperliquid perp for one window of 15m, 1h or 4h, biggest notional first. Each row carries coin, buy_notional, sell_notional, taker_buy_ratio (buy divided by buy plus sell, 0 to 1; null if no fills), n_trades, large_fill_share and partial_minutes. The envelope has window, window_minutes, large_fill_usd, as_of (the window's end, the last closed minute) and count.

curl "https://cryptodataapi.com/api/v1/hyperliquid/trade-flow/universe?window=1h" \
  -H "X-API-Key: cdk_live_your_key"

partial_minutes is the same number on every row, because coverage is a property of the connection, not the coin. Coins with zero fills in the window are omitted rather than reported as zeros. Warming and coverage semantics match the single-coin route.

The cross-venue read: /market-intelligence/taker-buy-sell

If you want Hyperliquid's taker bias next to other exchanges, GET /api/v1/market-intelligence/taker-buy-sell reports a taker buy ratio per exchange, per coin, over a 4-hour window. Here buy_ratio is a percent, 0 to 100, not a fraction. Since 2026-09-09 every coin Hyperliquid lists has an {"exchange": "Hyperliquid", "buy_ratio": ...} leg in the inner exchange_list, notional-weighted over the trailing 4h from the same trade tape; a coin only Hyperliquid carries gets its own top-level entry whose aggregate is the Hyperliquid figure. Any key can read it.

Two scales for the same idea is the kind of thing that costs an afternoon, so:

EndpointFieldScaleWindow
/hyperliquid/trade-flow/universetaker_buy_ratio0 to 115m, 1h or 4h, your choice
/market-intelligence/taker-buy-sellbuy_ratio0 to 1004h, fixed

An absent Hyperliquid leg means the tape is warming or not configured, never that Hyperliquid had no trades.

How do I get historical Hyperliquid taker volume to rebuild CVD?

From GET /api/v1/backtesting/hl-trade-flow (Pro), the archived minutes, and from the hl_trade_flow daily Parquet type for bulk. Coverage begins 2026-09-09, the day the collector went live; it is forward-only and nothing earlier can be reconstructed, for the same no-replay reason. Rows have the live shape minus cvd_usd: time, coin, buy_notional, sell_notional, n_trades, vwap, max_fill_usd, large_fill_share, partial. Minutes with no fills are not stored. Pass coin (or a comma list up to 25, or omit for all), a required start, an exclusive end, and page with cursor; format=csv works too.

curl "https://cryptodataapi.com/api/v1/backtesting/hl-trade-flow?coin=ETH&start=2026-10-01&end=2026-10-02&limit=10000" \
  -H "X-API-Key: cdk_live_your_key"

Rebuilding CVD from the archive is a cumulative sum that resets at every unwatched minute:

import httpx

API = "https://cryptodataapi.com/api/v1"
H = {"X-API-Key": "cdk_live_your_key", "Accept-Encoding": "gzip"}

rows, cursor = [], None
while True:
    p = {"coin": "ETH", "start": "2026-10-01", "end": "2026-10-02", "limit": 10000}
    if cursor: p["cursor"] = cursor
    j = httpx.get(f"{API}/backtesting/hl-trade-flow", headers=H, params=p, timeout=60).json()
    rows += j["data"]
    if not j.get("has_more"): break
    cursor = j["next_cursor"]

cvd, series = 0.0, []
for r in rows:
    if r["partial"]:                 # unwatched minute: the sum is no longer continuous
        cvd = 0.0
        series.append((r["time"], None))
        continue
    cvd += r["buy_notional"] - r["sell_notional"]
    series.append((r["time"], cvd))

For whole days across all coins, list and download the Parquet files with /backtesting/archives?data_type=hl_trade_flow and /backtesting/archives/download (Pro Plus), or buy a single day-file for $1.00 over x402 at /backtesting/archives/purchase. Columns match the row above.

How to use this in a bot (and when not to)

Use the slope, not the level. Because cvd_usd restarts at window_start on every request, compare the last N rows of one response, or compute your own running sum from buy_notional and sell_notional over a window you control.

Gate on coverage. If stream.coverage_pct is well under 100, or your window contains partial: true rows, the taker ratio for that window is biased by what was missed. Skip the signal rather than act on it.

Pair flow with price. Taker buying into a flat or falling vwap is absorption; the same buying with price rising is confirmation. The minute grid lines up with /hyperliquid/candles?interval=1m on bar open, so the join is a dictionary lookup on time.

Do not read liquidations out of this tape. The public trades stream carries no liquidation flag, so a forced close looks like any other aggressive fill here. The exact liquidation fills are a separate feed at /backtesting/hl-liquidations. Do not expect per-fill rows; the collector stores minutes. And do not expect anything before 2026-09-09.

The single-coin route is on Pro at $29 a month and the 24-hour Pro trial on email verification reaches it; the universe scan is Pro Plus. Reference at /docs, plans at /pricing.

Frequently asked questions

Does Hyperliquid publish CVD or taker buy/sell volume?

No. CVD is rebuilt from the public trades stream, where each fill's side says whether the taker bought (B) or sold (A). Every Hyperliquid CVD figure is somebody's sum of those fills.

What does cvd_usd on /hyperliquid/trade-flow measure?

The running buy_notional minus sell_notional from window_start of that request. It resets per request and is not archived, so compare rows within one response or compute your own sum from the archived minutes.

What does a null buy_notional with partial true mean?

Our socket did not watch the whole minute, typically a reconnect or deploy. Hyperliquid's stream has no replay, so the minute is never back-filled. A watched minute with no trades is a real zero with partial false.

How far back does Hyperliquid taker flow history go?

To 2026-09-09, when the collector went live. /backtesting/hl-trade-flow serves the archived 1-minute buckets and the hl_trade_flow Parquet type holds one file per day for all coins.