TradingView indicators

Price Momentum Oscillator on TradingView: formula and limits

The DecisionPoint Price Momentum Oscillator (PMO), developed by Carl Swenlin, smooths one-bar percentage returns twice. Its two smoothing stages use 2/N; the signal line uses an ordinary EMA. A script that applies three ordinary EMAs to the same length labels calculates a different variant.

Use this page to inspect the coefficients, trace a calculation, and copy a reference implementation with an explicit seed and closed-bar signals. The example is an indicator, with no trading orders or performance claims.

1-bar ROC → custom 35 → ×10 → custom 20 → PMO
PMO → ordinary EMA 10 → signal line

The formula and the version difference

The StockCharts formula specifies 35 and 20 periods for custom smoothing, a factor of 10 between those stages, and a 10-period EMA signal. The percentage ROC already includes a factor of 100.

r[t] = 100 × (close[t] / close[t−1] − 1)

u[t] = u[t−1] + (2/35) × (r[t] − u[t−1])

PMO[t] = PMO[t−1] + (2/20) × (10 × u[t] − PMO[t−1])

signal[t] = signal[t−1] + (2/11) × (PMO[t] − signal[t−1])

Implementation check: an ordinary 35-period EMA uses 2/36, and an ordinary 20-period EMA uses 2/21. The custom coefficients are equivalent to ordinary EMA lengths 34 and 19 only when input and initialization also match. The signal remains an ordinary 10-period EMA.

Version note: the earlier code on this page used ordinary 35/20 EMAs and replaced the first missing ROC with zero. The code below corrects the coefficients and documents its seed. It is a reference implementation of the published recurrence, with no claim of identical initial values to StockCharts.

Worked example: a rise followed by a reversal

Illustrative inputs, not market data: closes are 100, 101, 99.99, and 99.99. Use lengths 35/20/10, and seed each stage with its first available input. The first close has no prior close, so no ROC exists yet. Values below are rounded only for display.

Synthetic calculation with first-input seeding
CloseROC %First smoothingPMOSignal EMA
100.00UnavailableUnavailableUnavailableUnavailable
101.00+1.001.00000010.00000010.000000
99.99−1.000.8857149.8857149.979221
99.990.000.8351029.7322459.934316

For the third close: u = 1 + (2/35) × (−1 − 1) = 0.885714. PMO = 10 + (2/20) × (8.857143 − 10) = 9.885714. The signal is still higher at 9.979221, even though both lines remain positive.

This demonstrates three separate facts: the latest return is negative, accumulated smoothed momentum remains positive, and PMO has crossed below its signal. The example gate suppresses this early marker because only two valid ROC bars exist. None of these numbers is a forecast.

Copy the PMO Pine Script v6 example

Paste into TradingView Pine Editor and add it to a standard candle chart. Blue is PMO, orange is the signal, and the horizontal line is zero. The Data Window exposes ROC, the first smoothing stage, and the valid-bar count so a mismatch can be traced before changing parameters.

Pine Script v6 reference implementation
//@version=6
indicator("PMO: DecisionPoint coefficients, explicit seed", overlay = false, precision = 6)

source = input.source(close, "Source")
firstLength = input.int(35, "First custom smoothing", minval = 2)
secondLength = input.int(20, "Second custom smoothing", minval = 2)
signalLength = input.int(10, "Signal EMA length", minval = 1)
warmup = input.int(250, "Valid ROC bars before signals", minval = 2)

// This example requires positive consecutive source values.
valid = not na(source) and not na(source[1]) and source > 0 and source[1] > 0
roc = valid ? 100.0 * (source / source[1] - 1.0) : na
var float first = na
var float pmo = na
var float signal = na
var int validBars = 0

// Seed each stage with its first input. Reset after invalid input.
if valid
    first := na(first[1]) ? roc : first[1] + 2.0 / firstLength * (roc - first[1])
    scaled = 10.0 * first
    pmo := na(pmo[1]) ? scaled : pmo[1] + 2.0 / secondLength * (scaled - pmo[1])
    signal := na(signal[1]) ? pmo : signal[1] + 2.0 / (signalLength + 1) * (pmo - signal[1])
    validBars += 1
else
    first := na
    pmo := na
    signal := na
    validBars := 0

plot(pmo, "PMO", color = color.blue, linewidth = 2, style = plot.style_linebr)
plot(signal, "Signal EMA", color = color.orange, style = plot.style_linebr)
hline(0, "Zero", color = color.gray)
plot(roc, "One-bar ROC (%)", display = display.data_window)
plot(first, "First smoothing (before x10)", display = display.data_window)
plot(validBars, "Valid ROC bars", display = display.data_window)

crossUp = ta.crossover(pmo, signal)
crossDown = ta.crossunder(pmo, signal)
ready = validBars >= warmup
confirmedUp = ready and crossUp and barstate.isconfirmed
confirmedDown = ready and crossDown and barstate.isconfirmed
plotshape(confirmedUp, "Confirmed up cross", shape.triangleup, location.bottom, color.blue, size = size.tiny)
plotshape(confirmedDown, "Confirmed down cross", shape.triangledown, location.top, color.orange, size = size.tiny)
alertcondition(confirmedUp, "PMO crossed above signal", "PMO crossed above its signal on a closed bar after the warm-up gate.")
alertcondition(confirmedDown, "PMO crossed below signal", "PMO crossed below its signal on a closed bar after the warm-up gate.")

