Skip to content

VWAP (Volume-Weighted Average Price)

The volume-weighted mean of typical price; the institutional benchmark for "fair" intraday execution. Wickra ships both the unbounded cumulative session VWAP and a finite-window RollingVwap.

This page documents two distinct public types — jump straight to Vwap (cumulative) or RollingVwap (finite window).

Quick reference

ItemValue
FamilyVolume
Input typeCandle (uses high, low, close, volume)
Output typef64
Output rangeunbounded (price-units)
Default parametersnone for Vwap; period required for RollingVwap
Warmup period1 for Vwap, period for RollingVwap
Interpretationintraday fair-price benchmark for execution

Formula

Both variants use the typical price tp_t = (H_t + L_t + C_t) / 3 (see Candle::typical_price in crates/wickra-core/src/ohlcv.rs:104-108).

Cumulative VWAP:

VWAP_t = ( Σ_{i=1..t} tp_i * v_i ) / ( Σ_{i=1..t} v_i )

Rolling VWAP over the last period candles:

RollingVWAP_t = ( Σ_{i=t-period+1..t} tp_i * v_i ) / ( Σ_{i=t-period+1..t} v_i )

Both forms gate their output: when the relevant volume sum is 0.0, no value is emitted (vwap.rs:50, 121).


Vwap (cumulative)

The session VWAP. State grows forever; call reset() at session boundaries (e.g. the start of the trading day) to restart accumulation.

Parameters

Vwap::new() takes no parameters. Python: wickra.VWAP(). Node: new w.VWAP().

Inputs / Outputs

rust
use wickra::{Indicator, Vwap, Candle};
// Vwap: Input = Candle, Output = f64
const _: fn(&mut Vwap, Candle) -> Option<f64> = <Vwap as Indicator>::update;
  • Rust input. A full Candle; the indicator multiplies typical_price() * volume and accumulates.
  • Python batch. VWAP.batch(high, low, close, volume) returns a 1-D np.ndarray with NaN for any prefix where the cumulative volume is still 0.
  • Node batch. vwap.batch(high, low, close, volume) returns Array<number> with NaN for the same prefix.

Warmup

warmup_period() == 1. Provided the first candle has positive volume, the indicator emits on tick 1. If the first k candles all have volume == 0, no output is emitted until the first candle with non-zero volume — RollingVwap's warmup gating is independent of this volume-gating logic and applies on top of it.

Edge cases

  • Zero-volume bar. A candle with volume == 0 does not advance the running sums in any visible way and (if it is the first such bar the indicator has seen) keeps the output at None. The implementation short-circuits with if self.sum_v == 0.0 { return None; } (vwap.rs:50).
  • Constant input. Identical candles produce a flat VWAP equal to their typical price.
  • Session boundaries. There is no automatic reset; the caller is responsible for invoking reset() at the start of each new session.
  • NaN / infinity. Candle::new rejects non-finite OHLCV values before they can reach the indicator.
  • Reset. reset() zeroes both running sums and unsets the has_emitted flag.

Examples

Rust

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

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let candles = vec![
        Candle::new(10.0, 10.0, 10.0, 10.0, 1.0, 0)?, // tp = 10
        Candle::new(20.0, 20.0, 20.0, 20.0, 3.0, 0)?, // tp = 20
        Candle::new(30.0, 30.0, 30.0, 30.0, 1.0, 0)?, // tp = 30
        Candle::new(40.0, 40.0, 40.0, 40.0, 2.0, 0)?, // tp = 40
    ];
    let mut v = Vwap::new();
    println!("{:?}", v.batch(&candles));
    Ok(())
}

Output:

[Some(10.0), Some(17.5), Some(20.0), Some(25.714285714285715)]

Hand check at t = 2: (10*1 + 20*3) / (1+3) = 70/4 = 17.5. At t = 4: (10*1 + 20*3 + 30*1 + 40*2) / (1+3+1+2) = 180/7 ≈ 25.7142857.

Python

python
import numpy as np
import wickra as ta

vw = ta.VWAP()
h = np.array([10.0, 20.0, 30.0, 40.0])
l = np.array([10.0, 20.0, 30.0, 40.0])
c = np.array([10.0, 20.0, 30.0, 40.0])
v = np.array([ 1.0,  3.0,  1.0,  2.0])
print(vw.batch(h, l, c, v))

Output:

[10.         17.5        20.         25.71428571]

Node

js
const w = require('wickra');

const vw = new w.VWAP();
console.log(vw.batch(
  [10, 20, 30, 40],
  [10, 20, 30, 40],
  [10, 20, 30, 40],
  [ 1,  3,  1,  2],
));

Output:

[ 10, 17.5, 20, 25.714285714285715 ]

RollingVwap (finite window)

A rolling-window variant for streaming bots that want a finite-memory fair-price benchmark instead of an unbounded session aggregate.

Parameters

NameTypeDefaultConstraintSource
periodusize(no default)> 0RollingVwap::new (vwap.rs:89)

