Positive Volume Index on TradingView: formula and limits
The Positive Volume Index indicator compounds the closing-price return only when volume exceeds the preceding bar's volume. Equal or lower volume leaves the index unchanged. A larger volume increase does not give the return more weight. Use the formula, worked example and Pine Script to check which bars contribute.
For valid prices and a known volume pair:
PVI[t] = PVI[t-1] × (1 + (close[t] - close[t-1]) / close[t-1]) when volume[t] > volume[t-1].
Otherwise, keep PVI[t-1]. The update also equals PVI[t-1] × close[t] / close[t-1].
A +2% eligible return multiplies PVI by 1.02; it does not add 2 index points.
A twelve-bar PVI audit
This synthetic sequence starts both calculations at 1,000. The blue-series column follows the official ta.pvi reference rules after scaling; the custom column requires two known volume observations and two positive closes. Values are calculated from the displayed inputs, not from market history or a backtest.
| Bar | Close | Volume | Decision | Built-in × 1,000 | Skip-missing |
|---|---|---|---|---|---|
| 0 | 100 | 1,000 | Seed | 1,000 | 1,000 |
| 1 | 110 | 1,500 | Higher volume; +10% | 1,100 | 1,100 |
| 2 | 121 | 1,500 | Equal volume; hold | 1,100 | 1,100 |
| 3 | 108.9 | 1,200 | Lower volume; hold | 1,100 | 1,100 |
| 4 | 119.79 | 0 | Zero current volume; hold | 1,100 | 1,100 |
| 5 | 131.769 | 200 | 200 > 0; +10% | 1,210 | 1,210 |
| 6 | 144.9459 | Missing | Missing current volume; hold | 1,210 | 1,210 |
| 7 | 159.44049 | 300 | Previous volume missing; policies differ | 1,331 | 1,210 |
| 8 | 175.384539 | 400 | Higher volume; +10% for both | 1,464.1 | 1,331 |
| 9 | 0 | 500 | Zero current close; skip | 1,464.1 | 1,331 |
| 10 | 100 | 600 | Zero previous close; skip | 1,464.1 | 1,331 |
| 11 | 110 | 700 | Valid pair; +10% for both | 1,610.51 | 1,464.1 |
Bar 5 uses the immediately preceding close: 1,100 × 131.769 / 119.79 = 1,210. It does not catch up with returns ignored on bars 2 to 4. At bar 7, the built-in comparison treats the previous missing volume as zero, while the custom calculation skips the bar. Both later compound from their different totals.
With a seed of 1 instead of 1,000, the built-in column ends at 1.61051. A positive rescaling also rescales its EMA, so a same-series crossover does not change solely because the seed changed. Fixed numeric thresholds must use the chosen units.
Copy a Pine Script v6 comparison
Paste this indicator into TradingView's Pine Editor and add it to the chart. Blue is the scaled built-in PVI, amber is its EMA, and purple circles show the custom skip-missing series. A question mark flags an input pair rejected by the custom policy. This is an indicator with alert conditions; it places no strategy orders.
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify
//@version=6
indicator("Positive Volume Index audit", overlay=false)
scale = input.float(1000.0, "Initial scale", minval=0.01)
emaLength = input.int(255, "Built-in PVI EMA length", minval=1)
showStrict = input.bool(true, "Show skip-missing comparison")
confirmedOnly = input.bool(true, "Plot only confirmed bars")
// Built-in series starts at 1; scale changes its display units.
builtinPvi = ta.pvi * scale
signal = ta.ema(builtinPvi, emaLength)
// Custom policy: require known, nonnegative volume and positive prices.
validVolumes = not na(volume) and not na(volume[1]) and volume >= 0 and volume[1] >= 0
validPrices = not na(close) and not na(close[1]) and close > 0 and close[1] > 0
validInputs = validVolumes and validPrices
var float strictPvi = scale
strictPvi := nz(strictPvi[1], scale)
if validInputs and volume > volume[1]
strictPvi := strictPvi * close / close[1]
canPlot = not confirmedOnly or barstate.isconfirmed
plot(canPlot ? builtinPvi : na, "Built-in PVI", color=#2962ff, linewidth=2)
plot(canPlot ? signal : na, "Built-in PVI EMA", color=#f59e0b)
plot(canPlot and showStrict ? strictPvi : na, "Skip-missing PVI", color=#a855f7, style=plot.style_circles)
plotchar(canPlot and showStrict and bar_index > 0 and not validInputs, "Custom calculation skipped", "?", location.top, color=color.orange)
crossUp = ta.crossover(builtinPvi, signal)
crossDown = ta.crossunder(builtinPvi, signal)
alertcondition(barstate.isconfirmed and validInputs and crossUp, "PVI above EMA", "Built-in PVI crossed above its EMA at bar close.")
alertcondition(barstate.isconfirmed and validInputs and crossDown, "PVI below EMA", "Built-in PVI crossed below its EMA at bar close.")| Input | Default | Effect |
|---|---|---|
| Initial scale | 1,000 | Sets display units and the custom seed. |
| Built-in PVI EMA length | 255 bars | Smooths the built-in series, including held values. |
| Show skip-missing comparison | On | Displays the custom series and rejected-input markers. |
| Plot only confirmed bars | On | Hides open-bar plots. Alerts always require confirmation and valid inputs. |
The script uses the current chart timeframe and does not request another timeframe. On a live bar, volume can cross above the previous total and price can change before the close. Create an alert from the named crossover condition after adding the indicator; the code evaluates crosses each bar and gates publication with barstate.isconfirmed.
What changes the result?
Missing volume is different from zero
A reported zero is a numeric observation. Missing volume is unavailable data. The custom calculation holds its total when either volume is missing and also skips the next bar if its previous volume is missing. It resumes using the next valid adjacent pair, without filling the omitted return. The built-in uses its own reference rules; compare policies explicitly.
Zero prices need a defined policy
The general return formula cannot divide by a previous close of zero. The official reference holds when either close is zero or missing. The custom comparison also rejects negative prices. It is intended for positive-price data; its treatment of negative-price instruments is a deliberate difference from the built-in.
History and adjustments are inputs
A cumulative index carries earlier selected returns, so compare the same starting history, session and feed. Dividend adjustment changes the price series. By the formula, a changed return on an eligible bar changes PVI and its later values. A uniform price rescaling leaves the close ratio unchanged; an adjustment across an event can change that ratio. Keep the adjustment setting fixed when auditing results.
Higher volume does not mean a positive return
If volume rises from 1,000 to 1,500 and price falls from 100 to 98, PVI falls from 1,000 to 980. If volume instead rises to 15,000, PVI still becomes 980. The qualifying return has the same weight. A flat PVI can also mean an unchanged price or skipped data, so inspect the input pair before interpreting it.
PVI and NVI select different bars
| Volume relationship | PVI | NVI |
|---|---|---|
| Current > previous | Apply price return | Hold |
| Current < previous | Hold | Apply price return |
| Current = previous | Hold | Hold |
This comparison assumes valid inputs and matched seeds. Neither index weights the return by the size of volume. For the lower-volume calculation, see the Negative Volume Index guide. For a volume-weighted cumulative contribution, compare Price Volume Trend.
Specify the calculation before a trading rule
Pineify's Pine Script AI Coding Agent can help turn a written calculation into code. Provide the input policy and expected bar values so the output can be checked.
- Choose the built-in PVI or the custom skip-missing policy and state the initial scale.
- Specify the chart timeframe, EMA length and whether plots may show an unfinished bar.
- Supply the worked rows and ask for values that can be compared in TradingView's Data Window.
- Check equal volume, zero volume, missing data and zero prices before adding strategy logic.
- Define entries, exits, position sizing, commissions and slippage separately before running a backtest.
Positive Volume Index questions
Sources and calculation notes
Official documentation reviewed October 17, 2026. The worked sequence is hypothetical; the skip-missing comparison is a custom policy.