Volume Price Trend (VPT)

Overview

The Volume Price Trend indicator accumulates volume weighted by percentage price changes to track whether volume flows align with price movements, though its exact origins remain disputed with various sources crediting different creators from the 1960s. VPT multiplies each period's volume by the percentage change in price and adds this to a running total, creating a cumulative line that rises when buying pressure dominates and falls during distribution phases. Unlike On-Balance Volume which treats all volume equally regardless of price movement magnitude, VPT proportionally weights volume based on how much prices actually moved, providing a more nuanced view of accumulation and distribution. The indicator excels at confirming price trends through volume analysis because genuine moves typically show VPT moving in the same direction as price, while divergences often warn of weakening momentum. Traders watch for VPT breaking through support or resistance levels ahead of price action, as volume often leads price at turning points. The cumulative nature means VPT has no bounded range, allowing it to capture long term volume trends that oscillators might miss while still responding quickly to shifts in buying or selling pressure.

Implementation Examples

Compute cumulative VPT from slices or candles:

use vectorta::indicators::vpt::{vpt, VptInput, VptParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using slices (price and volume)
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let volume = vec![1200.0, 1500.0, 1300.0, 1800.0, 1600.0, 1700.0];
let input = VptInput::from_slices(&prices, &volume);
let result = vpt(&input)?; // VptOutput { values }

// Using Candles with default source ("close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = VptInput::with_default_candles(&candles);
let result = vpt(&input)?;

// Access the VPT values (index 0 is NaN by design)
for value in result.values {
    println!("VPT: {}", value);
}

API Reference

Input Methods
// From price and volume slices
VptInput::from_slices(&[f64], &[f64]) -> VptInput

// From candles with custom price source (e.g., "close", "hlc3")
VptInput::from_candles(&Candles, &str) -> VptInput

// From candles with defaults (source = "close")
VptInput::with_default_candles(&Candles) -> VptInput
Parameters Structure
#[derive(Debug, Clone, Default)]
pub struct VptParams; // No configurable parameters
Output Structure
pub struct VptOutput {
    pub values: Vec<f64>, // cumulative VPT; index 0 is NaN
}
Validation, Warmup & NaNs
  • price.len() == volume.len() and non‑empty; otherwise VptError::EmptyData.
  • Counts valid (non‑NaN) pairs; if zero → VptError::AllValuesNaN; if fewer than 2 → VptError::NotEnoughValidData.
  • Warmup: index 0 is always NaN. First finite output occurs at the earliest i ≥ 1 where P[i−1] finite and non‑zero, P[i] finite, V[i] finite.
  • Subsequent NaNs propagate; division by zero is treated as invalid and yields NaN for that step.
Error Handling
use vectorta::indicators::vpt::{vpt, VptError};

match vpt(&input) {
    Ok(output) => process(output.values),
    Err(VptError::EmptyData) => eprintln!("empty or mismatched input"),
    Err(VptError::AllValuesNaN) => eprintln!("all values are NaN"),
    Err(VptError::NotEnoughValidData) => eprintln!("need at least 2 valid points"),
    Err(VptError::InvalidLength { expected, got }) => eprintln!("invalid output length: expected={}, got={}", expected, got),
}
Python

Available when built with the Python feature:

from vectorta import vpt, vpt_batch, VptStream
import numpy as np

price = np.array([100.0, 102.0, 101.5, 103.0, 105.0])
volume = np.array([1200.0, 1500.0, 1300.0, 1800.0, 1600.0])

# Quick calculation (kernel optional: 'auto', 'scalar', 'avx2', ...)
y = vpt(price, volume, kernel=None)

# Streaming
s = VptStream()
for p, v in zip(price, volume):
    val = s.update(float(p), float(v))  # first produced value may be NaN

# Batch (VPT has no tunable parameters; returns dict with 'values' and empty 'params')
res = vpt_batch(price, volume, kernel='auto')
vals = res['values'][0]  # rows=1, cols=len(price)
JavaScript / WASM

Basic Usage

import { vpt_js } from 'vectorta-wasm';

const price = new Float64Array([100, 102, 101.5, 103, 105]);
const volume = new Float64Array([1200, 1500, 1300, 1800, 1600]);

const vptValues = vpt_js(price, volume); // Float64Array
console.log('VPT:', vptValues);

Memory‑Efficient Operations

import { vpt_alloc, vpt_free, vpt_into, memory } from 'vectorta-wasm';

const len = price.length;
const pricePtr = vpt_alloc(len);
const volumePtr = vpt_alloc(len);
const outPtr = vpt_alloc(len);

new Float64Array(memory.buffer, pricePtr, len).set(price);
new Float64Array(memory.buffer, volumePtr, len).set(volume);

// Compute into pre-allocated buffer
vpt_into(pricePtr, volumePtr, outPtr, len);
const out = new Float64Array(memory.buffer, outPtr, len).slice();

vpt_free(pricePtr, len);
vpt_free(volumePtr, len);
vpt_free(outPtr, len);

Batch Processing

import { vpt_batch } from 'vectorta-wasm';

// VPT has a single row (no parameter grid). Return shape is project-specific.
const out = vpt_batch(price, volume, {/* kernel/config if applicable */});
console.log(out);

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators