Why Is VWAP Important? A Pine Script v6 Deep Dive for Intraday Traders

VWAP (Volume-Weighted Average Price) is one of the most widely referenced benchmarks in intraday trading, used by institutional desks and algorithmic systems alike to assess whether a trade was executed at a favorable price relative to the day's volume-weighted mean. This article explains the mathematical foundation of VWAP, why it matters for intraday analysis, and how to implement a precise, session-aware VWAP indicator in Pine Script v6.


1. What Is VWAP? The Mathematical Definition

VWAP is defined as the cumulative sum of price × volume divided by the cumulative sum of volume, reset at the start of each trading session:

$$\text{VWAP}_t = \frac{\sum_{i=1}^{t} P_i \times V_i}{\sum_{i=1}^{t} V_i}$$

Where:

  • $P_i$ = Typical price at bar $i$, defined as $\frac{\text{High}_i + \text{Low}_i + \text{Close}_i}{3}$
  • $V_i$ = Volume at bar $i$
  • The summation resets at the start of each new trading session (e.g., each new day on a 1-minute chart)

This formula ensures that bars with higher volume exert a proportionally greater influence on the average price, making VWAP a volume-weighted measure rather than a simple time-weighted average.


2. Why Intraday Traders Rely on VWAP

VWAP serves several distinct analytical roles in intraday contexts:

Role Description
Execution Benchmark Institutional orders are evaluated against VWAP. Buying below VWAP is considered favorable; selling above VWAP is considered favorable.
Dynamic Support/Resistance Price frequently reacts at the VWAP line during the session, acting as a mean-reversion anchor.
Trend Confirmation Sustained price above VWAP suggests bullish intraday sentiment; sustained price below suggests bearish sentiment.
Fair Value Reference VWAP represents the average price at which all volume transacted during the session, making it a proxy for the market's consensus fair value.

3. VWAP vs. Simple Moving Average: A Key Distinction

A common misconception is that VWAP behaves like a simple moving average (SMA). The table below clarifies the structural differences:

Property VWAP SMA (e.g., 20-period)
Weighting Volume-weighted Equal-weighted
Session Reset Yes — resets each session No — rolls continuously
Lookback Window Cumulative from session open Fixed N bars
Volume Sensitivity High — volume spikes shift VWAP None
Primary Use Case Intraday execution benchmark Trend smoothing across any timeframe

4. Logic Flow: How VWAP Is Computed Bar by Bar

The following diagram illustrates the per-bar computation logic, including the session-reset mechanism:

flowchart TD A[New Bar Arrives] --> B{Is it a New Session?} B -- Yes --> C[Reset cumPV to typicalPrice x volume] C --> D[Reset cumPV2 to typicalPrice squared x volume] D --> E[Reset cumVol to volume] B -- No --> F[cumPV := cumPV + typicalPrice x volume] F --> G[cumPV2 := cumPV2 + typicalPrice squared x volume] G --> H[cumVol := cumVol + volume] E --> I{cumVol != 0?} H --> I I -- Yes --> J[VWAP = cumPV divided by cumVol] I -- No --> K[VWAP = na] J --> L[variance = cumPV2 divided by cumVol minus VWAP squared] L --> M[stdDev = sqrt of max of 0 and variance] M --> N[Upper Band = VWAP + mult x stdDev] M --> O[Lower Band = VWAP minus mult x stdDev] N --> P[Plot VWAP and Bands on Chart] O --> P K --> P

5. Pine Script v6 Implementation

5.1 Variable Persistence and the var Keyword

VWAP requires cumulative accumulators that grow throughout the session and reset only when a new session begins. In Pine Script v6, the var keyword controls when the initializer expression is evaluated: without var, the initializer (e.g., float x = close) is re-evaluated on every bar; with var, the initializer (e.g., var float x = close) is evaluated only once on bar 0, and the variable persists its value across all subsequent bars until explicitly reassigned with :=.

For VWAP, we declare accumulators with var so they are not re-initialized on every bar, and we manually reset them with := when a new session is detected:

  • var float cumPV = 0.0 — cumulative price × volume, initialized to 0.0 on bar 0 only
  • var float cumVol = 0.0 — cumulative volume, initialized to 0.0 on bar 0 only

