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
| Item | Value |
|---|---|
| Family | Volume |
| Input type | Candle (uses high, low, close, volume) |
| Output type | f64 |
| Output range | unbounded (price-units) |
| Default parameters | none for Vwap; period required for RollingVwap |
| Warmup period | 1 for Vwap, period for RollingVwap |
| Interpretation | intraday 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
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 multipliestypical_price() * volumeand accumulates. - Python batch.
VWAP.batch(high, low, close, volume)returns a 1-Dnp.ndarraywithNaNfor any prefix where the cumulative volume is still0. - Node batch.
vwap.batch(high, low, close, volume)returnsArray<number>withNaNfor 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 == 0does not advance the running sums in any visible way and (if it is the first such bar the indicator has seen) keeps the output atNone. The implementation short-circuits withif 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::newrejects non-finite OHLCV values before they can reach the indicator. - Reset.
reset()zeroes both running sums and unsets thehas_emittedflag.
Examples
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
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
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
| Name | Type | Default | Constraint | Source |
|---|---|---|---|---|
period | usize | (no default) | > 0 | RollingVwap::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
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 == 0and the indicator suppresses output until a positive-volume candle is in scope. - Reset.
reset()clears the window and both running sums. is_ready(). Returnstrueonly when the window is full andsum_v > 0(vwap.rs:138).
Examples
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
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
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
<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 = 0for "no print this minute". Cumulative VWAP returnsNoneuntil at least one positive-volume candle has been seen; downstream code should treatNone/NaN/nullas "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.