TradingView volume breadth guide

McClellan Volume Oscillator: volume breadth vs issue breadth

The McClellan Volume Oscillator subtracts a 39-day EMA from a 19-day EMA of daily advancing volume minus declining volume. It measures changes in volume breadth across a defined market. The familiar issue-based McClellan Oscillator instead uses counts of advancing and declining securities. Both use the same smoothing, but they can disagree because a few heavily traded stocks can dominate volume while many smaller stocks move the other way.

See the decision guide

Volume participation can disagree with stock participation

Up volume is the trading volume assigned to advancing securities in the selected breadth dataset; down volume belongs to declining securities. This classification is different from buyer-initiated versus seller-initiated trade volume. Every completed trade has both a buyer and a seller. It is also different from assigning an entire ETF candle to up or down volume based on its candle color.

  • Hypothetical universe: 80 advancing stocks trade 100 million shares in total, while 20 declining stocks trade 300 million. Net issues are +60; net volume is −200 million shares. Their current inputs disagree before either EMA is calculated.
  • The classification of unchanged securities, halted securities and eligible instruments belongs to the feed methodology. Do not assign them to a side or reconstruct missing constituent volume without that methodology.
MeasurementInputWhat a large constituent changes
Issue breadthAdvancing issue count minus declining issue countOne issue contributes one count regardless of its volume.
Volume breadthAdvancing volume minus declining volumeA heavily traded issue contributes more shares to its side.
Single-symbol volume oscillatorFast volume average versus slow volume averageMeasures activity in that symbol, without market-wide advance/decline classification.

Choose raw or ratio-adjusted volume before smoothing

McClellan Financial describes the volume oscillator as the difference between its 10% and 5% trends of daily up-minus-down volume. With X as the selected input, F = 0.10 × X + 0.90 × prior F; S = 0.05 × X + 0.95 × prior S; oscillator = F − S. The usual EMA weight 2/(length + 1) gives 0.10 for 19 and 0.05 for 39.

  • Apply normalization to each daily observation before updating the EMAs. Dividing the final raw oscillator by current total volume is a different operation.
  • Multiplying both U and D by the same constant multiplies the raw oscillator by that constant when seeds match; the adjusted oscillator is unchanged. Scaling only one feed corrupts both modes.
  • A ratio-adjusted input lies between −1000 and +1000 for nonnegative inputs with positive total volume. This is an input bound, not a trading threshold.
  • A volume Summation Index accumulates oscillator readings. Its calibration and published thresholds do not transfer to this oscillator.
ModeDaily XOutput scale
RawU − DSame units as the two volume feeds.
Raw with display divisor c(U − D) / cFeed units divided by c, with c strictly positive.
Ratio-adjusted variant used here1000 × (U − D) / (U + D)Dimensionless scaled ratio; unchanged volume is excluded.

Reproduce both versions from the same three sessions

These are invented observations in millions of shares. Seed both F and S with the first valid X, so the first oscillator is zero. Raw calculations below use millions as their unit; for feeds in individual shares, a divisor of 1,000,000 produces this display. No prior history is assumed. Keep full precision throughout.

  • Session 2 raw: F = 0.10 × (−400) + 0.90 × 200 = 140; S = 0.05 × (−400) + 0.95 × 200 = 170; F − S = −30 million shares.
  • Session 3 adjusted: X = 1000 × 200/400 = 500; F = 0.10 × 500 + 0.90 × 160 = 194; S = 0.05 × 500 + 0.95 × 180 = 196; oscillator = −2.
  • Session 3 has more up volume than down volume, yet both oscillators remain negative. Their signs compare accumulated fast and slow states rather than just that day's difference.
  • If session 4 has a missing D, the implementation below emits NA and clears both states. If the next valid session has U = 600 and D = 400, it seeds again at 200 and emits zero. That zero is initialization, not evidence of neutral momentum.
