New Indicator + Tutorial: Real buy and sell volume with Volume Delta and CVD


Volume tells you how much traded, but not who was pushing. A 2,000 BTC candle can be a wave of buyers or a wave of sellers, and the volume bar looks the same either way. Order-flow traders want the split: how much was bought and how much was sold.

Today we’re adding the Volume Delta indicator to TAAPI.IO. For every candle it returns the real buy volume, the real sell volume, the difference between them (the volume delta) and its running total, the cumulative volume delta (CVD). It works on Binance and Binance Futures, through REST, bulk and WebSocket.

This post explains where the numbers come from, how traders use them, and walks you through three examples you can copy and run.

Endpoint: volumedelta

Optional inputs:

  • anchor (default: auto): where the CVD restarts at 0, in UTC: hour, day, week, month, year, or none. auto picks one that fits your timeframe: 1m → hour, 5m and 15m → day, 1h and 4h → week, 1d → month, 1w → year.

Real buy and sell volume, not an estimate

Every trade has two sides. One side was already waiting in the order book with a limit order. The other side, the taker, came in with a market order and crossed the spread. When the taker is a buyer, that’s buying pressure; when the taker is a seller, that’s selling pressure.

Binance and Binance Futures record this for every candle: how much of the volume was bought by takers. Volume Delta uses that number directly:

  • buyVolume: volume bought with market orders
  • sellVolume: volume sold with market orders (the rest of the candle’s volume)
  • delta: buyVolume − sellVolume. Positive means buyers were more aggressive.
  • deltaPercent: the delta as a percentage of the candle’s volume, from −100 to +100. 0 means an even split; +20 means 60% was bought and 40% sold.
  • cvd: the deltas added up since the anchor, for example since 00:00 UTC.

Most “buy and sell volume” indicators guess the split from the shape of the candle: a close near the high counts as mostly buying. That works on any market, and it’s what our Volume Split indicator does when nothing better is available. But it is only a guess. Across 12,000 Binance candles, the candle-shape estimate picked the right side (more buying or more selling) only 59–68% of the time. Volume Delta isn’t a guess. It’s the exchange’s own count.

As far as we know, TAAPI.IO is the only technical indicator API that serves CVD from real taker volume. You can request it in the same call as your RSI, VWAP or moving averages, or stream it next to them. Dedicated order-flow data services do sell CVD, but as a separate product you would have to combine with your indicators yourself.

How traders use Volume Delta and CVD

1. Confirming a move. When price rises and the CVD rises with it, market buyers are driving the move. That’s a healthier rally than one where price drifts up while the CVD stays flat. Many traders want the delta to agree with a breakout before they trust it.

2. Spotting absorption. Sometimes the CVD climbs hour after hour, but price doesn’t go up. Aggressive buyers keep hitting the market, and someone with large resting sell orders keeps absorbing them. Traders watch for this because absorption often comes before a move the other way. The mirror case is heavy selling that fails to push price down.

3. Catching exhaustion. In a strong trend, each new high should come with solid buying. When price makes a higher high on a smaller and smaller delta, fewer buyers are stepping up to push it further.

4. Spot vs perpetual futures. In crypto, the same coin trades on spot and on perps. A rally where spot CVD leads is usually read as real buying. A rally driven by perps alone is leverage, and a leveraged rally can reverse quickly if positions get liquidated. With Volume Delta you can compare binance and binancefutures in one request.

5. An intraday bias. With anchor=day, the CVD shows whether buyers or sellers have been more aggressive since midnight UTC. Some traders only take longs while today’s CVD is positive, and only shorts while it’s negative.

None of these is a signal on its own. Order flow shows who was aggressive, not who will win: resting limit orders can absorb any amount of market buying. Traders use it as confirmation alongside price structure, levels and trend.

Your first request

curl -G https://v2.taapi.io/indicator/volumedelta \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data-urlencode "exchange=binance" \
  --data-urlencode "symbol=BTCUSDT" \
  --data-urlencode "timeframe=1h" \
  --data-urlencode "backtrack=1"

This is a real response, for the 09:00 UTC hour on 13 November 2026:

{
  "buyVolume": [507.7988399999999],
  "sellVolume": [633.18049],
  "delta": [-125.38165000000004],
  "deltaPercent": [-10.988950167922853],
  "cvd": [1271.15886],
  "anchor": "week",
  "timestamp": [1794560400]
}

Here’s how to read it:

  • In that hour, 1,141 BTC traded. Market buyers took 508 BTC and market sellers 633 BTC, so the delta was −125 BTC and the deltaPercent −11%. Sellers had the upper hand in that hour, by a modest margin.
  • The cvd of +1,271 BTC says that across the whole week so far, since Monday 00:00 UTC, market buyers still bought about 1,271 BTC more than market sellers sold. One red hour doesn’t change the bigger picture.
  • anchor shows which anchor auto picked: on 1-hour candles, that’s the week.

