Skip to content

CumulativeVolumeDelta ​

The running total of signed trade volume since the last reset. A rising line is net buying, a falling line net selling; divergence from price is the classic absorption signal.

Quick reference ​

ItemValue
FamilyMicrostructure
Input typeTrade — an executed trade with an aggressor side
Output typef64
Output rangeunbounded (running total)
Default parametersnone
Warmup period1
InterpretationCumulative net order flow

Formula ​

CVDₜ = CVDₜ₋₁ + sizeₜ · (+1 if buy, −1 if sell)

Stateful running sum; O(1) per trade. Call reset() at each session boundary to re-anchor at zero. See crates/wickra-core/src/indicators/cvd.rs.

Parameters ​

None. Construct with CumulativeVolumeDelta::new().

Inputs / Outputs ​

Indicator<Input = Trade, Output = f64>. Bindings: update(price, size, is_buy); Python / Node batch take three equal-length arrays → 1-D array. WASM streaming-only.

Warmup ​

warmup_period() == 1.

Edge cases ​

  • Reset. Re-anchors the running total at zero (new session / bar).
  • Unbounded by design — it is a cumulative line, not an oscillator.

Examples ​

Rust ​

rust
use wickra::{CumulativeVolumeDelta, Indicator, Side, Trade};

let mut cvd = CumulativeVolumeDelta::new();
assert_eq!(cvd.update(Trade::new(100.0, 5.0, Side::Buy, 0).unwrap()), Some(5.0));
assert_eq!(cvd.update(Trade::new(100.0, 2.0, Side::Sell, 1).unwrap()), Some(3.0));

Python ​

python
import wickra as ta
cvd = ta.CumulativeVolumeDelta()
print(cvd.update(100.0, 5.0, True))   # 5.0
print(cvd.update(100.0, 2.0, False))  # 3.0

Node ​

js
const { CumulativeVolumeDelta } = require('wickra');
const cvd = new CumulativeVolumeDelta();
cvd.update(100, 5, true);  // 5
console.log(cvd.update(100, 2, false)); // 3

Interpretation ​

CVD is the cumulative footprint of aggression — the running tug-of-war between buyers lifting offers and sellers hitting bids.

  • Rising. Net aggressive buying; demand is in control of the tape.
  • Falling. Net aggressive selling.
  • Divergence — the key signal. When price makes a new high but CVD does not, buyers are being absorbed by passive sellers without moving price — a classic exhaustion / absorption warning. The mirror holds for a new price low on flat CVD. Confirmation comes when price then reverses on the lighter side.

Reset per session so the line reflects the current day's flow rather than an unbounded all-time total.

Common pitfalls ​

  • The absolute level is meaningless. Only the shape and divergences carry information; the cumulative value drifts arbitrarily. Always reset() at the session (or bar) boundary so two days are comparable.
  • Aggressor flag required. CVD is only as good as the buy/sell labelling; if your feed lacks an aggressor side, infer it (tick / quote rule) first.
  • Venue-local. Flow on one exchange is not the whole market; a divergence on a single venue may simply be flow migrating elsewhere.

References ​

  • Charles M. C. Lee and Mark J. Ready, Inferring Trade Direction from Intraday Data, Journal of Finance, 1991 — signing trades into buy/sell volume.
  • David Easley, Marcos López de Prado, Maureen O'Hara, Flow Toxicity and Liquidity in a High-Frequency World, Review of Financial Studies, 2012 — cumulative signed-volume order flow.

See also ​