---
title: "Hyperliquid CVD in Python: Build an Order Flow Dashboard"
description: "Build a Hyperliquid order flow dashboard: live taker buy and sell, CVD history matched to raw trades, and cancels per fill. Tested live, code included."
canonical_url: "https://0xarchive.io/resources/market-data-guides/hyperliquid-order-flow-dashboard"
markdown_url: "https://0xarchive.io/resources/market-data-guides/hyperliquid-order-flow-dashboard.md"
route: "/resources/market-data-guides/hyperliquid-order-flow-dashboard"
robots: "index, follow, max-image-preview:large"
generated_from: "prerendered_html"
---

# How to Build a Hyperliquid Order Flow Dashboard (CVD, Taker Flow)

Build a BTC order flow dashboard on Hyperliquid: live taker flow, CVD backfilled for a week and checked to the dollar against raw trades. Tested 8 Nov 2026.

By

[0xArchive Team](https://0xarchive.io/authors/0xarchive-team)

November 9, 2026

15 min read

- [Historical data](https://0xarchive.io/resources?topic=historical-data)
- [API and alerts](https://0xarchive.io/resources?topic=api-and-alerts)
- [Hyperliquid](https://0xarchive.io/resources?topic=hyperliquid)

## Key findings

1. [Step 1: see what the native API gives you](https://0xarchive.io/resources/market-data-guides/hyperliquid-order-flow-dashboard#article-section-step-1-see-what-the-native-api-gives-you) 10 rows What Hyperliquid's recentTrades returns: 16 seconds of BTC trades.
2. [Step 3: check it against raw trades](https://0xarchive.io/resources/market-data-guides/hyperliquid-order-flow-dashboard#article-section-step-3-check-it-against-raw-trades) 129,672 Fills rebuilt into buy and sell volume, matching 0xArchive CVD to the dollar.
3. [Step 4: add order flow](https://0xarchive.io/resources/market-data-guides/hyperliquid-order-flow-dashboard#article-section-step-4-add-order-flow) 79 Median cancel events per fill event in an hour that week.
4. [Step 5: backfill a week](https://0xarchive.io/resources/market-data-guides/hyperliquid-order-flow-dashboard#article-section-step-5-backfill-a-week) -$468M BTC cumulative volume delta, 1 to 7 Nov 2026, from one request per page.

An order flow dashboard answers one question: are buyers or sellers hitting the book, and is that pressure building? This guide builds one for BTC on Hyperliquid, an onchain crypto exchange, from taker buy and sell volume, cumulative volume delta (CVD) and order activity. It starts live from Hyperliquid's own trade feed, then backfills a full week from 0xArchive and checks every number against raw trades. Everything ran on 8 November 2026, and every output is what came back.

## Short answer

- **Live CVD:** Hyperliquid's free `trades` WebSocket carries the taker side: `B` is a taker buy, `A` a taker sell. Delta is taker buy USD minus taker sell USD, and CVD is the running sum. It starts at zero when your script starts.
- **History:** `GET /v1/hyperliquid/cvd/{symbol}` returned a week of hourly taker buy, sell and delta in one request and 0.12 seconds. Hyperliquid's `recentTrades` returned 10 trades covering 16 seconds.
- **Exact:** for three hours on 6 November, the CVD route matched 129,672 raw fill rows and the candle's quote volume to the dollar.
- **Order activity is a separate measure:** `/orders/{symbol}/flow` counts order events per bucket: placements, cancels and fills. In the median hour of that week there were 79 cancel events for every fill event.

## Why build this on Hyperliquid

Every Hyperliquid fill is public, with the side that took liquidity. That makes taker flow something you measure, not something you estimate from candle colour. 0xArchive serves Hyperliquid trades from 15 April 2023, complete from 22 March 2025, so a dashboard can open already filled in with months of flow.

## What you need

- Python 3 with `requests` and `websockets` . The snippets are short prototypes with no retry logic.
- A 0xArchive API key in `OXARCHIVE_API_KEY` . Hyperliquid's own API needs no key. The free plan covers a rolling 30 days; the retained archive needs a paid plan, listed on [pricing](https://0xarchive.io/pricing) .

## Step 1: see what the native API gives you

Hyperliquid's `recentTrades` request returns the latest trades for a coin:

```
import requests

r = requests.post("https://api.hyperliquid.xyz/info", json={"type": "recentTrades", "coin": "BTC"}).json()
times = [t["time"] for t in r]
print("rows", len(r), "seconds covered", (max(times) - min(times)) / 1000)
print(r[0])
```

```
rows 10 seconds covered 15.939
{'coin': 'BTC', 'side': 'A', 'px': '76008.0', 'sz': '0.02967', 'time': 1794154934163, 'hash': '0x48b8...0084f6d8b8f7ec8156f21ad9740f', 'tid': 362349331820560, 'users': ['0xf5d8...f226', '0x6c5e...39c6']}
```

Ten rows, covering 16 seconds. It is the only market-wide trade request in Hyperliquid's info API, and it has no volume split by buyer and seller. Its candles carry `o h l c v n` and no taker split either. To build flow from the native API you must listen live and keep your own tape.

The live feed is good for that. The `trades` WebSocket pushes each trade with `side`, the taker side: `B` means the taker bought. We checked this against 0xArchive for 48 trades and the taker side matched on all 48. Here is a live CVD, in dollars, from 30 seconds of the feed:

```
import asyncio, json, time, websockets

async def main(seconds=30):
    cvd = 0.0
    async with websockets.connect("wss://api.hyperliquid.xyz/ws") as ws:
        await ws.send(json.dumps({"method": "subscribe", "subscription": {"type": "trades", "coin": "BTC"}}))
        t0, n = time.time(), 0
        while time.time() - t0 < seconds:
            msg = json.loads(await ws.recv())
            if msg.get("channel") != "trades":
                continue
            for t in msg["data"]:            # side is the taker side: B = taker bought
                v = float(t["px"]) * float(t["sz"])
                cvd += v if t["side"] == "B" else -v
                n += 1
        print("trades", n, "running CVD in USD", round(cvd))

asyncio.run(main())
```

```
trades 54 running CVD in USD 37221
```

In a longer 120-second run the same loop counted 193 trades: $153,002 of taker buying, $57,081 of taker selling, a delta of $95,921. That is a true CVD for the window, and it is all Hyperliquid's own API can give you: it starts at zero when your script starts. If the connection drops, the running total breaks. Fill the gap from history before you carry on, or the CVD silently restarts.

## Step 2: read CVD from 0xArchive

`GET /v1/hyperliquid/cvd/{symbol}` returns taker buy and sell notional per bucket, their difference and a running total. Intervals are 1m, 5m, 15m, 30m, 1h, 4h, 1d and 1w.

```
import os, time, requests

H = {"X-API-Key": os.environ["OXARCHIVE_API_KEY"]}
now = int(time.time() * 1000)
r = requests.get("https://api.0xarchive.io/v1/hyperliquid/cvd/BTC", headers=H,
                 params={"interval": "5m", "start": now - 20 * 60_000, "end": now}).json()
for b in r["data"]:
    print(b)
print(r["meta"])
```

```
{'timestamp': 1794153900000, 'buy_volume': 768684.2062900002, 'sell_volume': 1176895.7196499994, 'delta': -408211.51335999917, 'cumulative_delta': -408211.51335999917}
{'timestamp': 1794154200000, 'buy_volume': 258183.6344900001, 'sell_volume': 79817.34994999999, 'delta': 178366.28454000014, 'cumulative_delta': -229845.22881999903}
{'timestamp': 1794154500000, 'buy_volume': 186225.57398999963, 'sell_volume': 220309.78493, 'delta': -34084.21094000037, 'cumulative_delta': -263929.4397599994}
{'timestamp': 1794154800000, 'buy_volume': 20686.47555, 'sell_volume': 465802.25275000004, 'delta': -445115.77720000007, 'cumulative_delta': -709045.2169599994}
{'count': 4, 'request_id': 'dc6491c6-77cb-4113-aa60-8784a38bd539', 'has_more': False, 'symbol': 'BTC', 'venue': 'hyperliquid'}
```

The fields are exact: `timestamp` (bucket open, Unix milliseconds), `buy_volume` and `sell_volume` (taker notional in USD), `delta` (buy minus sell) and `cumulative_delta`. Two details bite. The last bucket may still be open, and a response is a page: `cumulative_delta` runs from the first bucket of that page, so across pages you rebuild it from `delta`. We tested that on one day of 1-minute buckets in three pages (`03_backfill.py` below): each page restarted its running total, and the sum of `delta` over the day was -$28,151,246 while the last page's own `cumulative_delta` read -$32,209,323.

## Step 3: check it against raw trades

Do not trust a number you have not rebuilt once. In the fills, each trade appears as two rows. The row with `crossed: true` is the taker, and its `side` says whether the taker bought. This rebuilds three hours of BTC flow on 6 November from raw fills and compares it with the CVD route and the hourly candle's `quote_volume`:

```
import os, datetime as dt, requests

H = {"X-API-Key": os.environ["OXARCHIVE_API_KEY"]}
B = "https://api.0xarchive.io/v1/hyperliquid"
ms = lambda *a: int(dt.datetime(*a, tzinfo=dt.timezone.utc).timestamp() * 1000)
S, E = ms(2026, 11, 6, 12), ms(2026, 11, 6, 15)

def get(path, **params):
    return requests.get(f"{B}{path}", headers=H, params=params, timeout=60).json()

def pages(path, **params):
    while True:
        j = get(path, **params)
        yield from j["data"]
        if not j["meta"].get("has_more"):
            return
        params["cursor"] = j["meta"]["next_cursor"]

cvd = {b["timestamp"]: b for b in get("/cvd/BTC", start=S, end=E, interval="1h")["data"]}
candles = {ms(*map(int, c["timestamp"][:13].replace("T", "-").split("-"))): c
           for c in get("/candles/BTC", start=S, end=E, interval="1h")["data"]}

rebuilt, rows = {}, 0
for t in pages("/trades/BTC", start=S, end=E, limit=50000):
    rows += 1
    if not t["crossed"]:          # each trade has two rows; crossed marks the taker
        continue
    hour = ms(*map(int, t["timestamp"][:13].replace("T", "-").split("-")))
    side = "buy" if t["side"] == "B" else "sell"
    rebuilt.setdefault(hour, {"buy": 0.0, "sell": 0.0})[side] += float(t["price"]) * float(t["size"])

print("fill rows", rows)
print("hour   cvd buy     rebuilt buy | cvd sell    rebuilt sell | buy+sell    candle quote")
for h in sorted(rebuilt):
    c, r = cvd[h], rebuilt[h]
    print(dt.datetime.fromtimestamp(h / 1000, dt.timezone.utc).strftime("%H:%M"),
          f"{c['buy_volume']:12,.0f} {r['buy']:12,.0f} | {c['sell_volume']:12,.0f} {r['sell']:12,.0f} |",
          f"{c['buy_volume'] + c['sell_volume']:12,.0f} {candles[h]['quote_volume']:12,.0f}")
```

```
fill rows 129672
hour   cvd buy     rebuilt buy | cvd sell    rebuilt sell | buy+sell    candle quote
12:00   32,945,074   32,945,074 |   27,753,143   27,753,143 |   60,698,217   60,698,217
13:00   72,677,820   72,677,820 |   91,318,092   91,318,092 |  163,995,911  163,995,911
14:00  196,603,268  196,603,268 |  182,244,669  182,244,669 |  378,847,938  378,847,938
```

All three agree to the dollar in every hour. One detail to know: with `end` at 15:00 the CVD route also returns the 15:00 bucket, because at 1h and longer a bucket counts when it *opens* inside `[start, end]`. The order flow route treats `end` as exclusive. Set your bounds to bucket opens so you never get a surprise partial bucket.

## Step 4: add order flow

`GET /v1/hyperliquid/orders/{symbol}/flow` is a different measure from CVD, and the name invites a mix-up. It does not return buy and sell volume. It counts order-status events per bucket, not unique orders, and it starts on 10 March 2026.

```
import os, time, requests

H = {"X-API-Key": os.environ["OXARCHIVE_API_KEY"]}
now = int(time.time() * 1000)
r = requests.get("https://api.0xarchive.io/v1/hyperliquid/orders/BTC/flow", headers=H,
                 params={"interval": "5m", "start": now - 15 * 60_000, "end": now}).json()
for b in r["data"]:
    print(b)
```

```
{'timestamp': '2026-11-08T16:05:00Z', 'limit_orders_placed': 20932, 'orders_canceled': 20734, 'orders_filled': 184, 'trigger_orders_placed': 30, 'tpsl_orders_placed': 8, 'orders_triggered': 10, 'orders_force_canceled': 12}
{'timestamp': '2026-11-08T16:10:00Z', 'limit_orders_placed': 27871, 'orders_canceled': 27745, 'orders_filled': 243, 'trigger_orders_placed': 68, 'tpsl_orders_placed': 23, 'orders_triggered': 0, 'orders_force_canceled': 13}
{'timestamp': '2026-11-08T16:15:00Z', 'limit_orders_placed': 23468, 'orders_canceled': 23455, 'orders_filled': 179, 'trigger_orders_placed': 83, 'tpsl_orders_placed': 25, 'orders_triggered': 0, 'orders_force_canceled': 9}
{'timestamp': '2026-11-08T16:20:00Z', 'limit_orders_placed': 12487, 'orders_canceled': 12374, 'orders_filled': 132, 'trigger_orders_placed': 39, 'tpsl_orders_placed': 11, 'orders_triggered': 4, 'orders_force_canceled': 17}
```

The fields are `timestamp` (bucket open, an RFC 3339 string, where CVD uses milliseconds), `limit_orders_placed`, `orders_canceled`, `orders_filled`, `trigger_orders_placed`, `tpsl_orders_placed`, `orders_triggered` and `orders_force_canceled`. Intervals are 1m, 5m, 15m and 1h. In the 16:05 bucket above, 20,932 limit orders were placed and 20,734 cancelled, while 184 were filled. A filled status counts events, not trades, and carries no size, so do not read it as volume.

What it shows you is churn. Most orders on Hyperliquid are placed and cancelled without ever trading, and the ratio moves with market conditions. It starts on 10 March 2026 for BTC (the 22:00 UTC bucket that day is the first full one), so it is a shorter history than CVD.

## Step 5: backfill a week

This is the job Hyperliquid's own API cannot do after the fact. One request each pulls seven days of hourly CVD, order flow and candles for BTC, then summarizes the week:

```
import os, statistics, datetime as dt, requests

H = {"X-API-Key": os.environ["OXARCHIVE_API_KEY"]}
B = "https://api.0xarchive.io/v1/hyperliquid"
ms = lambda *a: int(dt.datetime(*a, tzinfo=dt.timezone.utc).timestamp() * 1000)
S, E = ms(2026, 11, 1), ms(2026, 11, 7, 23)   # hourly buckets that open from 1 Nov 00:00 to 7 Nov 23:00

def get(path, **params):
    return requests.get(f"{B}{path}", headers=H, params=params, timeout=60).json()["data"]

cvd = get("/cvd/BTC", start=S, end=E, interval="1h", limit=10000)
flow = get("/orders/BTC/flow", start=S, end=E + 3_600_000, interval="1h", limit=10000)   # flow treats end as exclusive
candles = get("/candles/BTC", start=S, end=E, interval="1h", limit=10000)
print("buckets: cvd", len(cvd), "flow", len(flow), "candles", len(candles))

buy = sum(b["buy_volume"] for b in cvd)
sell = sum(b["sell_volume"] for b in cvd)
run, path = 0.0, []
for b in cvd:                       # rebuild the running total from delta: cumulative_delta restarts on every page
    run += b["delta"]
    path.append(run)
worst = min(cvd, key=lambda b: b["delta"])
agree = sum((b["delta"] > 0) == (c["close"] > c["open"]) for b, c in zip(cvd, candles))
usd = lambda v: ("-" if v < 0 else "") + f"${abs(v) / 1e6:,.0f}M"
churn = [f["orders_canceled"] / f["orders_filled"] for f in flow if f["orders_filled"]]

print(f"taker buy ${buy / 1e9:.2f}B  sell ${sell / 1e9:.2f}B  buy share {buy / (buy + sell):.1%}  net {usd(buy - sell)}")
print(f"CVD high {usd(max(path))}  low {usd(min(path))}  worst hour {usd(worst['delta'])} at",
      dt.datetime.fromtimestamp(worst["timestamp"] / 1000, dt.timezone.utc).strftime("%d %b %H:%M"))
print(f"delta sign matched candle direction in {agree} of {len(cvd)} hours")
print(f"limit orders placed {sum(f['limit_orders_placed'] for f in flow) / 1e6:.1f}M  cancelled {sum(f['orders_canceled'] for f in flow) / 1e6:.1f}M  filled {sum(f['orders_filled'] for f in flow) / 1e6:.2f}M")
print(f"cancels per fill: median {statistics.median(churn):.0f}  range {min(churn):.0f} to {max(churn):.0f}")
```

```
buckets: cvd 168 flow 168 candles 168
taker buy $9.27B  sell $9.74B  buy share 48.8%  net -$468M
CVD high $92M  low -$487M  worst hour -$92M at 05 Nov 22:00
delta sign matched candle direction in 108 of 168 hours
limit orders placed 136.2M  cancelled 134.7M  filled 2.00M
cancels per fill: median 79  range 24 to 157
```

Three requests returned 168 hourly buckets each. Takers bought $9.27 billion and sold $9.74 billion, so CVD ended the week $468 million negative. Its low was -$487 million, and the worst single hour was 5 November at 22:00 UTC. The sign of the hourly delta matched the candle's direction in 108 of 168 hours, 64%: flow moves with price more often than not, but it does not decide it.

Rebuild the running total from `delta`, as the script does. `cumulative_delta` restarts on every page: one day of 1-minute buckets came back in pages of 500, 500 and 441, and the sum of `delta` over the day was -$28,151,246 while the last page's own `cumulative_delta` read -$32,209,323. Below 1h a page can also be short and still carry a cursor, so stop on the cursor, not the page size.

To see how far back the data reaches, ask for daily buckets:

```
import os, datetime as dt, requests

H = {"X-API-Key": os.environ["OXARCHIVE_API_KEY"]}
B = "https://api.0xarchive.io/v1/hyperliquid"
ms = lambda *a: int(dt.datetime(*a, tzinfo=dt.timezone.utc).timestamp() * 1000)
day = lambda m: dt.datetime.fromtimestamp(m / 1000, dt.timezone.utc).strftime("%Y-%m-%d")

daily = requests.get(f"{B}/cvd/BTC", headers=H, timeout=60, params={
    "start": ms(2023, 4, 1), "end": ms(2025, 4, 5), "interval": "1d", "limit": 10000}).json()["data"]
print(len(daily), "daily buckets, first", day(daily[0]["timestamp"]))
for b in daily:
    if "2025-03-19" <= day(b["timestamp"]) <= "2025-03-24":
        print(day(b["timestamp"]), f"{b['buy_volume'] + b['sell_volume']:>16,.0f} USD taker volume")
```

```
703 daily buckets, first 2023-04-19
2025-03-19      863,263,560 USD taker volume
2025-03-20      873,995,387 USD taker volume
2025-03-21      400,167,114 USD taker volume
2025-03-22      394,026,086 USD taker volume
2025-03-23    1,061,106,657 USD taker volume
2025-03-24    2,587,750,831 USD taker volume
```

One request reached back to 19 April 2023. 0xArchive's API reference marks Hyperliquid trade history before 22 March 2025 as incomplete, and candles start on that date, so compare flow within the same era and start any price-joined dashboard there.

## Side by side: Hyperliquid's API and 0xArchive

| Row label | Hyperliquid native API | 0xArchive |
| --- | --- | --- |
| Recent trades | 10 rows, 16 seconds of BTC | 129,672 fill rows for 3 hours in 3 requests |
| Buy and sell split | Trades feed carries the taker side; you total it yourself | `/cvd` returns `buy_volume`, `sell_volume`, `delta` per bucket |
| CVD history | Starts when your script starts | Daily buckets from 19 April 2023 |
| One week, hourly | Not possible after the fact | 168 buckets, 1 request, 0.12 s |
| Order activity counts | Not offered | `/orders/BTC/flow`, from 10 March 2026 |
| Cost | Free, no key | Free plan: last 30 days. Longer history on paid plans. |

If you only need a live CVD for the next hour, the native WebSocket is enough and free. You need 0xArchive when the chart should open already filled in, or when the question is about last week.

## Step 6: draw the dashboard

Four panels on one time axis: price, taker buy and sell bars, CVD rebuilt from `delta`, and cancel events per fill event. This script makes the same three requests as Step 5 and writes an interactive `dashboard.html` (`pip install requests plotly`):

```
"""dashboard.py: a four-panel BTC order flow dashboard, 1 to 7 November 2026, hourly. Writes dashboard.html.
pip install requests plotly. Set OXARCHIVE_API_KEY."""
import os, datetime as dt, itertools, requests
import plotly.graph_objects as go
from plotly.subplots import make_subplots

H = {"X-API-Key": os.environ["OXARCHIVE_API_KEY"]}
B = "https://api.0xarchive.io/v1/hyperliquid"
ms = lambda *a: int(dt.datetime(*a, tzinfo=dt.timezone.utc).timestamp() * 1000)
S, E = ms(2026, 11, 1), ms(2026, 11, 7, 23)
get = lambda p, **q: requests.get(f"{B}{p}", headers=H, params=q, timeout=60).json()["data"]
cvd = get("/cvd/BTC", start=S, end=E, interval="1h", limit=10000)
flow = get("/orders/BTC/flow", start=S, end=E + 3_600_000, interval="1h", limit=10000)
candles = get("/candles/BTC", start=S, end=E, interval="1h", limit=10000)

t = [b["timestamp"] for b in cvd]
deltas = [b["delta"] for b in cvd]
running = list(itertools.accumulate(deltas))                   # CVD over the whole window, not per page
churn = [f["orders_canceled"] / f["orders_filled"] if f["orders_filled"] else None for f in flow]

fig = make_subplots(rows=4, cols=1, shared_xaxes=True, vertical_spacing=0.04,
                    subplot_titles=("BTC price (USD)", "Taker buy and sell (USD)", "CVD (USD)", "Cancel events per fill event"))
fig.add_trace(go.Scatter(x=[c["timestamp"] for c in candles], y=[c["close"] for c in candles], name="close"), 1, 1)
fig.add_trace(go.Bar(x=t, y=[b["buy_volume"] for b in cvd], name="taker buy", marker_color="#22c55e"), 2, 1)
fig.add_trace(go.Bar(x=t, y=[-b["sell_volume"] for b in cvd], name="taker sell", marker_color="#c6a837"), 2, 1)
fig.add_trace(go.Scatter(x=t, y=running, name="CVD"), 3, 1)
fig.add_trace(go.Scatter(x=[f["timestamp"] for f in flow], y=churn, name="cancels per fill"), 4, 1)
fig.update_layout(height=1000, barmode="relative", showlegend=False)
fig.write_html("dashboard.html")

print(len(cvd), "hours")
print(f"CVD at the end of the week: {running[-1] / 1e6:,.0f} million USD")
ok = [c for c in churn if c is not None]
print(f"cancels per fill: {min(ok):.0f} to {max(ok):.0f}")
```

```
168 hours
CVD at the end of the week: -468 million USD
cancels per fill: 24 to 157
```

To keep it live, load the week from history once, then append each closed hour. The chart below is the same week, drawn in our own style:

![Four hourly BTC panels, 1 to 7 November 2026. Price rises above 80,000 on 5 November, then drops to about 75,850. Taker volume spikes near 600 million dollars an hour both ways. CVD ends the week at minus 468 million. Cancel events per fill event range 24 to 157, median 79.](https://api.0xarchive.io/cms-assets/2026/11/97c52236-25bf-48db-bd67-fd805ca261c4.webp)

BTC on Hyperliquid, hourly, 1 to 7 Nov 2026 UTC. Source: 0xArchive `/cvd`, `/orders/{symbol}/flow` and `/candles`.

### How to read it

- **CVD with price:** when price and CVD rise together, takers bought more than they sold through the move. When price rises while CVD falls, takers sold more than they bought and price rose anyway. CVD does not say why: check the book and the order flow panel before you call it absorption.
- **Taker bars:** one-sided spikes mark the hours someone paid to trade now. Compare their size with the hour's book depth.
- **Cancel events per fill event:** a jump means quotes are being pulled and replaced faster than they trade. It counts events, not cancelled size. In the week above it ranged from 24 to 157.
- **What to put next to it:** resting depth from an [order book heatmap](https://0xarchive.io/resources/market-data-guides/hyperliquid-order-book-heatmap) , large accounts from a [whale tracker](https://0xarchive.io/resources/market-data-guides/hyperliquid-whale-tracker) , and [projected forced-liquidation price levels](https://0xarchive.io/resources/market-data-guides/liquidation-heatmap) . Each is its own data source, not something to derive from CVD.

## What to know before you build

- Taker side, not aggressor guesswork: `crossed: true` marks the taker row in fills, and the native trade `side` is the taker side.
- `/cvd` and `/orders/flow` use different timestamp formats and different `end` rules. Normalise them before you join.
- Rebuild `cumulative_delta` from `delta` whenever you page.
- Order flow counts events, not size. Do not add `orders_filled` to volume.
- Treat CVD before 22 March 2025 as partial. Order flow starts 10 March 2026.
- CVD volumes are notional USD. Each trade counts once, on its taker's side, so buy plus sell equals the market's traded volume.

## FAQ

### Does Hyperliquid publish CVD?

No. Its API publishes trades with the taker side, through `recentTrades` (the latest 10) and the `trades` WebSocket, so a CVD built on it starts when you subscribe. 0xArchive serves it ready-made at `/v1/hyperliquid/cvd/{symbol}`.

### How do I calculate CVD on Hyperliquid?

Add taker buy notional and subtract taker sell notional, bucket by bucket, then take a running total (`itertools.accumulate` in Python). From the native trades feed, `side` is the taker side. Or call `/v1/hyperliquid/cvd/{symbol}`, which returns the buckets and the delta.

### How far back does Hyperliquid trade data go?

Hyperliquid's `recentTrades` returned ten rows. Hyperliquid also writes raw fills by block to a requester-pays S3 bucket, `hl-mainnet-node-data`, which you download and parse yourself. 0xArchive serves Hyperliquid trades by symbol and time window from 15 April 2023, and its API reference marks history before 22 March 2025 as incomplete.

### What is the difference between delta and cumulative delta?

Delta is one bucket's taker buy notional minus its taker sell notional. Cumulative delta, or CVD, is the running total of those deltas from the start of your window, so its level depends on where you start and its slope is what you read.

### Is order flow the same as buy and sell volume?

No. In 0xArchive, order flow counts order placements, cancellations, fills and triggers per bucket. Buy and sell volume comes from CVD.

### Can I get CVD for any interval?

Yes: 1m, 5m, 15m, 30m, 1h, 4h, 1d and 1w. Buckets of 1h and longer roll up hourly totals.

## Next steps

- Pair flow with the book: [build an order book heatmap](https://0xarchive.io/resources/market-data-guides/hyperliquid-order-book-heatmap) .
- Use the same data to test a signal: [build a Hyperliquid trading bot](https://0xarchive.io/resources/market-data-guides/hyperliquid-trading-bot) .
- Read [what tick data is](https://0xarchive.io/resources/market-data-guides/what-is-tick-data) and the [historical data guide for backtesting](https://0xarchive.io/resources/market-data-guides/hyperliquid-historical-data-api-for-backtesting) .
- Browse [trades](https://0xarchive.io/data-types/trades) , [order flow](https://0xarchive.io/data-types/order-flow) and [candles](https://0xarchive.io/data-types/candles) .
- Check the [CVD](https://docs.0xarchive.io/api-reference/hyperliquid--convenience/get-hyperliquid-cumulative-volume-delta) and [order flow](https://docs.0xarchive.io/api-reference/hyperliquid--orders/get-hyperliquid-order-flow) references.
- See [Hyperliquid data](https://0xarchive.io/hyperliquid) and [pricing](https://0xarchive.io/pricing) .

Tested 8 November 2026 on BTC.
