Mass Index (MASS)
period = 5 Overview
The Mass Index, developed by Donald Dorsey in the early 1990s and introduced in his book "The New Trading Dimensions", identifies potential trend reversals by detecting range expansions through a unique double smoothing technique of the high-low differential. The indicator calculates a 9 period EMA of the daily range, then applies another 9 period EMA to create a double smoothed value, dividing the single EMA by the double EMA to produce a ratio that captures range volatility, with this ratio then summed over a specified window (traditionally 25 periods, though this implementation defaults to 5). Dorsey's key innovation was the "reversal bulge" pattern, where the Mass Index rising above 27 and then falling below 26.5 signals an imminent trend reversal regardless of direction, though in practice these extreme readings are rare and traders often lower the thresholds to generate more signals. Unlike directional oscillators, Mass Index has no inherent bias toward bullish or bearish conditions, functioning purely as a volatility indicator that rises when the trading range expands and falls when it contracts. The double smoothing process makes the indicator particularly sensitive to sustained range expansions rather than single-bar spikes, helping filter out insignificant volatility while capturing meaningful changes in market behavior that often precede major reversals.
Implementation Examples
Compute MASS from high/low slices or candles:
use vectorta::indicators::mass::{mass, MassInput, MassParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using with high/low slices
let high = vec![10.0, 11.2, 10.8, 12.1, 11.9];
let low = vec![ 9.6, 10.7, 10.2, 11.3, 11.1];
let params = MassParams { period: Some(5) }; // Default is 5
let input = MassInput::from_slices(&high, &low, params);
let result = mass(&input)?;
// Using with Candles (defaults high/low sources)
let candles: Candles = read_candles_from_csv("data/sample_ohlc.csv")?;
let input = MassInput::with_default_candles(&candles);
let result = mass(&input)?;
// Access MASS values
for v in result.values { println!("MASS: {}", v); } API Reference
Input Methods ▼
// From high/low slices
MassInput::from_slices(&[f64], &[f64], MassParams) -> MassInput
// From candles with custom sources
MassInput::from_candles(&Candles, &str, &str, MassParams) -> MassInput
// From candles with defaults (sources: "high"/"low", period=5)
MassInput::with_default_candles(&Candles) -> MassInput Parameters Structure ▼
#[derive(Debug, Clone)]
pub struct MassParams {
pub period: Option<usize>, // Default: 5
} Output Structure ▼
#[derive(Debug, Clone)]
pub struct MassOutput {
pub values: Vec<f64>, // MASS values (sum of EMA1/EMA2 over window)
} Validation, Warmup & NaNs ▼
period > 0andperiod ≤ len; otherwiseMassError::InvalidPeriod.- High/low slices must be same length and non-empty; otherwise
MassError::EmptyDataorMassError::DifferentLengthHL. - Requires at least
16 + period − 1valid bars after the first finitehigh/lowpair; elseMassError::NotEnoughValidData. - Indices before warmup are
NaN(alloc_with_nan_prefix); mass_into_slice also fills warmup asNaN. - All inputs
NaN→MassError::AllValuesNaN. Kernel::Autoresolves to scalar for stability; explicit SIMD is allowed where supported.
Error Handling ▼
use vectorta::indicators::mass::{mass, MassError};
match mass(&input) {
Ok(output) => process(output.values),
Err(MassError::EmptyData) => eprintln!("Empty input"),
Err(MassError::DifferentLengthHL) => eprintln!("high/low length mismatch"),
Err(MassError::InvalidPeriod { period, data_len }) =>
eprintln!("Invalid period {} for len {}", period, data_len),
Err(MassError::NotEnoughValidData { needed, valid }) =>
eprintln!("Need {} valid bars, have {}", needed, valid),
Err(MassError::AllValuesNaN) => eprintln!("All values are NaN"),
} Python Bindings
Basic Usage ▼
Calculate MASS from NumPy arrays (defaults: period=5):
import numpy as np
from vectorta import mass
high = np.array([10.0, 11.2, 10.8, 12.1, 11.9], dtype=np.float64)
low = np.array([ 9.6, 10.7, 10.2, 11.3, 11.1], dtype=np.float64)
# Default period (5)
values = mass(high, low, period=5)
# Custom kernel selection ("auto", "scalar", "avx2", "avx512")
values = mass(high, low, period=10, kernel="auto")
print(values) # NumPy array, same length as inputs Streaming Real-time Updates ▼
Stream high/low updates and receive MASS after warmup:
from vectorta import MassStream
stream = MassStream(period=5)
for h, l in feed:
val = stream.update(h, l)
if val is not None:
print("MASS:", val) Batch Parameter Optimization ▼
Run many window lengths at once:
import numpy as np
from vectorta import mass_batch
high = np.asarray([...], dtype=np.float64)
low = np.asarray([...], dtype=np.float64)
# (start, end, step)
results = mass_batch(high, low, period_range=(5, 20, 5), kernel="auto")
vals = results['values'] # shape: (num_periods, len(series))
periods = results['periods'] # list of periods tested
print(vals.shape, periods) CUDA Acceleration ▼
CUDA support for MASS is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.
# Coming soon: CUDA-accelerated MASS calculations
# See other CUDA-enabled indicators for expected API patterns. JavaScript/WASM Bindings
Basic Usage ▼
Calculate MASS in JavaScript/TypeScript:
import { mass_js } from 'vectorta-wasm';
const high = new Float64Array([/* high prices */]);
const low = new Float64Array([/* low prices */]);
// Calculate MASS with a specific period
const values = mass_js(high, low, 5);
console.log('MASS values:', values); Memory-Efficient Operations ▼
Use zero-copy into and manual allocation:
import { mass_alloc, mass_free, mass_into, memory } from 'vectorta-wasm';
const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
const n = high.length;
// Allocate WASM memory
const highPtr = mass_alloc(n);
const lowPtr = mass_alloc(n);
const outPtr = mass_alloc(n);
// Copy inputs into WASM memory
new Float64Array(memory.buffer, highPtr, n).set(high);
new Float64Array(memory.buffer, lowPtr, n).set(low);
// Compute MASS directly into pre-allocated output
// Args: high_ptr, low_ptr, out_ptr, len, period
mass_into(highPtr, lowPtr, outPtr, n, 5);
// Read results
const out = new Float64Array(memory.buffer, outPtr, n).slice();
// Free memory
mass_free(highPtr, n);
mass_free(lowPtr, n);
mass_free(outPtr, n); Batch Processing ▼
Unified config API and low-level pointer variant:
import { mass_batch } from 'vectorta-wasm';
const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
// Unified config API (returns { values, combos, rows, cols })
const config = { period_range: [5, 20, 5] };
const res = mass_batch(high, low, config);
console.log(res.rows, res.cols, res.combos);
// Pointer-based into variant
import { mass_alloc, mass_free, mass_batch_into, memory } from 'vectorta-wasm';
const n = high.length;
const highPtr = mass_alloc(n);
const lowPtr = mass_alloc(n);
const rows = /* compute based on range: e.g., 5..20 step 5 => 4 */ 4;
const outPtr = mass_alloc(rows * n);
new Float64Array(memory.buffer, highPtr, n).set(high);
new Float64Array(memory.buffer, lowPtr, n).set(low);
const actualRows = mass_batch_into(highPtr, lowPtr, outPtr, n, 5, 20, 5);
const flat = new Float64Array(memory.buffer, outPtr, actualRows * n).slice();
mass_free(highPtr, n);
mass_free(lowPtr, n);
mass_free(outPtr, actualRows * n); Streaming ▼
import { MassStreamWasm } from 'vectorta-wasm';
const stream = new MassStreamWasm(5);
for (const [h, l] of feed) {
const v = stream.update(h, l);
if (v !== undefined && v !== null) console.log('MASS:', v);
} Performance Analysis
Across sizes, Rust CPU runs about 4.68× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
Related Indicators
Average Directional Index
Technical analysis indicator
Average Directional Movement Index Rating
Technical analysis indicator
Alligator
Technical analysis indicator
Aroon
Technical analysis indicator
Aroon Oscillator
Technical analysis indicator
Chande Momentum Oscillator
Technical analysis indicator