Skip to content

Chande Momentum Oscillator (CMO)

The Chande Momentum Oscillator (CMO) is a momentum indicator developed by Tushar Chande that measures the strength and direction of price movement. Unlike RSI which is bounded between 0-100, CMO oscillates between -100 and +100, using the sum of all recent gains and losses rather than averages. This makes CMO more responsive to market changes and provides an unbounded view of momentum strength.

Usage

require 'sqa/tai'

prices = [44.34, 44.09, 44.15, 43.61, 44.33, 44.83,
          45.10, 45.42, 45.84, 46.08, 46.03, 46.41,
          46.22, 45.64, 46.21, 46.25, 46.08, 46.46]

# Calculate 14-period CMO
cmo = SQA::TAI.cmo(prices, period: 14)

puts "Current CMO: #{cmo.last.round(2)}"

Parameters

Parameter Type Required Default Description
prices Array Yes - Array of price values
period Integer No 14 Number of periods for calculation

Returns

Returns an array of CMO values ranging from -100 to +100. The first period values will be nil.

Interpretation

CMO Value Interpretation
+50 to +100 Strong uptrend / Extremely overbought
+25 to +50 Moderate uptrend / Overbought
0 to +25 Weak uptrend
0 to -25 Weak downtrend
-25 to -50 Moderate downtrend / Oversold
-50 to -100 Strong downtrend / Extremely oversold
0 Neutral - balanced momentum

Note: Array elements should be ordered from oldest to newest (chronological order)

Example: Basic CMO Trading Signals

prices = load_historical_prices('AAPL')

cmo = SQA::TAI.cmo(prices, period: 14)
current_cmo = cmo.last

case current_cmo
when -100...-50
  puts "CMO Extremely Oversold (#{current_cmo.round(2)}) - Strong BUY"
  puts "Intense selling pressure likely to reverse"
when -50...-25
  puts "CMO Oversold (#{current_cmo.round(2)}) - Potential BUY"
when 25...50
  puts "CMO Overbought (#{current_cmo.round(2)}) - Potential SELL"
when 50..100
  puts "CMO Extremely Overbought (#{current_cmo.round(2)}) - Strong SELL"
  puts "Intense buying pressure likely to reverse"
when -10...10
  puts "CMO Neutral (#{current_cmo.round(2)}) - No clear momentum"
else
  puts "CMO at #{current_cmo.round(2)}"
end

# Check momentum direction
if current_cmo > 0
  puts "Positive momentum (buyers in control)"
else
  puts "Negative momentum (sellers in control)"
end

Example: CMO Zero-Line Crossover Strategy

prices = load_historical_prices('MSFT')

cmo = SQA::TAI.cmo(prices, period: 14)

current_cmo = cmo.last
prev_cmo = cmo[-2]
prev_prev_cmo = cmo[-3]

# Zero-line crossovers are important trend signals
if prev_cmo < 0 && current_cmo > 0
  puts "CMO crossed above zero - BUY signal"
  puts "Momentum shifted from negative to positive"
  puts "Previous: #{prev_cmo.round(2)} -> Current: #{current_cmo.round(2)}"
elsif prev_cmo > 0 && current_cmo < 0
  puts "CMO crossed below zero - SELL signal"
  puts "Momentum shifted from positive to negative"
  puts "Previous: #{prev_cmo.round(2)} -> Current: #{current_cmo.round(2)}"
end

# Extreme level crossovers
if prev_cmo < 50 && current_cmo >= 50
  puts "CMO crossed above +50 - Extremely strong uptrend"
  puts "Consider taking profits or tightening stops"
elsif prev_cmo > -50 && current_cmo <= -50
  puts "CMO crossed below -50 - Extremely strong downtrend"
  puts "Consider taking profits on shorts or tightening stops"
end

# Momentum acceleration
if current_cmo > prev_cmo && prev_cmo > prev_prev_cmo
  puts "CMO accelerating upward - Momentum building"
elsif current_cmo < prev_cmo && prev_cmo < prev_prev_cmo
  puts "CMO accelerating downward - Momentum declining"
end

Example: CMO Divergence Detection

prices = load_historical_prices('TSLA')

