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.
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.
| Close | ROC % | First smoothing | PMO | Signal EMA |
|---|---|---|---|---|
| 100.00 | Unavailable | Unavailable | Unavailable | Unavailable |
| 101.00 | +1.00 | 1.000000 | 10.000000 | 10.000000 |
| 99.99 | −1.00 | 0.885714 | 9.885714 | 9.979221 |
| 99.99 | 0.00 | 0.835102 | 9.732245 | 9.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.
//@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.
- Fix the symbol, exchange feed, standard candle type, source, timeframe, session, adjustment settings, and all three lengths.
- Load substantially more history than the visible comparison window. Record PMO and signal on several closed bars.
- Extend the history again and compare those same timestamps. Use a declared tolerance, such as 0.0001 oscillator units, for this numerical check.
- 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.
| Observation | Check before acting |
|---|---|
| PMO above zero, below signal | Positive smoothed return and a lower reading than its signal can coexist, as the calculation table shows. |
| Cross near a flat signal | Inspect the difference between the lines; a very small change may reverse the crossing. |
| A high absolute PMO value | Check history and the return scale. No fixed upper limit is encoded in the recurrence. |
| Lower PMO peak while price rises | Define 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 AgentFrequently asked questions
Sources and related reading
Source review: October 17, 2026. The arithmetic table and initialization checks are original demonstrations using stated assumptions.