TradingView indicator guides

Percentage Price Oscillator on TradingView

The Percentage Price Oscillator indicator (PPO) divides the gap between a fast and a slow moving average by the slow average. Its percentage scale helps compare the same calculation at different price levels. A reading is a relative MA gap, not a return forecast.

PPO = (fastMA − slowMA) / slowMA × 100

Signal = MA of PPO. Histogram = PPO − signal. Choose the MA types explicitly; this page provides EMA and SMA options.

A price-scaling test you can reproduce

These are hypothetical MA values at one bar, not market data or a backtest. For row B, multiply the entire input history in A by 10 and keep the MA method and initialization unchanged. The averages scale by 10; the percentage gap stays at 2%.

Hypothetical scale and denominator examples
CaseFast MASlow MAAPO: gapPPO
A: baseline10210022%
B: history × 101,0201,000202%
C: history + 10020220021%
D: zero denominator202Undefined
E: negative denominator-98-1002-2%

Row A gives (102 − 100) / 100 × 100 = 2%. If its signal is an assumed 1.5%, the histogram is 0.5 percentage points. That signal value must come from earlier PPO observations; the current fast and slow averages alone cannot determine it.

Row C shows the limit: adding a constant to every input changes PPO. Row E shows why a positive PPO normally means fastMA is above slowMA only when the denominator is positive. A denominator close to zero can also produce an extreme percentage from a small gap. The script below deliberately stops for a zero or negative slow average.

Match the calculation before reading signals

TradingView exposes separate MA choices for the oscillator and signal. EMA and SMA settings can give different values with identical lengths. The example defaults to close, fast 12, slow 26, signal 9, with EMA for both choices. These are reproducible starting inputs, not optimized trading settings.

  • A zero crossing concerns the fast and slow averages. With a positive slow average, PPO above zero means fastMA is higher.
  • A signal crossing concerns PPO and its own average. The histogram changes sign at that crossing; it does not measure price acceleration directly.
  • For a negative PPO of -2% and signal of -3%, the histogram is +1 percentage point. A positive histogram can coexist with a negative PPO.
  • Changing the chart timeframe changes the duration of each input bar. The same 12 bars cover different elapsed time on a 15-minute chart and a daily chart.

Definition and settings: TradingView PPO documentation. The numerical examples and denominator policy are specific to this page.

PPO Pine Script v6 with inspectable inputs

The example plots PPO, signal and histogram in a separate pane. It exposes both price averages in the Data Window and marks signal crosses only at the chart bar close. It uses no other timeframe and places no orders. Teal columns mean histogram ≥ 0; red columns mean histogram < 0.

Pine Script v6

Copy into a new indicator in the TradingView Pine Editor.

//@version=6
indicator("PPO: formula check", shorttitle="PPO", overlay=false, precision=4)
source = input.source(close, "Source")
fastLength = input.int(12, "Fast length", minval=1)
slowLength = input.int(26, "Slow length", minval=2)
signalLength = input.int(9, "Signal length", minval=1)
oscillatorType = input.string("EMA", "Oscillator MA type", options=["EMA", "SMA"])
signalType = input.string("EMA", "Signal MA type", options=["EMA", "SMA"])
if fastLength >= slowLength
    runtime.error("Fast length must be smaller than slow length.")
fastEMA = ta.ema(source, fastLength)
slowEMA = ta.ema(source, slowLength)
fastSMA = ta.sma(source, fastLength)
slowSMA = ta.sma(source, slowLength)
fastMA = oscillatorType == "EMA" ? fastEMA : fastSMA
slowMA = oscillatorType == "EMA" ? slowEMA : slowSMA
if not na(slowMA) and slowMA <= 0
    runtime.error("This example requires a positive slow MA. Check the source series.")
