How to Create a Volume Oscillator on TradingView
Measure shifts in participation by comparing short and long volume averages.
The Volume Oscillator highlights when short-term volume activity diverges from its longer baseline. This tutorial covers the exact formula, how traders read crosses and divergences, and a Pine Script v5 implementation using ta.sma on volume.
What Is the Volume Oscillator?
The Volume Oscillator (VO) is the difference between two simple (or sometimes exponential) moving averages of volume:
VO = MA_fast(volume) − MA_slow(volume)
It oscillates around zero: positive readings mean recent volume is hotter than the longer average; negative readings suggest cooling participation.
This tutorial uses a custom dual-SMA difference. Its optional percentage mode is (fast − slow) / slow × 100. It is not the TFS directional-volume oscillator, and its histogram is the oscillator itself, not PVO minus a signal line. See TradingView’s PVO definition before comparing variants.
Volume activity does not establish price direction or a profitable trading rule.
Why Traders Use This Indicator
- Spots expansion and contraction phases in trading activity.
- Helps validate breakouts when volume oscillator rises with price.
- Surfaces potential divergences when price makes extremes without volume support.
- Simple to code with two ta.sma calls on volume.
- Complements price-only oscillators like RSI or MACD.
| Parameter | Default | Description |
|---|---|---|
| Fast length | 5 | Short lookback for responsive volume average; common choices 5–14. |
| Slow length | 10 | Longer smoothing baseline; often 10–30 depending on timeframe. |
| MA type | SMA | This example uses SMA. TradingView PVO supports SMA or EMA; match the type when comparing. |
| Source | volume | Use raw volume; for spread assets verify volume availability. |
How the Volume Oscillator Is Computed
Step 1: Compute fastVol = SMA(volume, fastLen).
Step 2: Compute slowVol = SMA(volume, slowLen).
Step 3: VO = fastVol − slowVol.
Plot VO as a line or histogram centered on zero. Optional normalization (percent of slow) exists in some platforms—state your variant when comparing results.
Signal Interpretation
Zero-line cross: Fast volume MA crossing above slow hints at building participation; crossing below hints at fade.
Trend confirmation: Rising prices with a rising VO suggest conviction; rising prices with falling VO warns of weak thrust.
Divergence: A price high without a matching volume-average high is a descriptive comparison, not an established reversal forecast. Define swing rules before testing divergence.
Combining with Other Indicators
Pair with OBV or Accumulation/Distribution to blend activity with directional flow estimates.
Use alongside VWAP to see whether volume surges align with accepted value areas.
Writing Pine Script Manually
Challenges of Manual Coding
Choosing lengths that are not redundant (fast must stay below slow).
Handling symbols with sporadic zero-volume bars.
Deciding between histogram vs line for crowded indicator panes.
Avoiding overfitting fast/slow pairs without out-of-sample checks.
Separating genuine divergence rules from hindsight bias in prose.
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify — Volume Oscillator (dual SMA)
//@version=5
indicator("Volume Oscillator (SMA)", shorttitle="Vol Osc")
fastLen = input.int(5, "Fast SMA length", minval=1)
slowLen = input.int(10, "Slow SMA length", minval=2)
useHist = input.bool(true, "Plot as histogram")
normalize = input.bool(false, "Normalize as % of slow SMA")
if fastLen >= slowLen
runtime.error("Fast length must be smaller than slow length.")
if na(volume)
runtime.error("This example requires volume on every chart bar.")
fastVol = ta.sma(volume, fastLen)
slowVol = ta.sma(volume, slowLen)
vo = fastVol - slowVol
voPct = not na(slowVol) and slowVol > 0 ? (vo / slowVol) * 100.0 : na
plotVal = normalize ? voPct : vo
plot(useHist ? na : plotVal, color=color.new(color.blue, 0), title="VO line", linewidth=2)
plot(useHist ? plotVal : na, style=plot.style_histogram, color=plotVal >= 0 ? color.new(color.teal, 30) : color.new(color.orange, 30), title="VO hist")
hline(0, "Zero", color=color.new(color.gray, 40))
crossUp = ta.crossover(plotVal, 0)
crossDown = ta.crossunder(plotVal, 0)
bullCross = barstate.isconfirmed and crossUp
bearCross = barstate.isconfirmed and crossDown
plotshape(bullCross, title="VO > 0 cross", style=shape.triangleup, location=location.bottom, size=size.tiny, color=color.new(color.green, 0))
plotshape(bearCross, title="VO < 0 cross", style=shape.triangledown, location=location.top, size=size.tiny, color=color.new(color.red, 0))
// Smoothed VO for slower visuals
voSmooth = ta.ema(plotVal, 3)
plot(voSmooth, color=color.new(color.purple, 40), title="VO EMA3", linewidth=1)
var table vt = table.new(position.bottom_right, 2, 5, border_width=1)
if barstate.islast
table.cell(vt, 0, 0, "Fast SMA", text_color=color.white, bgcolor=color.new(color.gray, 60))
table.cell(vt, 1, 0, str.tostring(fastVol, format.volume), text_color=color.white, bgcolor=color.new(color.gray, 60))
table.cell(vt, 0, 1, "Slow SMA", text_color=color.white, bgcolor=color.new(color.gray, 70))
table.cell(vt, 1, 1, str.tostring(slowVol, format.volume), text_color=color.white, bgcolor=color.new(color.gray, 70))
table.cell(vt, 0, 2, "VO", text_color=color.white, bgcolor=color.new(color.gray, 60))
table.cell(vt, 1, 2, str.tostring(vo, format.volume), text_color=color.white, bgcolor=color.new(color.gray, 60))
table.cell(vt, 0, 3, "VO %", text_color=color.white, bgcolor=color.new(color.gray, 70))
table.cell(vt, 1, 3, na(voPct) ? "Unavailable" : str.tostring(voPct, "#.##") + "%", text_color=color.white, bgcolor=color.new(color.gray, 70))
table.cell(vt, 0, 4, "Bias", text_color=color.white, bgcolor=color.new(color.gray, 60))
bias = na(plotVal) ? "Unavailable" : vo > 0 ? "Expansion" : vo < 0 ? "Contraction" : "Neutral"
table.cell(vt, 1, 4, bias, text_color=color.white, bgcolor=color.new(color.gray, 60))
alertcondition(bullCross, title="VO crosses above zero", message="Volume oscillator crossed above zero")
alertcondition(bearCross, title="VO crosses below zero", message="Volume oscillator crossed below zero")Maintenance Note: The script rejects missing volume and fast lengths greater than or equal to slow lengths. A zero slow average produces an unavailable percentage, not zero. The EMA3 line uses the selected plot units. Cross markers and alerts require a closed bar; plotted values update intrabar. Create alerts in TradingView and recreate them after input changes. No divergence detector or trading strategy is included.
Build with Pineify Visual Editor
Pineify lets you configure supported indicators and export Pine Script. Available settings and plan requirements vary by indicator. Compare the generated rules with the example before using its signals.
| Feature | Manual Pine Script | Pineify Visual Editor |
|---|---|---|
| Built-in visual block | The example below computes a custom dual-SMA difference. | The Visual Editor provides Percentage Volume Oscillator with short, long and signal lengths; its formula differs from this raw-volume example. |
| Exact custom variant | Inspect and edit the supplied source in Pine Editor. | Describe the dual-SMA formula, normalization and confirmed alerts to the AI Coding Agent, then inspect its generated source. |
Configure the existing PVO block with short, long and signal lengths.
Use the PVO, Signal and Difference outputs for visual conditions.
For the exact tutorial variant, use custom code and verify its formula rather than substituting the TFS Volume Oscillator.
Step-by-Step Tutorial
Choose the intended variant
For the existing visual block, select Percentage Volume Oscillator. The separately named Volume Oscillator is TFS directional volume and does not implement the formula below.
Set PVO parameters
Configure Short Length, Long Length and Signal Length. Inspect the exported formula before comparing against this SMA example.
Review outputs
PVO, Signal and Difference are separate outputs. The Difference histogram is not the raw fast-minus-slow volume histogram shown here.
Use custom code when needed
To reproduce this exact SMA variant, paste the example into TradingView or request that formula through the Pine Script AI Coding Agent. Review and chart-test the result.
Trading Strategies & Pro Tips
Breakout confirmation
Test whether adding VO > 0 to a predefined breakout rule changes results after costs; a volume filter alone does not establish an edge.
Pro Tip: Pair with a minimum absolute volume filter on illiquid names.
Pullback participation
In uptrends, look for VO turning positive as price leaves a shallow dip.
Divergence fade
Define swing points and a comparison window before studying price/volume disagreement. This example does not detect swing divergence.
Event study baseline
Mark earnings or macro prints and compare VO behavior vs median days.
Common Mistakes to Avoid
- Setting fast length greater than or equal to slow.
- Treating VO direction as automatic price direction.
- Ignoring asset-specific volume quality (FX vs equities).
- Overloading the pane with cumulative volume by mistake.
- Calling single-bar divergences without swing definitions.
Frequently Asked Questions
Ship volume tools faster with Pineify
- Generate VO variants and normalization toggles from one spec.
- Bundle alerts and tables without repetitive typing.
- Keep research notes and code definitions aligned.
Disclaimer: Trading involves significant risk. The indicators, code, and strategies provided are for educational purposes only and do not constitute financial advice. Past performance is not indicative of future results.