SessionU / D (millions)Raw X; F; S; oscillatorAdjusted X; F; S; oscillator
1: seed600 / 400200; 200; 200; 0200; 200; 200; 0
2800 / 1200−400; 140; 170; −30−200; 160; 180; −20
3300 / 100200; 146; 171.5; −25.5500; 194; 196; −2

Verify the NYSE feed pair and its limits

TradingView publicly lists USI:UVOL as NYSE UP VOLUME and USI:DVOL as NYSE DOWN VOLUME. These are the defaults below. Their public pages establish symbol identity, but did not expose usable daily values during this page's verification. They do not establish entitlement, complete history, precise constituent filters or unit scaling for your account. Open both symbols and inspect completed sessions before interpreting any result.

  • A symbol that exists in search may still have inaccessible, delayed or incomplete data. The script can report unavailable inputs; it cannot obtain a missing entitlement.
  • A missing host-chart session has no bar on which Pine can execute. The script therefore cannot detect every absent session or stale-but-plausible feed value. Audit the exchange calendar and source history separately.
  • A holiday is not a synthetic zero-volume trading day. Do not add weekend or holiday zeros to an EMA sequence.
Coverage or data checkRequired action
NYSE versus another marketKeep both feeds from the same documented universe. This pair is not Nasdaq, S&P 500, global equities or crypto breadth.
Symbol field and unitsRead the statistical index value through close. Its Pine volume field is not the breadth value. Verify whether the index value is in shares or a scaled unit.
Matching dates and sessionsCompare both daily timestamps and publication cutoffs. A stale value from a prior session cannot complete the pair.
Issue coverageCheck common-stock-only versus composite coverage, ETFs and other issue types. The public titles alone do not resolve this.
Missing or halted dataAn NA value is unavailable, not zero. The script cannot reconstruct unreported activity or determine a halted constituent's classification.
Zero totalU = D = 0 makes the adjusted ratio undefined. This implementation rejects that observation in both modes. U = D > 0 is valid and gives X = 0.

Complete Pine Script for a daily breadth audit

Open the selected up-volume symbol, initially USI:UVOL, on a standard 1D TradingView chart, then paste this Pine v6 indicator into Pine Editor. Select a compatible down-volume feed. The indicator uses completed chart bars and matching source timestamps, reads close from each statistical index, and keeps gaps as NA. It updates once per available daily bar. It deliberately stops on intraday, multi-day or nonstandard charts.

  • The earliest seed date defaults to January 1, 2020. The actual seed is the first valid loaded observation on or after that date. Loading less history can change that observation and subsequent results.
  • The first valid X seeds both averages; every observed invalid pair clears them. This is an explicit reproducibility policy, not a claim to reproduce a publisher's undisclosed seed or gap policy.
  • Use the Data Window to inspect U, D, X, F, S and the valid-observation count. Record the first seed date, divisor and adjustment mode with any exported comparison.
  • The current unconfirmed daily bar has no oscillator output. A chart close still does not certify that a provider has published its final revision; reload and compare dated observations when auditing.
Pine v6 syntax validated with Pineify. Syntax validation does not verify live feed values, permissions or provider revisions.pine
//@version=6
indicator("McClellan Volume Oscillator: daily audit", overlay=false, precision=4)
upSymbol = input.symbol("USI:UVOL", "Up-volume symbol")
downSymbol = input.symbol("USI:DVOL", "Down-volume symbol")
ratioAdjusted = input.bool(false, "Ratio-adjusted input x 1000")
rawDivisor = input.float(1.0, "Raw input divisor (same units for both feeds)", minval=0.000001)
startDate = input.time(timestamp("01 Jan 2020 00:00 +0000"), "Earliest seed date")
if not timeframe.isdaily or timeframe.multiplier != 1 or not chart.is_standard
    runtime.error("Use a standard 1D chart.")
if ticker.standard(syminfo.tickerid) != upSymbol
    runtime.error("Open the selected up-volume symbol as the chart to use its session calendar.")