Why backtrack=1? Without it you get the candle that’s still forming, and its numbers keep changing until it closes. backtrack=1 gives you the last closed candle. For a live view of the forming candle, leave it out, or stream it over WebSocket.

Units. Volumes are in the base asset: BTC for BTCUSDT, ETH for ETHUSDT. Use deltaPercent when you compare coins with each other.

Example 1: Is the move spot-led or leverage-led? (Node.js)

This compares today’s CVD on spot and on perps for a few coins, in one bulk request. anchor=day restarts the CVD at 00:00 UTC, and backtrack=1 reads it up to the last closed hour.

const API_KEY = 'YOUR_API_KEY';
const SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'XRPUSDT'];

// Today's CVD (since 00:00 UTC) up to the last closed hour, on spot and on perps
const constructs = SYMBOLS.flatMap((symbol) => ['binance', 'binancefutures'].map((exchange) => ({
  id: `${exchange}_${symbol}`,
  exchange,
  symbol,
  timeframe: '1h',
  indicators: [{ id: 'vd', indicator: 'volumedelta', anchor: 'day', backtrack: 1 }]
})));

function read(spot, perp) {
  if (spot > 0 && perp > 0) return 'buyers in control on both markets';
  if (spot < 0 && perp < 0) return 'sellers in control on both markets';
  if (spot > 0) return 'spot buying, perps selling (spot-led)';
  return 'perps buying, spot selling (leverage-led)';
}

async function run() {
  const res = await fetch('https://v2.taapi.io/bulk', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_KEY}` },
    body: JSON.stringify({ constructs })
  });
  const data = await res.json();

  for (const symbol of SYMBOLS) {
    const spot = data[`binance_${symbol}`]?.vd?.cvd?.[0];
    const perp = data[`binancefutures_${symbol}`]?.vd?.cvd?.[0];
    if (spot == null || perp == null) {
      console.log(`${symbol.padEnd(8)} no CVD yet`);
      continue;
    }
    const fmt = (v) => (v > 0 ? '+' : '') + Math.round(v).toLocaleString('en-US');
    console.log(`${symbol.padEnd(8)} spot ${fmt(spot).padStart(12)}   perps ${fmt(perp).padStart(12)}   → ${read(spot, perp)}`);
  }
}

run();

Run it with Node.js 18 or newer (node spot-vs-perps.js). When we ran it at 10:00 UTC on 13 November, the output was:

BTCUSDT  spot         -913   perps      -10,018   → sellers in control on both markets
ETHUSDT  spot      -23,153   perps     -117,593   → sellers in control on both markets
SOLUSDT  spot     -100,755   perps     -563,421   → sellers in control on both markets
XRPUSDT  spot   -1,741,929   perps  -15,218,040   → sellers in control on both markets

That morning, sellers had the upper hand everywhere, and much more so on perps. The interesting moments are when the two markets disagree, and that’s what this check is built to catch.

In the first hour after midnight UTC, the last closed hour still belongs to yesterday, so you get yesterday’s full-day CVD. From 01:00 UTC on, it’s today’s.

Example 2: An absorption detector (Python)

This compares the order flow of the last six closed hours with what price did in those hours. When market buyers took a clear majority of the volume but price didn’t rise, the buying is being absorbed. The same check works the other way for sellers.

import requests

API_KEY = 'YOUR_API_KEY'
HOURS = 6

def check(symbol, exchange='binancefutures'):
    resp = requests.post(
        'https://v2.taapi.io/bulk',
        headers={'Authorization': f'Bearer {API_KEY}'},
        json={'constructs': [{
            'id': 'c',
            'exchange': exchange,
            'symbol': symbol,
            'timeframe': '1h',
            'indicators': [
                {'id': 'vd', 'indicator': 'volumedelta', 'results': HOURS, 'backtrack': 1},
                {'id': 'price', 'indicator': 'price', 'results': HOURS + 1, 'backtrack': 1},
            ],
        }]},
    )
    data = resp.json()['c']
    vd, closes = data['vd'], data['price']['value']

    bought, sold = sum(vd['buyVolume']), sum(vd['sellVolume'])
    buy_share = bought / (bought + sold) * 100
    price_change = (closes[-1] / closes[0] - 1) * 100

    if buy_share > 55 and price_change <= 0:
        verdict = 'buying absorbed: aggressive buyers are not lifting price'
    elif buy_share < 45 and price_change >= 0:
        verdict = 'selling absorbed: aggressive sellers are not pushing price down'
    elif (buy_share > 52 and price_change > 0) or (buy_share < 48 and price_change < 0):
        verdict = 'confirmed: order flow and price agree'
    else:
        verdict = 'balanced: no clear edge'

    print(f'{symbol:<9} last {HOURS}h: {buy_share:4.1f}% bought, price {price_change:+.2f}%  → {verdict}')

for symbol in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'DOGEUSDT', 'BNBUSDT']:
    check(symbol)

The price indicator asks for one more candle than volumedelta, so the price change starts from the close just before the six-hour window. On a quiet morning, every coin came back balanced:

BTCUSDT   last 6h: 49.9% bought, price +0.21%  → balanced: no clear edge
ETHUSDT   last 6h: 50.1% bought, price +0.38%  → balanced: no clear edge
SOLUSDT   last 6h: 50.9% bought, price +2.01%  → balanced: no clear edge
DOGEUSDT  last 6h: 51.4% bought, price +1.82%  → balanced: no clear edge
BNBUSDT   last 6h: 48.4% bought, price +0.63%  → balanced: no clear edge

That’s intended: the thresholds keep it quiet unless order flow and price clearly disagree. To see it fire, we pointed the same check at BTCUSDT spot (check('BTCUSDT', 'binance'), with HOURS = 5 and backtrack moved back to the morning of 12 November), covering 06:00 to 10:59 UTC:

BTCUSDT   last 5h: 59.2% bought, price -0.17%  → buying absorbed: aggressive buyers are not lifting price

Market buyers took almost 60% of the spot volume for five hours, and price still slipped. Over the next four hours the delta flipped to −504 BTC and price fell 2.5%. On perps, the same five hours showed only 48.6% bought, so the absorption was visible on spot only. That’s another reason to check both markets. One example proves nothing, though: many absorption setups fade without a move, and a check like this tells you where to look, not what to trade.

Example 3: Who’s winning today? A buy/sell scanner (Node.js)

The daily candle that’s still forming covers today so far, in UTC. Its deltaPercent is today’s buy/sell balance, and because it’s a percentage, you can rank coins against each other:

const API_KEY = 'YOUR_API_KEY';
const SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'XRPUSDT', 'BNBUSDT', 'DOGEUSDT', 'ADAUSDT', 'LINKUSDT'];

async function run() {
  const res = await fetch('https://v2.taapi.io/bulk', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_KEY}` },
    body: JSON.stringify({
      constructs: SYMBOLS.map((symbol) => ({
        id: symbol,
        exchange: 'binancefutures',
        symbol,
        timeframe: '1d', // the daily candle that is still forming = today so far (UTC)
        indicators: [{ id: 'vd', indicator: 'volumedelta' }]
      }))
    })
  });
  const data = await res.json();

  const rows = SYMBOLS
    .map((symbol) => ({ symbol, pct: data[symbol]?.vd?.deltaPercent?.[0] }))
    .filter((row) => row.pct != null)
    .sort((a, b) => b.pct - a.pct);

  for (const { symbol, pct } of rows) {
    const bought = 50 + pct / 2; // deltaPercent 0 = 50% bought, +20 = 60% bought
    const bar = (pct > 0 ? '+' : '-').repeat(Math.min(20, Math.round(Math.abs(pct) * 2)));
    console.log(`${symbol.padEnd(9)} ${bought.toFixed(1)}% bought  ${(pct > 0 ? '+' : '') + pct.toFixed(1)}`.padEnd(36) + bar);
  }
}