When a new session starts (timeframe.change("D") returns true), both accumulators are reset to 0.0 via :=, then the current bar's contribution is added. This is the correct pattern for session-scoped cumulative calculations.

5.2 Full Indicator Code

🔽 [Click to expand] View Full Pine Script Code — Session VWAP with Standard Deviation Bands
//@version=6
indicator(
    title        = "Session VWAP with StdDev Bands",
    shorttitle   = "VWAP+Bands",
    overlay      = true,
    max_bars_back = 500
)

// ─── INPUTS ───────────────────────────────────────────────────────────────────
int   bandMult1 = input.int(1, title = "Band Multiplier 1", minval = 1, maxval = 5)
int   bandMult2 = input.int(2, title = "Band Multiplier 2", minval = 1, maxval = 5)
bool  showBands = input.bool(true, title = "Show Standard Deviation Bands")

// ─── SESSION DETECTION ────────────────────────────────────────────────────────
// timeframe.change("D") returns true on the first bar of each new daily session.
// This is the standard Pine v6 method for detecting session boundaries.
bool isNewSession = timeframe.change("D")

// ─── TYPICAL PRICE ────────────────────────────────────────────────────────────
// Typical price is the standard VWAP price input: (H + L + C) / 3
float typicalPrice = (high + low + close) / 3.0

// ─── CUMULATIVE ACCUMULATORS (var — initialized once on bar 0) ────────────────
// var ensures the initializer (0.0) runs only on bar 0.
// On all subsequent bars, the value persists until explicitly reassigned with :=
var float cumPV  = 0.0   // cumulative sum of (typicalPrice × volume)
var float cumVol = 0.0   // cumulative sum of volume
var float cumPV2 = 0.0   // cumulative sum of (typicalPrice² × volume) for variance

// ─── SESSION RESET + ACCUMULATION ────────────────────────────────────────────
// On a new session: reset all accumulators to the current bar's contribution.
// On all other bars: add the current bar's contribution to the running totals.
if isNewSession
    cumPV  := typicalPrice * volume
    cumPV2 := typicalPrice * typicalPrice * volume
    cumVol := volume
else
    cumPV  := cumPV  + typicalPrice * volume
    cumPV2 := cumPV2 + typicalPrice * typicalPrice * volume
    cumVol := cumVol + volume

// ─── VWAP CALCULATION ─────────────────────────────────────────────────────────
// Guard against zero volume (e.g., pre-market bars with no trades)
float vwap = cumVol != 0.0 ? cumPV / cumVol : na

// ─── VARIANCE AND STANDARD DEVIATION ─────────────────────────────────────────
// Variance formula: E[P²] - (E[P])²
// Where E[P²] = cumPV2 / cumVol and E[P] = vwap
// This is the volume-weighted variance of price around VWAP.
float variance = na(vwap) ? na : (cumPV2 / cumVol) - (vwap * vwap)

// Guard: variance can be slightly negative due to floating-point precision.
// Use math.max to clamp to 0.0 before taking the square root.
float stdDev = na(variance) ? na : math.sqrt(math.max(0.0, variance))

// ─── BAND LEVELS ──────────────────────────────────────────────────────────────
float upperBand1 = na(stdDev) ? na : vwap + bandMult1 * stdDev
float lowerBand1 = na(stdDev) ? na : vwap - bandMult1 * stdDev
float upperBand2 = na(stdDev) ? na : vwap + bandMult2 * stdDev
float lowerBand2 = na(stdDev) ? na : vwap - bandMult2 * stdDev

// ─── PLOTS ────────────────────────────────────────────────────────────────────
// VWAP line — primary output, always visible
plot(
    vwap,
    title     = "VWAP",
    color     = color.new(color.blue, 0),
    linewidth = 2,
    style     = plot.style_line
)

// Standard deviation bands — conditionally shown based on user input
p_upper1 = plot(
    showBands ? upperBand1 : na,
    title = "Upper Band 1",
    color = color.new(color.orange, 30),
    linewidth = 1,
    style = plot.style_line
)
p_lower1 = plot(
    showBands ? lowerBand1 : na,
    title = "Lower Band 1",
    color = color.new(color.orange, 30),
    linewidth = 1,
    style = plot.style_line
)
p_upper2 = plot(
    showBands ? upperBand2 : na,
    title = "Upper Band 2",
    color = color.new(color.red, 50),
    linewidth = 1,
    style = plot.style_line
)
p_lower2 = plot(
    showBands ? lowerBand2 : na,
    title = "Lower Band 2",
    color = color.new(color.red, 50),
    linewidth = 1,
    style = plot.style_line
)

