Mass Index (MASS)

Parameters: 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 > 0 and period ≤ len; otherwise MassError::InvalidPeriod.
  • High/low slices must be same length and non-empty; otherwise MassError::EmptyData or MassError::DifferentLengthHL.
  • Requires at least 16 + period − 1 valid bars after the first finite high/low pair; else MassError::NotEnoughValidData.
  • Indices before warmup are NaN (alloc_with_nan_prefix); mass_into_slice also fills warmup as NaN.
  • All inputs NaNMassError::AllValuesNaN.
  • Kernel::Auto resolves 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

Comparison:
View:

Across sizes, Rust CPU runs about 4.68× faster than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05

Related Indicators