cmo = SQA::TAI.cmo(prices, period: 14)

# Find recent highs in price and CMO
price_high_1_idx = prices[-20..-10].index(prices[-20..-10].max) + (prices.length - 20)
price_high_2_idx = prices[-9..-1].index(prices[-9..-1].max) + (prices.length - 9)

price_high_1 = prices[price_high_1_idx]
price_high_2 = prices[price_high_2_idx]

cmo_high_1 = cmo[price_high_1_idx]
cmo_high_2 = cmo[price_high_2_idx]

# Bearish divergence: price makes higher high, CMO makes lower high
if price_high_2 > price_high_1 && cmo_high_2 < cmo_high_1
  puts "Bearish Divergence detected!"
  puts "Price higher high: #{price_high_1.round(2)} -> #{price_high_2.round(2)}"
  puts "CMO lower high: #{cmo_high_1.round(2)} -> #{cmo_high_2.round(2)}"
  puts "Upward momentum weakening despite rising prices"
  puts "Potential reversal signal"
end

# Bullish divergence: price makes lower low, CMO makes higher low
price_low_1_idx = prices[-20..-10].index(prices[-20..-10].min) + (prices.length - 20)
price_low_2_idx = prices[-9..-1].index(prices[-9..-1].min) + (prices.length - 9)

price_low_1 = prices[price_low_1_idx]
price_low_2 = prices[price_low_2_idx]

cmo_low_1 = cmo[price_low_1_idx]
cmo_low_2 = cmo[price_low_2_idx]

if price_low_2 < price_low_1 && cmo_low_2 > cmo_low_1
  puts "Bullish Divergence detected!"
  puts "Price lower low: #{price_low_1.round(2)} -> #{price_low_2.round(2)}"
  puts "CMO higher low: #{cmo_low_1.round(2)} -> #{cmo_low_2.round(2)}"
  puts "Downward momentum weakening despite falling prices"
  puts "Potential reversal signal"
end

Example: CMO vs RSI Comparison

prices = load_historical_prices('NVDA')

# Calculate both indicators
cmo = SQA::TAI.cmo(prices, period: 14)
rsi = SQA::TAI.rsi(prices, period: 14)

current_cmo = cmo.last
current_rsi = rsi.last

puts "CMO: #{current_cmo.round(2)}"
puts "RSI: #{current_rsi.round(2)}"

# Convert RSI to same scale as CMO for comparison
# RSI 0-100 -> CMO -100 to +100: (RSI - 50) * 2
rsi_as_cmo = (current_rsi - 50) * 2

puts "RSI as CMO scale: #{rsi_as_cmo.round(2)}"
puts "Difference: #{(current_cmo - rsi_as_cmo).round(2)}"

# CMO is more responsive than RSI
if current_cmo.abs > rsi_as_cmo.abs
  puts "CMO showing stronger momentum than RSI"
  puts "CMO's unbounded nature captures extreme moves better"
elsif rsi_as_cmo.abs > current_cmo.abs
  puts "RSI showing stronger momentum than CMO"
  puts "Unusual - may indicate smoothing effects in RSI"
end

# Both showing extreme readings = high conviction
if current_cmo > 50 && current_rsi > 70
  puts "Both overbought - high conviction sell signal"
elsif current_cmo < -50 && current_rsi < 30
  puts "Both oversold - high conviction buy signal"
end

Example: CMO with Trend Filter

prices = load_historical_prices('GOOGL')

cmo = SQA::TAI.cmo(prices, period: 14)
sma_50 = SQA::TAI.sma(prices, period: 50)
sma_200 = SQA::TAI.sma(prices, period: 200)

current_price = prices.last
current_cmo = cmo.last

# Determine overall trend
trend = if sma_50.last > sma_200.last
  "uptrend"
elsif sma_50.last < sma_200.last
  "downtrend"
else
  "neutral"
end

puts "Current trend: #{trend}"
puts "CMO: #{current_cmo.round(2)}"