period == 0 returns Error::PeriodZero. RollingVwap is exposed in all four bindings: as RollingVwap in Rust and RollingVWAP in Python, Node and WASM. The plain VWAP class in each binding remains the cumulative form; RollingVWAP is the finite-window variant.

Inputs / Outputs

rust
use wickra::{Indicator, RollingVwap, Candle};
// RollingVwap: Input = Candle, Output = f64
const _: fn(&mut RollingVwap, Candle) -> Option<f64> = <RollingVwap as Indicator>::update;

The window stores (typical_price * volume, volume) pairs and runs incremental sum_pv / sum_v aggregates, so each update is O(1).

Warmup

warmup_period() == period. The first period - 1 candles return None; the period-th candle emits the first value provided the rolling volume sum is positive. If the entire window has volume == 0, the indicator stays at None.

Edge cases

  • Window slides. Once window.len() == period, the oldest (pv, v) pair is subtracted from the running sums before the new pair is added.
  • Zero-volume window. If every candle in the window has zero volume, sum_v == 0 and the indicator suppresses output until a positive-volume candle is in scope.
  • Reset. reset() clears the window and both running sums.
  • is_ready(). Returns true only when the window is full andsum_v > 0 (vwap.rs:138).

Examples

Rust

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

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let candles = vec![
        Candle::new(10.0, 10.0, 10.0, 10.0, 1.0, 0)?,
        Candle::new(20.0, 20.0, 20.0, 20.0, 3.0, 0)?,
        Candle::new(30.0, 30.0, 30.0, 30.0, 1.0, 0)?,
        Candle::new(40.0, 40.0, 40.0, 40.0, 2.0, 0)?,
    ];
    let mut rv = RollingVwap::new(3)?;
    println!("{:?}", rv.batch(&candles));
    Ok(())
}

Output:

[None, None, Some(20.0), Some(28.333333333333332)]

Hand check at t = 3 with window [10@1, 20@3, 30@1]: (10 + 60 + 30) / (1+3+1) = 100/5 = 20.0. At t = 4 with window [20@3, 30@1, 40@2]: (60 + 30 + 80) / (3+1+2) = 170/6 ≈ 28.333.

Python

python
import numpy as np
import wickra as ta

high   = np.array([10.0, 20.0, 30.0, 40.0])
low    = np.array([10.0, 20.0, 30.0, 40.0])
close  = np.array([10.0, 20.0, 30.0, 40.0])
volume = np.array([ 1.0,  3.0,  1.0,  2.0])

rv = ta.RollingVWAP(3)
print(rv.batch(high, low, close, volume))
# [nan, nan, 20.0, 28.333333333333332]

Node

js
const { RollingVWAP } = require('wickra');

const high   = [10, 20, 30, 40];
const low    = [10, 20, 30, 40];
const close  = [10, 20, 30, 40];
const volume = [ 1,  3,  1,  2];

const rv = new RollingVWAP(3);
console.log(rv.batch(high, low, close, volume));
// [ NaN, NaN, 20, 28.333333333333332 ]

WASM

html
<script type="module">
  import init, { RollingVWAP } from './pkg/wickra_wasm.js';
  await init();
  const rv = new RollingVWAP(3);
  const out = rv.batch(
    new Float64Array([10, 20, 30, 40]),
    new Float64Array([10, 20, 30, 40]),
    new Float64Array([10, 20, 30, 40]),
    new Float64Array([ 1,  3,  1,  2]),
  );
  console.log(Array.from(out));
  // [ NaN, NaN, 20, 28.333333333333332 ]
</script>

Interpretation

  • Execution benchmark. "Beat VWAP" is the canonical buy-side execution mandate: an aggressive algo that ends up paying below VWAP on the day is considered to have earned alpha relative to a passive participation strategy.
  • Mean reversion. Intraday strategies often fade extensions away from VWAP, treating the VWAP line as a magnet.
  • Trend filter. Some systems trade only longs above VWAP and only shorts below it; the line acts as a session-aware bias toggle.

Common pitfalls

  • Forgetting to reset. Call reset() at session start (or on each new trading day) — otherwise you average yesterday's tape into today's signal and the line drifts permanently behind current price action.
  • Zero-volume warmup. Several common data sources include pre-session candles with volume = 0 for "no print this minute". Cumulative VWAP returns None until at least one positive-volume candle has been seen; downstream code should treat None / NaN / null as "not yet ready," not as "VWAP is zero."
  • Typical price vs close. Wickra uses typical price (H + L + C) / 3, not close. A naive implementation that uses close will produce noticeably different numbers on bars with wide intraday ranges.

References

  • The VWAP construct emerged in institutional execution literature in the late 1980s and early 1990s; it has no single attributed inventor. The textbook reference for its role as an execution benchmark is Bertsimas & Lo, "Optimal control of execution costs," Journal of Financial Markets, 1998.

See also

  • OBV — cumulative signed-volume measure that pairs well with VWAP as a divergence flag.
  • MFI — money-flow oscillator that also blends typical price with volume.
  • Bollinger Bands — non-volume volatility envelope, often layered alongside VWAP on intraday charts.