ppo = slowMA > 0 ? 100 * (fastMA - slowMA) / slowMA : na
signalEMA = ta.ema(ppo, signalLength)
signalSMA = ta.sma(ppo, signalLength)
signal = signalType == "EMA" ? signalEMA : signalSMA
histogram = ppo - signal
crossUp = ta.crossover(ppo, signal)
crossDown = ta.crossunder(ppo, signal)
confirmedUp = crossUp and barstate.isconfirmed
confirmedDown = crossDown and barstate.isconfirmed
histColor = histogram >= 0 ? color.teal : color.red
plot(histogram, "Histogram (percentage points)", style=plot.style_columns, color=histColor)
plot(ppo, "PPO (%)", color=color.blue, linewidth=2)
plot(signal, "Signal (%)", color=color.orange)
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)
plot(fastMA, "Fast MA (price units)", display=display.data_window)
plot(slowMA, "Slow MA (price units)", display=display.data_window)
plotshape(confirmedUp, "Confirmed cross above signal", shape.triangleup, location.bottom, color.teal, size=size.tiny)
plotshape(confirmedDown, "Confirmed cross below signal", shape.triangledown, location.top, color.red, size=size.tiny)
alertcondition(confirmedUp, "PPO cross above signal at close", "PPO crossed above its signal on a confirmed chart bar.")
alertcondition(confirmedDown, "PPO cross below signal at close", "PPO crossed below its signal on a confirmed chart bar.")

Verify one closed bar at a time

  1. Paste into a new TradingView indicator and add it to a standard candle chart. Set the source, three lengths and two MA types. Fast length must be smaller than slow length.
  2. Match the built-in PPO inputs and chart timeframe. Check after the chosen averages have values; allow sufficient prior history for the EMA calculation to settle.
  3. Use the Data Window to read fastMA and slowMA on a closed bar. Recalculate PPO, then subtract the displayed signal. Compare within the displayed rounding precision.
  4. Check a bar before and after a signal crossing. The marker requires the current bar to close. Lines and columns can still move while the current bar is open.
  5. Create an alert from either exposed condition if needed. Treat it as a calculation event; define exits, position sizing, fees and slippage separately before testing a strategy.

The close condition uses barstate.isconfirmed. It addresses the open chart bar, not every possible data revision or multi-timeframe repainting case.

PPO, APO, PVO and ROC answer different questions

MeasureCalculationWhat changes?
PPO100 × price MA gap / slow price MARelative separation of price averages
APOFast price MA − slow price MAGap in price units; absolute does not mean unsigned
PVO100 × volume EMA gap / slow volume EMARelative volume, not price direction
ROC100 × (close − close[n]) / close[n]Price change over n bars, not an MA gap

An EMA-based APO is the MACD line with matching inputs. Dividing it by a positive slow EMA preserves zero crossings. It does not guarantee identical signal crossings: averaging a time-varying ratio differs from averaging the raw gap.

PPO is not bounded to a fixed 0 to 100 range. A universal overbought threshold, a fixed win rate, or a preferred timeframe does not follow from its formula. Repeated crosses can occur without a sustained price move. Educational calculation examples are not investment advice.

Turn a PPO rule into a script you can check

Use Pineify's Pine Script AI Coding Agent to describe the inputs, plot choices and confirmation rule you need. Include the denominator policy in the request, then verify generated bar values before adding strategy rules.

Create a PPO indicator using close, 12/26 price EMAs and a 9-period EMA signal. Show the fast and slow averages in the Data Window. Stop if the slow average is zero or negative. Mark signal crosses only when the chart bar closes. Do not place strategy orders.
Customize PPO in Pineify

PPO questions

No. It means the fast average is 2% above the slow average when the slow average is positive. Price return compares prices at two dates; PPO compares two moving averages at the same bar.

Check the source series, oscillator MA type, signal MA type, timeframe, available history and whether the bar has closed. A 12/26/9 label alone does not specify the calculation. This example runs on the chart timeframe and defaults to close with EMA for both MA types.

Yes. If PPO is -2% and its signal is -3%, the histogram is +1 percentage point. The fast average can remain below the slow average while PPO is above its own signal.

No. Dividing by the slow average removes a constant positive price multiplier. It does not equalize volatility, liquidity, trading sessions or data quality. Compare matching inputs and timeframes; equal PPO values do not imply equal risk or expected returns.

Zero makes the division undefined. A negative denominator reverses the sign of the fast-minus-slow gap. This example stops with an explicit error for a nonpositive slow average, so its usual positive-price interpretation cannot silently carry over to a spread or other signed series.

No. With matching EMAs and a positive denominator, their zero crossings coincide. Signal crossings need not coincide: PPO smooths a gap divided by a slow average that changes over time, while MACD smooths the raw gap.

Sources and related guides

Documentation checked October 17, 2026. Calculation tables are hypothetical examples.

Browse TradingView indicator guides