# Trade with the trend
case trend
when "uptrend"
  # In uptrend, only take bullish CMO signals
  if current_cmo < -25 && current_cmo > -50
    puts "Uptrend + CMO oversold = BUY opportunity"
    puts "Pullback in strong trend"
  elsif current_cmo > 50
    puts "Uptrend + CMO extremely overbought = Take profits"
    puts "Momentum may be overextended"
  elsif current_cmo < 0
    puts "Uptrend but CMO negative = Caution"
    puts "Wait for momentum to turn positive"
  end

when "downtrend"
  # In downtrend, only take bearish CMO signals
  if current_cmo > 25 && current_cmo < 50
    puts "Downtrend + CMO overbought = SELL opportunity"
    puts "Bounce in weak trend"
  elsif current_cmo < -50
    puts "Downtrend + CMO extremely oversold = Cover shorts"
    puts "Downward momentum may be overextended"
  elsif current_cmo > 0
    puts "Downtrend but CMO positive = Caution"
    puts "Wait for momentum to turn negative"
  end

when "neutral"
  # In neutral trend, use traditional overbought/oversold
  if current_cmo > 50
    puts "Neutral trend + CMO overbought = SELL"
  elsif current_cmo < -50
    puts "Neutral trend + CMO oversold = BUY"
  else
    puts "Neutral trend + Neutral CMO = Wait"
  end
end

Advanced Techniques

1. CMO Extremes as Continuation Signals

In strong trends, extreme CMO values can indicate continuation rather than reversal: - CMO > +75 in uptrend = Trend acceleration, not necessarily reversal - CMO < -75 in downtrend = Trend acceleration, not necessarily reversal

2. CMO Rate of Change

Monitor how fast CMO changes: - Rapid CMO increase = Sudden momentum shift - Gradual CMO increase = Steady momentum build - CMO flattening at extremes = Potential exhaustion

3. Multiple Timeframe CMO

Use CMO on different timeframes for confirmation: - Daily CMO for trend - Hourly CMO for entries - Weekly CMO for position sizing

4. CMO Bands

Create custom bands based on historical CMO values: - Top 10% of CMO values = Extreme overbought - Bottom 10% of CMO values = Extreme oversold

Example: CMO Momentum Breakout Strategy

prices = load_historical_prices('AMD')
highs = load_historical_data('AMD', field: :high)
lows = load_historical_data('AMD', field: :low)

cmo = SQA::TAI.cmo(prices, period: 14)

# Calculate recent price range
recent_high = highs[-20..-1].max
recent_low = lows[-20..-1].min

current_price = prices.last
current_cmo = cmo.last
prev_cmo = cmo[-2]

puts "Price Range: #{recent_low.round(2)} - #{recent_high.round(2)}"
puts "Current Price: #{current_price.round(2)}"
puts "CMO: #{current_cmo.round(2)}"

# Bullish breakout with momentum confirmation
if current_price > recent_high && current_cmo > 25
  puts "BULLISH BREAKOUT with momentum confirmation!"
  puts "Price broke above resistance with CMO > +25"
  puts "Strong continuation signal"

  if current_cmo > 50
    puts "Extremely strong momentum - high conviction"
  end

# Bearish breakdown with momentum confirmation
elsif current_price < recent_low && current_cmo < -25
  puts "BEARISH BREAKDOWN with momentum confirmation!"
  puts "Price broke below support with CMO < -25"
  puts "Strong continuation signal"

  if current_cmo < -50
    puts "Extremely strong negative momentum - high conviction"
  end

# False breakout warnings
elsif current_price > recent_high && current_cmo < 0
  puts "WARNING: Price breakout but CMO negative"
  puts "Potential false breakout - momentum not confirming"

elsif current_price < recent_low && current_cmo > 0
  puts "WARNING: Price breakdown but CMO positive"
  puts "Potential false breakdown - momentum not confirming"
end

# Momentum acceleration into breakout
if current_price > recent_high && current_cmo > prev_cmo + 10
  puts "Accelerating momentum into breakout - very bullish"
elsif current_price < recent_low && current_cmo < prev_cmo - 10
  puts "Accelerating momentum into breakdown - very bearish"
end

Example: Multi-Period CMO Analysis

prices = load_historical_prices('SPY')

