Volume Price Confirmation Indicator (VPCI)

Parameters: short_range = 5 | long_range = 25

Overview

Buff Dormeier developed the Volume Price Confirmation Indicator to reveal the proportional imbalances between price trends and volume-weighted price trends, winning the prestigious Charles H. Dow Award in 2007 for this innovative work. The indicator measures the intrinsic relationship between price and volume by comparing volume-weighted moving averages against simple moving averages over identical periods, exposing whether price movements have proper volume support. When VPCI rises, it indicates that volume confirms the price trend as traders participate with conviction, while declining VPCI values suggest weakening participation that often precedes trend reversals. Dormeier built VPCI on his earlier work with volume-weighted moving averages from the 1990s, believing that volume represents the force of investor conviction and typically leads price action. The indicator excels at identifying divergences where price continues trending while volume support weakens, providing advance warning of potential exhaustion points. By incorporating both the main VPCI line and its smoothed counterpart VPCIS, traders can filter out noise while maintaining sensitivity to genuine volume-price relationships that drive sustainable market moves.

Implementation Examples

Compute VPCI and VPCIS from slices or candles:

use vectorta::indicators::vpci::{vpci, VpciInput, VpciParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From close + volume slices
let close = vec![101.0, 102.0, 100.5, 103.0, 104.0];
let volume = vec![2000.0, 2500.0, 1800.0, 3000.0, 3200.0];
let params = VpciParams { short_range: Some(5), long_range: Some(25) }; // defaults 5/25
let input = VpciInput::from_slices(&close, &volume, params);
let out = vpci(&input)?; // out.vpci, out.vpcis

// From Candles with defaults (sources: "close" and "volume")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = VpciInput::with_default_candles(&candles);
let out = vpci(&input)?;

// Access values
for (v, s) in out.vpci.iter().zip(out.vpcis.iter()) {
    println!("VPCI: {v}, VPCIS: {s}");
}

API Reference

Input Methods
// From close + volume slices
VpciInput::from_slices(&[f64], &[f64], VpciParams) -> VpciInput

// From candles with custom sources
VpciInput::from_candles(&Candles, &str /*close_source*/, &str /*volume_source*/, VpciParams) -> VpciInput

// From candles with defaults ("close"/"volume", short=5, long=25)
VpciInput::with_default_candles(&Candles) -> VpciInput
Parameters Structure
#[derive(Debug, Clone)]
pub struct VpciParams {
    pub short_range: Option<usize>, // Default: 5
    pub long_range: Option<usize>,  // Default: 25
}
Output Structure
#[derive(Debug, Clone)]
pub struct VpciOutput {
    pub vpci: Vec<f64>,  // main oscillator
    pub vpcis: Vec<f64>, // short-term, volume-weighted smoothing
}
Validation, Warmup & NaNs
  • close.len() == volume.len() or VpciError::MismatchedInputLengths.
  • short_range > 0, long_range > 0, and short_range ≤ long_range; otherwise VpciError::InvalidRange.
  • If short_range or long_range exceeds input length, VpciError::InvalidRange.
  • Warmup: first valid index is the first point where both close and volume are finite; outputs before first + long_range − 1 are NaN.
  • Zero denominators produce NaN (e.g., VWMA when volume sum is 0 over a window).
  • If not enough valid data after the first finite pair (len − first < long_range), VpciError::NotEnoughValidData.
  • Streaming: VpciStream::update returns None until count ≥ long_range; then returns (vpci, vpcis) for the newest point.
Error Handling
use vectorta::indicators::vpci::{vpci, VpciError};

match vpci(&input) {
    Ok(output) => process(output.vpci, output.vpcis),
    Err(VpciError::AllValuesNaN) =>
        eprintln!("All close or volume values are NaN"),
    Err(VpciError::InvalidRange { period, data_len }) =>
        eprintln!("Invalid range: period = {period}, data length = {data_len}"),
    Err(VpciError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {needed} data points after first valid; only {valid}"),
    Err(VpciError::SmaError(e)) =>
        eprintln!("SMA error: {e}"),
    Err(VpciError::MismatchedInputLengths { close_len, volume_len }) =>
        eprintln!("close len {close_len} != volume len {volume_len}"),
    Err(VpciError::MismatchedOutputLengths { vpci_len, vpcis_len, data_len }) =>
        eprintln!("output lens (vpci={vpci_len}, vpcis={vpcis_len}) must equal data_len={data_len}"),
    Err(VpciError::KernelNotAvailable) =>
        eprintln!("Requested kernel not available for this build/CPU"),
}

Python Bindings

Basic Usage

Compute VPCI and VPCIS from NumPy arrays:

import numpy as np
from vectorta import vpci

close = np.array([101.0, 102.0, 100.5, 103.0, 104.0], dtype=float)
volume = np.array([2000.0, 2500.0, 1800.0, 3000.0, 3200.0], dtype=float)

# Defaults are short=5, long=25; specify explicitly if desired
vpci_vals, vpcis_vals = vpci(close, volume, short_range=5, long_range=25)

# Optional: select CPU kernel ("auto", "scalar", "avx2", "avx512" — availability dependent)
vpci_vals, vpcis_vals = vpci(close, volume, short_range=5, long_range=25, kernel="avx2")

print("VPCI:", vpci_vals)
print("VPCIS:", vpcis_vals)
Streaming Real-time Updates

Process real-time (close, volume) ticks:

from vectorta import VpciStream

stream = VpciStream(short_range=5, long_range=25)

for close_val, volume_val in feed:
    result = stream.update(close_val, volume_val)
    if result is not None:
        vpci_val, vpcis_val = result
        handle(vpci_val, vpcis_val)
Batch Parameter Optimization

Sweep short/long ranges and collect results:

import numpy as np
from vectorta import vpci_batch

close = np.array([...], dtype=float)
volume = np.array([...], dtype=float)

results = vpci_batch(
    close,
    volume,
    short_range_tuple=(3, 10, 1),
    long_range_tuple=(15, 60, 5),
    kernel="auto"
)

# Results dict keys: 'vpci' [rows x cols], 'vpcis' [rows x cols],
#                    'short_ranges' [rows], 'long_ranges' [rows]
vpci_matrix = results['vpci']
vpcis_matrix = results['vpcis']
shorts = results['short_ranges']
longs = results['long_ranges']
CUDA Acceleration

CUDA support for VPCI is coming soon.

JavaScript/WASM Bindings

Basic Usage

Compute VPCI and VPCIS in JS/TS:

import { vpci_js } from 'vectorta-wasm';

const close = new Float64Array([101, 102, 100.5, 103, 104]);
const volume = new Float64Array([2000, 2500, 1800, 3000, 3200]);

const { vpci, vpcis } = vpci_js(close, volume, 5, 25) as { vpci: Float64Array, vpcis: Float64Array };
console.log('VPCI:', vpci);
console.log('VPCIS:', vpcis);
Memory-Efficient Operations

Use zero-copy operations for large datasets:

import { vpci_alloc, vpci_free, vpci_into, memory } from 'vectorta-wasm';

const len = close.length;
const closePtr = vpci_alloc(len);
const volumePtr = vpci_alloc(len);
const vpciPtr = vpci_alloc(len);
const vpcisPtr = vpci_alloc(len);

// Copy inputs into WASM memory
new Float64Array(memory.buffer, closePtr, len).set(close);
new Float64Array(memory.buffer, volumePtr, len).set(volume);

// Compute directly into output buffers
vpci_into(closePtr, volumePtr, vpciPtr, vpcisPtr, len, 5, 25);

// Read results (slice() to copy out)
const vpciOut = new Float64Array(memory.buffer, vpciPtr, len).slice();
const vpcisOut = new Float64Array(memory.buffer, vpcisPtr, len).slice();

// Free allocated memory
vpci_free(closePtr, len);
vpci_free(volumePtr, len);
vpci_free(vpciPtr, len);
vpci_free(vpcisPtr, len);
Batch Processing

Test multiple parameter combinations efficiently:

import { vpci_batch } from 'vectorta-wasm';

const cfg = { short_range: [3, 10, 1], long_range: [15, 60, 5] };
const res = vpci_batch(close, volume, cfg) as {
  vpci: Float64Array,
  vpcis: Float64Array,
  combos: Array<{ short_range?: number, long_range?: number }>,
  rows: number,
  cols: number
};

// Flattened output: row-major [rows * cols]
function row(slice: Float64Array, r: number, cols: number) {
  const start = r * cols; return slice.slice(start, start + cols);
}

// Example: first combo
const firstVpci = row(res.vpci, 0, res.cols);
const firstVpcis = row(res.vpcis, 0, res.cols);
// Parameters for that row
const firstParams = res.combos[0];

Performance Analysis

Comparison:
View:
Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05

Related Indicators