The positive-source check and reset policy are choices in this example. Missing or nonpositive consecutive inputs reset all states and the counter. Each stage starts from its first input after that reset. This avoids silently interpreting unavailable ROC as a flat-price observation.

The curves update during an open bar. Markers and alert conditions require barstate.isconfirmed; they also require the warm-up gate. An alert must still be created in TradingView. There is no higher-timeframe request or future-bar pivot logic in this script.

Initialization needs more history than a length label

A recursive smoother retains part of its previous state on every bar. For the first stage, two runs with the same later inputs but a different seed have a difference of d[k] = d[0] × (33/35)k. After 100 updates, about 0.2783% of that first-stage difference remains. The second stage and signal have their own state, so that percentage is not a bound on the entire PMO output.

The 250-bar gate is a deliberately long example default for markers and alerts. It does not stop the plots, guarantee agreement to a fixed tolerance, or replace a convergence check. Longer lengths need a fresh check. Loading more history can change the initial state and therefore later values.

  1. Fix the symbol, exchange feed, standard candle type, source, timeframe, session, adjustment settings, and all three lengths.
  2. Load substantially more history than the visible comparison window. Record PMO and signal on several closed bars.
  3. Extend the history again and compare those same timestamps. Use a declared tolerance, such as 0.0001 oscillator units, for this numerical check.
  4. If differences persist, inspect raw ROC first, then the first smoother, PMO, and signal. Do not round intermediate values or tune lengths to hide a formula mismatch.

The tolerance above is an illustrative validation choice, not a trading threshold. Syntax validation confirms that the source compiles; it does not certify numerical parity with another vendor.

Compare momentum with consistent inputs

StockCharts describes PMO as normalized and suitable for relative-strength ranking. It also notes that securities have different historical ranges and that changing the chart timeframe changes the oscillator. Signal-line crossings can repeat without useful follow-through.

A scale check follows directly from the ratio: multiplying every close in the worked example by 10 leaves its returns and PMO unchanged. Thus a nominal price of 1,000 does not by itself imply stronger momentum than a price of 100. The same calculation does not normalize volatility, liquidity, transaction costs, or trading hours.

For a comparison table, use one timeframe, parameter set, history policy, and synchronized closed timestamps. A daily equity close and a still-open crypto bar are different observations. A ranking is a description of the chosen return histories, not a ranking of expected returns or risk.

ObservationCheck before acting
PMO above zero, below signalPositive smoothed return and a lower reading than its signal can coexist, as the calculation table shows.
Cross near a flat signalInspect the difference between the lines; a very small change may reverse the crossing.
A high absolute PMO valueCheck history and the return scale. No fixed upper limit is encoded in the recurrence.
Lower PMO peak while price risesDefine which peaks are compared and when they were known. The supplied script does not detect divergence.

ROC is the one-bar input here. PMO adds recursive smoothing and a signal comparison. MACD instead begins with a difference between price averages; its raw units differ. RSI uses a bounded gain/loss construction. These distinct formulas do not establish a universal best oscillator.

Give Pineify a specification you can verify

Open the Pine Script AI Coding Agent with the reference code and a request that names the coefficients, seed, missing-input policy, warm-up, and confirmation time. Keep the intermediate Data Window plots while checking generated changes.

Use one-bar percentage ROC, custom smoothing coefficients 2/35 and 2/20, the x10 multiplier, and a 10-period ordinary EMA signal. Preserve first-input seeds, reset on invalid inputs, expose each stage, and confirm crossovers at bar close after 250 consecutive valid ROC bars. Match the three synthetic calculation rows before adding any strategy orders.

Once the indicator values match, define entry timing, exits, sizing, commissions, and slippage as a separate strategy specification. A marker describes an event; it supplies none of those trading rules. This page is educational and is not investment advice.

Open Pine Script AI Coding Agent

Frequently asked questions

Check the two smoothing coefficients, the percentage ROC scale, the x10 multiplier, source, timeframe, price adjustments, and seed. This page previously used ordinary EMA coefficients for both smoothing stages; the revised example uses the DecisionPoint coefficients. Matching labels such as 35/20/10 alone does not establish equal calculations.

No. This example seeds each stage from its first valid input and plots immediately so initialization is visible. Its marker and alert gate defaults to 250 consecutive valid ROC bars. That is an example setting, not proof of convergence or a StockCharts requirement. Increase loaded history and check whether recent values stabilize.

No. The script draws a marker and exposes alert conditions after the warm-up gate and bar close. It has no strategy orders, exits, position sizing, commission, or slippage model. Create an alert in TradingView if needed; a plotted marker alone does not create one.

The example returns no oscillator value, resets all smoothing states, and restarts the valid-bar counter. This is an explicit input policy for the demonstration. It prevents a missing return from being silently treated as a zero return, and excludes negative-price series from this implementation.

This page makes no optimal-settings claim. Use the reference lengths to verify the calculation first. Changing a length, timeframe, session, or source changes the series; assess a separately specified trading strategy with its costs and out-of-sample data before choosing parameters.

Sources and related reading

Source review: October 17, 2026. The arithmetic table and initialization checks are original demonstrations using stated assumptions.