run();

Output at 10:00 UTC on 13 November:

DOGEUSDT  48.6% bought  -2.8        ------
ETHUSDT   48.5% bought  -2.9        ------
XRPUSDT   48.0% bought  -4.0        --------
SOLUSDT   47.8% bought  -4.4        ---------
ADAUSDT   47.6% bought  -4.8        ----------
BTCUSDT   47.1% bought  -5.7        -----------
BNBUSDT   46.8% bought  -6.3        -------------
LINKUSDT  46.3% bought  -7.4        ---------------

Sellers led on every coin that morning, LINK the most. A few percent may not look like much, but it adds up: BTC’s −5.7 meant roughly 10,000 BTC more sold than bought on Binance Futures in ten hours. Run it every hour and watch which coins flip from red to green.

Streaming

For a live view, subscribe to volumedelta over WebSocket on 1m, 5m, 15m or 1h. You get new values for the forming candle every few seconds, which is useful for flagging a one-sided minute (for example, deltaPercent beyond ±30 on BTCUSDT) as it happens. If you’re new to streaming, start with our WebSocket tutorial.

Good to know

  • Exchanges: Volume Delta needs the exchange to report taker volume with its candles. Binance (binance) and Binance Futures (binancefutures) do. On other exchanges and US stocks, the endpoint returns an error that points you to Volume Split, which estimates the split from the candle shape. On Binance and Binance Futures, Volume Split now uses the real taker volume too.
  • History: the last 3,000 candles per timeframe, so 1m covers about two days.
  • The CVD anchor: with an anchor, the same candle always gets the same CVD, no matter when you ask. With anchor=none, the CVD adds up from the first candle the API fetched, so its level moves as new candles come in and only its changes mean anything.
  • What it misses: large players often split their orders and use limit orders, so a lot of their activity never shows up as taker volume.

Nothing in this post is financial advice.

Get started

Don’t have an API key yet? Start for free.