Chande Momentum Oscillator: formula and limits
The Chande Momentum Oscillator (CMO) compares the sum of upward price changes with the sum of downward changes over a rolling window. It divides their difference by their combined magnitude, then multiplies by 100. The result is bounded by −100 and +100 when the denominator is positive.
CMO = 100 × (U − D) / (U + D)
U and D are sums of consecutive price changes, not sums of prices. A flat window makes the denominator zero. Neither a high reading nor a threshold crossing establishes a reversal.
Calculate each change once
For every bar, subtract the preceding source value from the current value. Add positive differences to U and the magnitudes of negative differences to D over the last N changes. A length of 5 therefore needs 6 source observations. In Pine, ta.change(src) supplies the consecutive difference.
Do not sum src − src[i] for i = 1 through N: that compares every historical value with today and counts earlier movement multiple times. The correct historical difference is src[i] − src[i + 1], starting at i = 0, or a rolling sum of ta.change(src).
Bounds, flat windows and exact thresholds
Since U and D are nonnegative, |U − D| ≤ U + D. That proves the ±100 bound whenever U + D is positive. Gaps can change the sums, but cannot invalidate this inequality. Clamping a wrong result conceals the calculation error.
| Window | Condition | CMO |
|---|---|---|
| Only upward or flat changes; at least one rise | U > 0, D = 0 | +100 |
| Only downward or flat changes; at least one fall | U = 0, D > 0 | −100 |
| Equal nonzero upward and downward totals | U = D > 0 | 0 |
| Every change is zero | U = D = 0 | Undefined; this code returns na |
| Upward total is three times downward total | U = 3D, D > 0 | +50 exactly |
| Downward total is three times upward total | D = 3U, U > 0 | −50 exactly |
The usual overbought/oversold regions are above +50 and below −50. Equality is a boundary, not entry into either strict region. At +50, U is three times D; this is a ratio of movement amounts, not a count of rising bars or a probability of the next move. Strong trends can remain in an extreme region.
CMO vs RSI on the same price sequence
This hypothetical calculation starts at close 100 and uses length 5 for both indicators. The prices are invented to make every operation checkable; they are not a backtest.
| Bar | Close | Change | Up amount | Down amount |
|---|---|---|---|---|
| 1 | 102 | +2 | 2 | 0 |
| 2 | 101 | −1 | 0 | 1 |
| 3 | 104 | +3 | 3 | 0 |
| 4 | 104 | 0 | 0 | 0 |
| 5 | 102 | −2 | 0 | 2 |
| 6 | 105 | +3 | 3 | 0 |
At bar 5, U = 2 + 3 = 5 and D = 1 + 2 = 3. CMO is 100 × (5 − 3) / 8 = 25. At bar 6, the first +2 leaves the five-change window and +3 enters: U = 6, D = 3, CMO = 33.3333.
A window-only RSI calculation would be 100 × U / (U + D). Algebra then gives CMO = 2 × window RSI − 100. Using averages instead of sums does not change that ratio if both averages use the same five observations. The distinction from TradingView RSI is its Wilder RMA smoothing, not simply sums versus averages.
Seed Wilder averages at bar 5 with gain 5/5 = 1 and loss 3/5 = 0.6. On bar 6, update each average as (previous × 4 + current) / 5: gain becomes 1.4 and loss 0.48. RSI is 100 × 1.4 / 1.88 = 74.4681.
| Bar | CMO | Window RSI | Wilder RSI | 2 × Wilder RSI − 100 |
|---|---|---|---|---|
| 5 | 25.0000 | 62.5000 | 62.5000 | 25.0000 |
| 6 | 33.3333 | 66.6667 | 74.4681 | 48.9362 |
The equal values at the seed do not make the methods equivalent. CMO drops the oldest change completely; Wilder RSI carries a decaying contribution from earlier changes. A chart with more preceding history can have a different RSI seed. This example establishes neither indicator as an earlier or better signal.
Pine Script v6: audit the CMO calculation
This indicator retains editable source and length inputs, plots CMO and its reference levels, and exposes both sums and a comparison with ta.cmo in the Data Window. The optional orange line rescales ta.rsi to the same axis; it does not turn RSI into CMO. Length 9 is an example input.
//@version=6
indicator("Chande Momentum Oscillator audit", shorttitle="CMO audit", overlay=false)
length = input.int(9, "Length", minval=2, maxval=500)
src = input.source(close, "Source")
showRsi = input.bool(false, "Show RSI rescaled to -100 / +100")
// Each change compares consecutive bars, not today's price with every past bar.
change = ta.change(src)
up = math.max(change, 0.0)
down = math.max(-change, 0.0)
upSum = math.sum(up, length)
downSum = math.sum(down, length)
validCount = math.sum(na(change) ? 0.0 : 1.0, length)
ready = validCount == length
movement = upSum + downSum
// Policy: missing inputs, warm-up and a flat window are undefined.
cmoValue = ready and movement > 0.0 ? 100.0 * (upSum - downSum) / movement : na
builtIn = ta.cmo(src, length)
rsiValue = ta.rsi(src, length)
plot(cmoValue, "Manual CMO", color=color.blue, linewidth=2, style=plot.style_linebr)
plot(showRsi ? 2.0 * rsiValue - 100.0 : na, "2 x Wilder RSI - 100", color=color.orange)
plot(builtIn, "Built-in CMO", display=display.data_window)
plot(cmoValue - builtIn, "Manual minus built-in", display=display.data_window)
plot(upSum, "Upward change sum", display=display.data_window)
plot(downSum, "Downward change sum", display=display.data_window)
hline(100, "+100", color=color.gray, linestyle=hline.style_dotted)
hline(50, "+50", color=color.gray, linestyle=hline.style_dashed)
hline(0, "Zero", color=color.gray)
hline(-50, "-50", color=color.gray, linestyle=hline.style_dashed)
hline(-100, "-100", color=color.gray, linestyle=hline.style_dotted)
crossUp = ta.crossover(cmoValue, 50.0)
crossDown = ta.crossunder(cmoValue, -50.0)
validPair = not na(cmoValue) and not na(cmoValue[1])
alertcondition(barstate.isconfirmed and validPair and crossUp, "CMO above +50 on close", "CMO crossed above +50 on a completed bar")
alertcondition(barstate.isconfirmed and validPair and crossDown, "CMO below -50 on close", "CMO crossed below -50 on a completed bar")The manual series requires N valid consecutive changes and returns na for a zero denominator. Missing source values also invalidate the window. Compare the built-in only on populated, nonflat windows; do not assume matching missing-data policies. The difference should be zero within floating-point tolerance when both calculations use the same valid changes.
The +50 alert requires a current value strictly above +50 and a previous value at or below +50. The −50 alert uses a current value strictly below −50 and a previous value at or above −50. Both require valid current and previous values and barstate.isconfirmed. A move from 49 to 50 does not trigger; 50 to 51 does. Repeated bars above +50 do not repeat the crossing.
The plotted value still changes while a realtime bar is open. Create an alert from the desired condition and use Once Per Bar Close. The code requests no other timeframe and submits no orders. Syntax validation checks compilation; compare actual bar values before using the conditions in a strategy.
Settings and chart verification
- Match the inputs. Use the same symbol, source, length, timeframe, session and chart type. A close-based CMO and an HLC3-based CMO need not agree. Length counts bars, not calendar days.
- Check a completed bar. In TradingView, add the code through Pine Editor and inspect U, D, manual CMO and built-in CMO in the Data Window. Recalculate a small window from consecutive source values.
- Inspect boundary cases. Verify positive-only, negative-only, balanced and flat changes. Zero with movement means U = D; na on a flat window means the ratio has no defined value. Do not silently substitute one for the other.
- Change one setting at a time. A shorter length removes older changes sooner and gives a new change a larger share of the window. A longer length retains more history. Neither establishes a better trading outcome.
- Separate signals from execution. A zero crossing, threshold crossing or divergence needs its own rule. Pivot-based divergence also needs a stated confirmation delay. Define entries, exits, sizing, fees and slippage before testing any trading claim.
Educational calculation example, not investment advice or evidence of future returns.
Turn the calculation contract into a custom script
Pineify's Pine Script AI Coding Agent can help write or modify the indicator. Give it the source, lookback, flat-window policy and bar-close rule, then check the resulting values against the example before adding strategy orders.
Build a CMO indicator using consecutive close changes and a configurable length. Return na when a full valid window is unavailable or total movement is zero. Show upward and downward sums in the Data Window. Alert only on confirmed crosses above +50 or below −50 with valid values on both bars. Keep orders out until entry, exit and cost rules are specified.Build a custom CMO script
Chande Momentum Oscillator FAQ
Sources and related reading
Sources reviewed October 17, 2026. The calculation tables are hypothetical examples derived from the formulas.