Skip to content

Negative Volume Index (NVI) ​

Paul Dysart's cumulative index (popularised by Norman Fosback) that only updates when volume contracts. The hypothesis is that smart-money accumulation happens on quiet days, so NVI tracks the "smart money" leg of price action while ignoring volume-spike days that retail tends to chase.

Quick reference ​

ItemValue
FamilyVolume
Input typeCandle (uses close, volume)
Output typef64
Output rangeunbounded (anchored at 1000.0)
Default parametersnone
Warmup period2
Interpretation"Smart money" trend on quiet days

Formula ​

Seed: NVI = 1000.0

NVI_t = NVI_{t-1} · (1 + (close_t - close_{t-1}) / close_{t-1})
        if volume_t < volume_{t-1}

NVI_t = NVI_{t-1}    otherwise

The starting value 1000 matches Fosback's textbook convention. See crates/wickra-core/src/indicators/nvi.rs.

Parameters ​

None — Nvi::new() takes no arguments.

Inputs / Outputs ​

Indicator<Input = Candle, Output = f64>. Python: Nvi().batch(close, volume) returns an array.array('d'). Node: same.

Warmup ​

warmup_period() == 2. First bar seeds; bar 2 emits the first non-trivial value.

Edge cases ​

  • Volume equal. No update — index unchanged.
  • Equal volume + equal close. No update either way.
  • Long quiet stretches. NVI moves frequently; long noisy stretches leave it unchanged for many bars.
  • Reset. Resets to 1000 and clears the prior bar.

Examples ​

Rust ​

rust
use wickra::{BatchExt, Candle, Indicator, Nvi};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let candles: Vec<Candle> = (0..20).map(|i| {
        let b = 100.0 + f64::from(i);
        let v = if i % 3 == 0 { 500.0 } else { 200.0 };  // alternating volume
        Candle::new(b, b + 0.5, b - 0.5, b, v, i as i64).unwrap()
    }).collect();
    let mut nvi = Nvi::new();
    println!("last = {:?}", nvi.batch(&candles).last());
    Ok(())
}

Python ​

python
import numpy as np
import wickra as ta

close = 100 + np.arange(20, dtype=float)
vol = np.where(np.arange(20) % 3 == 0, 500.0, 200.0)
nvi = ta.NVI()
print(nvi.batch(close, vol)[-1])

Node ​

javascript
const wickra = require('wickra');
const nvi = new wickra.NVI();
// feed c, v

Streaming ​

rust
use wickra::{Candle, Indicator, Nvi};

let mut nvi = Nvi::new();
let candle_stream: Vec<wickra::Candle> = Vec::new(); // your live OHLCV candle feed
for bar in candle_stream {
    if let Some(v) = nvi.update(bar) {
        // v is the smart-money cumulative index
    }
}

Interpretation ​

  • NVI trend. Rising NVI = price advances on quiet days = smart-money accumulation. Falling NVI = price falls on quiet days = smart-money distribution.
  • NVI vs 255-day EMA. Fosback's classic rule: NVI above its 255-day EMA = "smart money is bullish, bull market 95% likely per Fosback's stats".
  • Pair with PVI. PVI and NVI together cover all-day flow. Most informative when they diverge.

Common pitfalls ​

  • Frequency dependence. "Quiet day" means a lower-volume bar than the immediately prior bar. Intraday NVI on minute bars behaves differently from daily NVI.
  • Volume data quality. Stale or aggregated volume produces spurious NVI moves. Best on bar-level direct exchange data.
  • Anchor 1000. Relative magnitude meaningless; only the direction and crossings of trend lines (e.g. its own EMA) matter.

References ​

  • Paul L. Dysart, 1936 — original Negative Volume Index.
  • Norman G. Fosback, Stock Market Logic (1976) — popularised NVI and PVI together as a smart-money / crowd analysis system.

See also ​

  • Pvi — complementary crowd-money cousin.
  • Obv — simpler all-day cumulative volume.
  • Adl — alternative cumulative flow.
  • Indicators-Overview — full taxonomy.