TradingView indicator guide

Volume Zone Oscillator: formula, flat bars and limits

The Volume Zone Oscillator (VZO) divides an exponential average of signed volume by an exponential average of total volume, then multiplies by 100. The sign comes from the change in closing price. Check how your script treats an unchanged close: the author presentation assigns it negative volume, while sign-of-change implementations assign zero. This choice can change both the reading and its zone crossings.

Build a VZO script

The formula and the unchanged-close fork

Let C be the current close, Cprev the previous close, V the current nonnegative volume, and n the EMA length. The May 2011 TASC implementations use the same-period exponential averages for signed and total volume. Their flat-close conventions differ: AIQ assigns negative volume at equality; TradeStation uses the sign of the close change.

  • With paired seeds and nonnegative volume, |VP| cannot exceed TV, so the ratio stays between −100 and +100. This bound need not hold if a variant mixes different smoothers or weights.
  • The ratio is not an EMA of the per-bar signed percentage. For example, a large down-volume bar must carry more weight than a small up-volume bar.
  • Under the negative-flat convention, define U and D as the identically smoothed positive and negative volume buckets. VZO = 100 × (U − D)/(U + D). Thus +40 corresponds to 70% in U and 30% in D, not a 40% chance of a price rise. Neutral-flat volume adds a third bucket, changing that interpretation.
StageCalculationCheck
Signed volume R+V when C > Cprev; otherwise −VThis page defaults to the negative-flat convention.
Neutral-flat alternative+V when C > Cprev; −V when C < Cprev; 0 at equalityThe denominator still includes the flat bar volume.
EMA statea = 2 / (n + 1); Enew = a × input + (1 − a) × EprevUpdate VP from R and TV from V with identical weights.
VZO100 × VP / TV, provided TV > 0Divide after smoothing both series.

Recalculate four bars, including an unchanged close

This hypothetical arithmetic fixture uses n = 3, so a = 0.5. Before A, assume previous close = 100, signed EMA VP = −60, and total EMA TV = 100. These are supplied prior states, not a claim about historical market data or the script default. Use negative volume for an unchanged close and retain full precision until display.

  • At A, VP = 0.5 × 200 + 0.5 × (−60) = 70 and TV = 0.5 × 200 + 0.5 × 100 = 150. VZO is 100 × 70/150.
  • At B, price is unchanged. With the neutral-flat alternative, R is 0, VP becomes 35 and VZO becomes +28, instead of −12. The data and length are identical; only the equality rule changed.
  • At D, the negative-flat sequence crosses upward through −40 from −74.1176 to +10.3030. Merely being above −40 on a later bar would not be a fresh crossover.
  • The move from A to C shows how a positive zone reading can precede a decline. This constructed sequence establishes no probability or trading performance.
BarClose / volumeSigned RVP / TVVZO
A101 / 200+20070 / 150+46.6667
B101 / 100−100−15 / 125−12.0000
C100 / 300−300−157.5 / 212.5−74.1176
D102 / 200+20021.25 / 206.25+10.3030

Separate zone labels from a trend-filtered event

The author presentation lists zones at +60, +40, +15, 0, −5, −40 and −60. It labels ±40 overbought/oversold and ±60 extreme, then combines VZO events with a 60-period price EMA and 14-period ADX. Its trending classification uses ADX above 18; below 18 it uses separate range rules. These are published conventions, not proven optimal settings.

  • EMA and ADX numbers in this table are assumed inputs from sufficient prior history; they cannot be reconstructed from the four closes above.
  • This example implements only the logical filter: confirmed upward −40 crossing AND close > EMA60 AND ADX14 > 18. It does not reproduce every entry, divergence or exit rule in the author system.
  • A move above +40 is a zone condition, not an instruction to short immediately. An event, direction filter and exit specification are separate decisions.
Hypothetical D conditionResult for the example long filterReason
VZO crosses above −40; close 102; EMA60 100; ADX14 22PassBoth trend conditions and the crossover are true.
Same crossover; close 102; EMA60 103; ADX14 22FailPrice is below the EMA.
Same crossover; close 102; EMA60 100; ADX14 16FailThe trend filter requires ADX > 18.
Same crossover; close equals EMA, or ADX equals 18Fail in this explicit exampleStrict comparisons exclude equality.

Inspect a complete Pine Script v6 calculation

