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; otherwiseVptError::EmptyData.- Counts valid (non‑NaN) pairs; if zero →
VptError::AllValuesNaN; if fewer than 2 →VptError::NotEnoughValidData. - Warmup: index
0is alwaysNaN. First finite output occurs at the earliesti ≥ 1whereP[i−1]finite and non‑zero,P[i]finite,V[i]finite. - Subsequent
NaNs propagate; division by zero is treated as invalid and yieldsNaNfor 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
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
Related Indicators
Accumulation/Distribution
Technical analysis indicator
Accumulation/Distribution Oscillator
Technical analysis indicator
Balance of Power
Technical analysis indicator
Chaikin Flow Oscillator
Technical analysis indicator
Elder Force Index
Technical analysis indicator
Ease of Movement
Technical analysis indicator