[upVolume, upTime] = request.security(upSymbol, "1D", [close, time], gaps=barmerge.gaps_on, lookahead=barmerge.lookahead_off, ignore_invalid_symbol=true)
[downVolume, downTime] = request.security(downSymbol, "1D", [close, time], gaps=barmerge.gaps_on, lookahead=barmerge.lookahead_off, ignore_invalid_symbol=true)
validPair = not na(upVolume) and not na(downVolume) and not na(upTime) and not na(downTime) and upTime == downTime and upTime == time and upVolume >= 0 and downVolume >= 0 and upVolume + downVolume > 0
float x = validPair ? (ratioAdjusted ? 1000.0 * (upVolume - downVolume) / (upVolume + downVolume) : (upVolume - downVolume) / rawDivisor) : na
var float fast = na
var float slow = na
var int samples = 0
if barstate.isconfirmed
    if time < startDate or not validPair
        fast := na
        slow := na
        samples := 0
    else
        fast := na(fast) ? x : 0.10 * x + 0.90 * fast
        slow := na(slow) ? x : 0.05 * x + 0.95 * slow
        samples += 1
ready = barstate.isconfirmed and validPair and time >= startDate
plot(ready ? fast - slow : na, "Volume oscillator", color=color.blue, linewidth=2, style=plot.style_linebr)
hline(0, "Zero", color=color.gray)
plot(ready ? x : na, "Input X", display=display.data_window)
plot(ready ? fast : na, "10% trend (19 EMA)", display=display.data_window)
plot(ready ? slow : na, "5% trend (39 EMA)", display=display.data_window)
plot(upVolume, "Up volume: feed units", display=display.data_window)
plot(downVolume, "Down volume: feed units", display=display.data_window)
plot(samples, "Valid observations since seed", display=display.data_window)
var table status = table.new(position.top_right, 1, 2)
if barstate.islast
    state = time < startDate ? "Before seed date" : not barstate.isconfirmed ? "Waiting for daily close" : not validPair ? "Unavailable: check feeds and dates" : "Valid observations: " + str.tostring(samples)
    table.cell(status, 0, 0, ratioAdjusted ? "Ratio-adjusted x 1000" : "Raw difference / divisor", text_color=color.white, bgcolor=color.navy)
    table.cell(status, 0, 1, state, text_color=color.white, bgcolor=color.navy)

Interpret the EMA gap after checking initialization

A value above zero means the fast volume-breadth average exceeds the slow one. A rising negative value means that gap is narrowing. Neither establishes how many stocks advanced, whether an index must rise next, or whether a trade has positive expectancy. Raw extremes also depend on overall trading activity and feed units.

  • Different seed errors decay at different rates: after k updates, an initial fast-state error is multiplied by 0.90^k and a slow-state error by 0.95^k. At 100 updates these factors are about 0.0000266 and 0.00592. This describes decay, not a universal warm-up guarantee.
  • For divergence research, define price swings and oscillator swings on comparable dates. NYSE volume breadth paired with another market's index answers a cross-market question.
  • Before testing trades, specify the instrument, availability time of the breadth observation, execution delay, exits and costs. The oscillator is a diagnostic series, not an executable strategy.

Ask Pineify to preserve the calculation contract

The Pine Script AI Coding Agent can help draft and revise this indicator from a written specification. Supply your verified symbols, units, seed date, ratio mode and missing-data policy. Include the three-session arithmetic table and ask for intermediate outputs so you can compare the result before adding alerts or trade rules.

  • Keep source selection and confirmation rules visible when extending the example. An intraday display requires a separate daily-state calculation, not an EMA of repeated daily values.
  • Review and validate generated code, then compare actual completed feed observations. Pineify does not supply missing exchange breadth history or prove a signal's profitability.
Where Pineify fits

Pine Script AI Coding Agent

Draft or revise a volume-breadth indicator with explicit data sources, initialization and inspectable intermediate values.

Draft a volume breadth indicator

Frequently asked questions

Educational information, not investment advice. Numerical examples are hypothetical. Breadth readings depend on feed definitions, availability and revisions; they do not guarantee price direction or trading returns.

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.