Chande Momentum Oscillator (CMO)
period = 14 Overview
The Chande Momentum Oscillator (CMO), developed by Tushar Chande, measures price momentum by calculating the difference between the sum of gains and losses over a specified period, then dividing by their total sum. The indicator separates positive and negative price changes, sums each category independently, and expresses the net difference as a percentage of total movement. This calculation produces values between negative 100 and positive 100, with the extremes representing maximum bearish and bullish momentum respectively. Unlike RSI which uses exponential smoothing, CMO employs simple summation for more responsive momentum readings.
CMO oscillates around the zero line, with positive values indicating upward momentum and negative values showing downward pressure. Readings above 50 suggest strong bullish momentum while values below negative 50 indicate powerful bearish forces. The indicator reaches extreme levels more frequently than RSI, making it particularly useful for identifying overbought and oversold conditions in volatile markets. Typical overbought levels sit at 50 while oversold conditions occur below negative 50, though these thresholds vary by market and timeframe.
Traders use CMO for multiple applications including trend identification, momentum confirmation, and divergence analysis. Zero line crossovers signal potential trend changes, with moves from negative to positive territory suggesting new uptrends beginning. The indicator excels at spotting divergences between price and momentum that often precede reversals. CMO also works effectively in ranging markets where its bounded nature helps identify cycle extremes. Many traders combine CMO with moving averages or other trend indicators to filter signals and confirm directional bias before entering positions.
Implementation Examples
Get started with CMO in just a few lines:
use vectorta::indicators::cmo::{cmo, CmoInput, CmoParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using with price data slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = CmoParams { period: Some(14) };
let input = CmoInput::from_slice(&prices, params);
let result = cmo(&input)?;
// Using with Candles data structure
// Quick and simple with default parameters (period=14; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = CmoInput::with_default_candles(&candles);
let result = cmo(&input)?;
// Access the CMO values
for value in result.values {
println!("CMO: {}", value);
} API Reference
Input Methods ▼
// From price slice
CmoInput::from_slice(&[f64], CmoParams) -> CmoInput
// From candles with custom source
CmoInput::from_candles(&Candles, &str, CmoParams) -> CmoInput
// From candles with default params (close prices, period=14)
CmoInput::with_default_candles(&Candles) -> CmoInput Parameters Structure ▼
pub struct CmoParams {
pub period: Option<usize>, // Default: 14
} Output Structure ▼
pub struct CmoOutput {
pub values: Vec<f64>, // CMO values in [-100, 100]
} Validation, Warmup & NaNs ▼
period > 0andperiod ≤ data_len; otherwiseCmoError::InvalidPeriod.- Requires at least
period + 1valid points after the first finite value; elseCmoError::NotEnoughValidData. - If all inputs are
NaN, returnsCmoError::AllValuesNaN. - Warmup: indices before
first_valid + periodareNaN. First computed value appears at that index. cmo_into_slice: output slice length must equal input length; elseCmoError::InvalidOutputLen.- Streaming:
CmoStream::updatereturnsNoneuntil warmup completes, then yieldsSome(f64)each tick.
Error Handling ▼
use vectorta::indicators::cmo::{cmo, CmoError};
match cmo(&input) {
Ok(output) => process_results(output.values),
Err(CmoError::EmptyData) => println!("Input data is empty"),
Err(CmoError::InvalidPeriod { period, data_len }) =>
println!("Invalid period {} for data length {}", period, data_len),
Err(CmoError::AllValuesNaN) => println!("All values are NaN"),
Err(CmoError::NotEnoughValidData { needed, valid }) =>
println!("Need {} data points, only {} valid", needed, valid),
Err(CmoError::InvalidOutputLen { expected, got }) =>
println!("Output len mismatch: expected {}, got {}", expected, got),
} Python Bindings
Basic Usage ▼
Calculate CMO using NumPy arrays (default period=14; optional kernel selection):
import numpy as np
from vectorta import cmo
# Prepare price data as NumPy array
prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5], dtype=float)
# Calculate CMO with defaults (period=14)
result = cmo(prices)
# Or specify custom parameters
result = cmo(prices, period=14)
# Specify kernel for performance optimization ("auto", "scalar", "avx2", "avx512")
result = cmo(prices, period=14, kernel="auto")
# Result is a NumPy array matching input length
print(f"CMO values: {result}") Streaming Real-time Updates ▼
Process real-time price updates efficiently:
from vectorta import CmoStream
# Initialize streaming CMO calculator
stream = CmoStream(period=14)
# Process real-time price updates
for price in price_feed:
cmo_value = stream.update(price)
if cmo_value is not None:
# CMO value is ready (None during warmup)
print(f"Current CMO: {cmo_value}")
if cmo_value > 0:
print("Momentum is positive")
else:
print("Momentum is negative") Batch Parameter Optimization ▼
Test multiple parameter combinations for optimization:
import numpy as np
from vectorta import cmo_batch
# Your price data
prices = np.array([...], dtype=float)
# Define parameter range to test
period_range = (5, 30, 5) # 5, 10, 15, 20, 25, 30
# Run batch calculation
results = cmo_batch(
prices,
period_range=period_range,
kernel="auto"
)
# Access results
print(f"Values shape: {results['values'].shape}") # (num_combinations, len(prices))
print(f"Periods tested: {results['periods']}")
# Example: pick specific period row
target_period = 20
idx = list(results['periods']).index(target_period)
values_for_20 = results['values'][idx]
print(values_for_20) CUDA Acceleration ▼
CUDA support for CMO is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.
JavaScript/WASM Bindings
Basic Usage ▼
Calculate CMO in JavaScript/TypeScript:
import { cmo_js } from 'vectorta-wasm';
// Price data as Float64Array or regular array
const prices = new Float64Array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5]);
// Calculate CMO with specified parameters
const result = cmo_js(prices, 14); // period=14
// Result is a Float64Array
console.log('CMO values:', result); Memory-Efficient Operations ▼
Use zero-copy operations for better performance with large datasets:
import { cmo_alloc, cmo_free, cmo_into, memory } from 'vectorta-wasm';
// Prepare your price data
const prices = new Float64Array([/* your data */]);
const length = prices.length;
// Allocate WASM memory for input and output
const inPtr = cmo_alloc(length);
const outPtr = cmo_alloc(length);
// Copy input data into WASM memory
new Float64Array(memory.buffer, inPtr, length).set(prices);
// Calculate CMO directly into allocated memory
// Args: in_ptr, out_ptr, len, period
cmo_into(inPtr, outPtr, length, 14);
// Read results from WASM memory (slice() to copy out)
const cmoValues = new Float64Array(memory.buffer, outPtr, length).slice();
// Free allocated memory when done
cmo_free(inPtr, length);
cmo_free(outPtr, length);
console.log('CMO values:', cmoValues); Batch Processing ▼
Test multiple parameter combinations efficiently:
import { cmo_batch } from 'vectorta-wasm';
// Your price data
const prices = new Float64Array([/* historical prices */]);
// Define parameter sweep range
const config = { period_range: [5, 30, 5] }; // 5,10,15,20,25,30
// Calculate all combinations
const { values, combos, rows, cols } = cmo_batch(prices, config);
// Reshape flat values into a matrix [rows x cols]
const resultMatrix = [] as Float64Array[];
for (let i = 0; i < rows; i++) {
const start = i * cols;
const end = start + cols;
resultMatrix.push(values.slice(start, end));
}
// Access specific parameter results
// combos[i] has shape { period: number | null }
const rowIdx = combos.findIndex(c => (c.period ?? 14) === 20);
const cmoValuesFor20 = resultMatrix[rowIdx]; Performance Analysis
Across sizes, Rust CPU runs about 2.60× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05