# Calculate multiple periods for comprehensive view
cmo_7 = SQA::TAI.cmo(prices, period: 7)
cmo_14 = SQA::TAI.cmo(prices, period: 14)
cmo_21 = SQA::TAI.cmo(prices, period: 21)
cmo_50 = SQA::TAI.cmo(prices, period: 50)

puts "Short-term CMO (7): #{cmo_7.last.round(2)}"
puts "Standard CMO (14): #{cmo_14.last.round(2)}"
puts "Medium-term CMO (21): #{cmo_21.last.round(2)}"
puts "Long-term CMO (50): #{cmo_50.last.round(2)}"

# All periods aligned = very strong signal
if cmo_7.last > 25 && cmo_14.last > 25 && cmo_21.last > 25 && cmo_50.last > 25
  puts "\nAll timeframes showing positive momentum - STRONG BUY"
  puts "Aligned bullish momentum across all periods"

  if cmo_7.last > 50 && cmo_14.last > 50
    puts "Near-term extremely overbought - consider waiting for pullback"
  end

elsif cmo_7.last < -25 && cmo_14.last < -25 && cmo_21.last < -25 && cmo_50.last < -25
  puts "\nAll timeframes showing negative momentum - STRONG SELL"
  puts "Aligned bearish momentum across all periods"

  if cmo_7.last < -50 && cmo_14.last < -50
    puts "Near-term extremely oversold - shorts may want to cover"
  end
end

# Divergence between timeframes
if cmo_7.last > 25 && cmo_50.last < -25
  puts "\nShort-term bullish but long-term bearish"
  puts "Counter-trend rally - be cautious"

elsif cmo_7.last < -25 && cmo_50.last > 25
  puts "\nShort-term bearish but long-term bullish"
  puts "Pullback in uptrend - potential buying opportunity"
end

# Momentum turning points
if cmo_7.last > 0 && cmo_14.last < 0 && cmo_21.last < 0
  puts "\nMomentum may be turning positive"
  puts "Short-term leading longer-term"

elsif cmo_7.last < 0 && cmo_14.last > 0 && cmo_21.last > 0
  puts "\nMomentum may be turning negative"
  puts "Short-term leading longer-term"
end

# Calculate momentum consistency
cmo_values = [cmo_7.last, cmo_14.last, cmo_21.last, cmo_50.last]
cmo_avg = cmo_values.sum / cmo_values.length
cmo_range = cmo_values.max - cmo_values.min

puts "\nMomentum Consistency:"
puts "Average CMO: #{cmo_avg.round(2)}"
puts "CMO Range: #{cmo_range.round(2)}"

if cmo_range < 20
  puts "Low range - consistent momentum across timeframes"
elsif cmo_range > 50
  puts "High range - conflicting signals across timeframes"
end

Common Settings

Period Use Case
7 Short-term trading, quick signals
9 Day trading
14 Standard (most common)
20 Swing trading
50 Long-term momentum analysis

Key Differences from RSI

Feature CMO RSI
Range -100 to +100 0 to 100
Calculation Sum of all gains and losses Exponential average of gains and losses
Zero Line Yes (important reference) No (uses 50 as midpoint)
Sensitivity More responsive to changes Smoother, less noise
Extreme Values Can exceed ±50 regularly Rarely exceeds 80/20
Best For Trend strength, breakouts Overbought/oversold conditions
Data Usage All data in period Weighted/smoothed data

CMO Advantages:

  • Unbounded: Can show true momentum extremes without artificial limits
  • More responsive: Uses all data equally, catches changes faster
  • Clearer neutral: Zero line provides obvious momentum direction
  • Better for trends: Extreme values indicate strong trends rather than just overbought/oversold

CMO Disadvantages:

  • More volatile: Can give false signals in choppy markets
  • Harder to interpret: No fixed overbought/oversold levels
  • Less popular: Fewer traders use it, less self-fulfilling prophecy effect
  • RSI - Similar momentum oscillator (bounded 0-100)
  • ROC - Rate of Change (percentage based)
  • MOM - Momentum indicator (absolute values)
  • MACD - Moving Average Convergence Divergence
  • Stochastic - Another momentum oscillator

See Also