This indicator makes the flat-close choice visible and exposes intermediate values in the Data Window. Both EMA states start at the first valid signed-volume/volume pair. It hides the first n − 1 pairs after each reset. Missing close/previous close, missing volume or negative volume resets both states; a zero denominator returns no VZO. This is an explicit implementation policy, not a promise to match every community script.

  • Zero volume is valid input. After a positive denominator exists, a zero-volume bar decays numerator and denominator equally, leaving their ratio unchanged. Missing volume is different and resets this example.
  • First-pair seeding is different from seeding with an n-bar simple average. Suppressing early values does not remove all seed influence; compare the same timestamps with more preceding history.
  • The plots develop while the bar is open. Only the alert condition waits for barstate.isconfirmed. An intrabar change in closing direction can flip the entire signed-volume contribution.
  • Pineify syntax validation passed for this exact source. Compilation does not establish agreement with another script, live alert behavior or profitable trading.
Chart-timeframe VZO with a confirmed upward −40 alert. The alert has no EMA/ADX trend filter and submits no orders.pine
//@version=6
indicator("VZO: explicit volume convention", overlay = false)

length = input.int(14, "EMA length", minval = 2)
flatIsNegative = input.bool(true, "Count unchanged close as negative volume")
alpha = 2.0 / (length + 1.0)
valid = not na(close) and not na(close[1]) and not na(volume) and volume >= 0
direction = close > close[1] ? 1.0 : close < close[1] ? -1.0 : flatIsNegative ? -1.0 : 0.0
signedVolume = valid ? direction * volume : na

// Paired seeds and updates; invalid data resets both accumulators.
var float signedEma = na
var float totalEma = na
var int validBars = 0
if valid
    signedEma := na(signedEma[1]) ? signedVolume : alpha * signedVolume + (1 - alpha) * signedEma[1]
    totalEma := na(totalEma[1]) ? volume : alpha * volume + (1 - alpha) * totalEma[1]
    validBars := nz(validBars[1]) + 1
else
    signedEma := na
    totalEma := na
    validBars := 0

// Suppress the first length - 1 paired samples after each reset.
ready = validBars >= length and totalEma > 0
vzo = ready ? 100 * signedEma / totalEma : na
plot(vzo, "VZO", color = color.blue, linewidth = 2, style = plot.style_linebr)
plot(signedVolume, "Signed volume", display = display.data_window)
plot(signedEma, "Signed volume EMA", display = display.data_window)
plot(totalEma, "Total volume EMA", display = display.data_window)
hline(60, "+60")
hline(40, "+40")
hline(15, "+15")
hline(0, "Zero", color = color.gray)
hline(-5, "-5")
hline(-40, "-40")
hline(-60, "-60")

// This event has no trend filter or order execution.
crossUp = ta.crossover(vzo, -40)
alertcondition(barstate.isconfirmed and crossUp, "VZO crosses above -40", "Confirmed chart-bar VZO crossed above -40.")

Verify the TradingView chart before adding strategy rules

Paste the source into Pine Editor, add it to a standard candlestick chart, and keep symbol, exchange, timeframe and session fixed. Start with completed bars. In the Data Window, compare signed volume, both EMA states and VZO; checking the final line alone can hide a convention mismatch.

  • Include an unchanged close, a high-volume reversal, a zero-volume bar if available, and the first values after a data gap. Confirm the selected feed supplies the volume measure you intend to analyze.
  • For another VZO script, inspect its author and source, equality rule, smoothing method, initialization and any extra signal smoothing. An indicator name does not establish numerical equivalence.
  • A flat or sparse-volume series can produce little useful variation. Price-direction labels also discard intrabar path and price-change magnitude: a tiny higher close and a large higher close receive the same sign.
  • Ask the Pine Script AI Coding Agent to preserve the chosen convention, expose the intermediate values, and add the separate confirmed EMA/ADX filter if needed. Compare bar values before specifying order timing, exits, sizing, fees and slippage.
  • This is the VZO calculation. Do not substitute a Volume Flow Indicator or a fast-minus-slow volume oscillator merely because its label also refers to volume.
Where Pineify fits

Pine Script AI Coding Agent

Draft or revise VZO with an explicit flat-close rule, paired EMA states and confirmation timing. Inspect the source and compare bar values before adding a trading strategy.

Build a VZO script

Frequently asked questions

Educational indicator information, not investment advice. All calculation and filter examples are hypothetical. Zone labels, alerts and syntax validation do not establish future returns or limit trading losses.

Sources and verification

Published by Pineify. Pineify is an independent product and is not affiliated with TradingView. Sources support the technical explanations; examples do not establish trading performance.