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%.
| Case | Fast MA | Slow MA | APO: gap | PPO |
|---|---|---|---|---|
| A: baseline | 102 | 100 | 2 | 2% |
| B: history × 10 | 1,020 | 1,000 | 20 | 2% |
| C: history + 100 | 202 | 200 | 2 | 1% |
| D: zero denominator | 2 | 0 | 2 | Undefined |
| E: negative denominator | -98 | -100 | 2 | -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.
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
- 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.
- 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.
- 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.
- 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.
- 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
| Measure | Calculation | What changes? |
|---|---|---|
| PPO | 100 × price MA gap / slow price MA | Relative separation of price averages |
| APO | Fast price MA − slow price MA | Gap in price units; absolute does not mean unsigned |
| PVO | 100 × volume EMA gap / slow volume EMA | Relative volume, not price direction |
| ROC | 100 × (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
Sources and related guides
Documentation checked October 17, 2026. Calculation tables are hypothetical examples.