// Fill between band pairs for visual clarity
fill(p_upper1, p_lower1, color = color.new(color.orange, 90))
fill(p_upper2, p_lower2, color = color.new(color.red,    95))

6. Dissecting the Standard Deviation Band Formula

The standard deviation bands are derived from the volume-weighted variance of price around VWAP. The formula used is the computational form of variance:

$$\sigma^2 = \frac{\sum_{i=1}^{t} P_i^2 \times V_i}{\sum_{i=1}^{t} V_i} - \left(\text{VWAP}_t\right)^2$$

This is equivalent to $E[P^2] - (E[P])^2$, the standard identity for variance. It requires only one additional accumulator (cumPV2) beyond what VWAP itself needs, making it computationally efficient. The standard deviation is then:

$$\sigma = \sqrt{\max(0,\, \sigma^2)}$$

The math.max(0.0, variance) guard is necessary because floating-point arithmetic can produce values like $-1 \times 10^{-14}$ when all prices in the session are identical, which would cause math.sqrt() to return na.


7. Important Usage Constraints

7.1 Timeframe Dependency

VWAP is an intraday indicator. It is mathematically meaningful only on intraday timeframes (e.g., 1m, 5m, 15m, 1H). On daily or weekly charts, the session-reset mechanism fires on every bar, making VWAP collapse to the typical price of each individual bar — which is not useful. The indicator should be applied exclusively to intraday charts.

7.2 Volume Data Availability

VWAP requires reliable volume data. On some instruments (e.g., certain forex pairs or synthetic indices), volume data may represent tick count rather than actual traded volume. In such cases, VWAP values are still mathematically valid but represent a tick-weighted average price rather than a true volume-weighted average.

7.3 The timeframe.change("D") Session Boundary

The expression timeframe.change("D") detects when the chart's current bar belongs to a new calendar day. This is the standard Pine Script v6 method for daily session resets. For instruments with non-standard sessions (e.g., futures with overnight sessions), a custom session string via input.session() may be more appropriate.


8. Interpreting VWAP in Practice

Price Location Interpretation Band Context
Price > VWAP Buyers are in control; average participant is in profit Between VWAP and Upper Band 1: mild bullish
Price < VWAP Sellers are in control; average participant is at a loss Between VWAP and Lower Band 1: mild bearish
Price at Upper Band 2 Price is 2σ above VWAP — statistically extended Potential mean-reversion zone
Price at Lower Band 2 Price is 2σ below VWAP — statistically extended Potential mean-reversion zone

9. Conclusion

  • VWAP is a volume-weighted cumulative average that resets each session, making it fundamentally different from rolling moving averages. Its value at any point in the session reflects the true average transaction price weighted by volume, not time.
  • The Pine Script v6 implementation requires var accumulators that persist across bars and are manually reset on session boundaries via timeframe.change("D"). The var keyword ensures the initializer expression runs only on bar 0, preventing re-initialization each bar — unlike a plain declaration such as float x = close, whose initializer is re-evaluated on every bar.
  • Standard deviation bands are derived from the volume-weighted variance identity $E[P^2] - (E[P])^2$, requiring only one additional accumulator and providing statistically grounded distance levels around VWAP.

Ideas for Further Development

  • Anchored VWAP: Allow users to anchor the VWAP start to a specific bar (e.g., an earnings date or a swing low) using a input.time() parameter and a manual reset condition, rather than resetting on every daily session boundary.
  • Multi-Session VWAP: Extend the script to display VWAPs for the current week and current month simultaneously, using timeframe.change("W") and timeframe.change("M") as additional reset conditions with separate accumulator sets.

Related Posts

Comments

Popular posts from this blog

How to Build a Volume Indicator in Pine Script v6: Buying & Selling Pressure Analysis

Pine Script v6 plot() Function: Complete Guide to Lines, Histograms, and Circles

SuperTrend Indicator in Pine Script v6: Canonical Formula, Flip Logic, and Non-Repainting Signals