MESA Adaptive Moving Average (MAMA)

Parameters: fast_limit = 0.5 (0.3–0.7) | slow_limit = 0.05 (0.03–0.1)

Overview

The MESA Adaptive Moving Average, developed by John Ehlers, adapts to price movement based on the rate of change of phase as measured by the Hilbert Transform Discriminator, which imparts a 90 degree phase shift to frequency components to track dominant market cycles. Rather than using fixed smoothing periods, MAMA calculates an adaptive alpha value bounded between fast and slow limits (typically 0.5 and 0.05) based on the phase rate of change, where shorter cycles produce faster adaptation and longer cycles result in slower smoothing. The indicator features a fast attack average and slow decay average that rapidly ratchets behind price changes in a distinctive staircase pattern, then holds the average value until the next ratchet occurs, eliminating the curved lag typical of traditional moving averages. MAMA produces two outputs: the primary MAMA line and the Following Adaptive Moving Average (FAMA), which uses half the alpha value of MAMA to create a slower companion line synchronized in time but with reduced vertical movement. Crossovers between MAMA and FAMA generate reliable trading signals with minimal whipsaws, as the two lines rarely cross unless a major directional change occurs, while both lines serve as dynamic support and resistance levels for pullback entries. The Hilbert Transform discriminator computes the dominant cycle by averaging phase differentials until they sum to 360 degrees, enabling MAMA to automatically adjust its responsiveness to match the prevailing market rhythm.

Implementation Examples

Compute MAMA and FAMA from prices or candles:

use vectorta::indicators::moving_averages::mama::{mama, MamaInput, MamaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using a price slice
let prices = vec![100.0, 101.5, 103.0, 102.2, 104.8];
let params = MamaParams { fast_limit: Some(0.5), slow_limit: Some(0.05) };
let input = MamaInput::from_slice(&prices, params);
let result = mama(&input)?;  // MamaOutput { mama_values, fama_values }

// Using Candles with defaults (source = "close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = MamaInput::with_default_candles(&candles);
let result = mama(&input)?;

// Access paired series
for (m, f) in result.mama_values.iter().zip(&result.fama_values) {
    println!("MAMA={}, FAMA={}", m, f);
}

API Reference

Input Methods
// From a price slice
MamaInput::from_slice(&[f64], MamaParams) -> MamaInput

// From candles with custom source
MamaInput::from_candles(&Candles, &str, MamaParams) -> MamaInput

// From candles with defaults (close, fast_limit=0.5, slow_limit=0.05)
MamaInput::with_default_candles(&Candles) -> MamaInput
Parameters Structure
pub struct MamaParams {
    pub fast_limit: Option<f64>, // Default: 0.5
    pub slow_limit: Option<f64>, // Default: 0.05
}
Output Structure
pub struct MamaOutput {
    pub mama_values: Vec<f64>, // primary adaptive average
    pub fama_values: Vec<f64>, // following (slower) average of MAMA
}
Validation, Warmup & NaNs
  • Requires at least 10 points; else MamaError::NotEnoughData.
  • fast_limit > 0 and slow_limit > 0; non‑finite values error.
  • Auto kernel routes to Kernel::Scalar for this indicator.
  • Warmup: first 10 outputs are NaN to align with streaming; subsequent values are finite if inputs are.
  • Streaming returns None until its internal buffer is filled (≈10 samples), then yields (MAMA, FAMA).
Error Handling
use vectorta::indicators::moving_averages::mama::{mama, MamaError};

match mama(&input) {
    Ok(out) => handle(out),
    Err(MamaError::NotEnoughData { needed, found }) =>
        eprintln!("need {needed} points, found {found}"),
    Err(MamaError::InvalidFastLimit { fast_limit }) =>
        eprintln!("invalid fast_limit: {fast_limit}"),
    Err(MamaError::InvalidSlowLimit { slow_limit }) =>
        eprintln!("invalid slow_limit: {slow_limit}"),
}

Python Bindings

Basic Usage

Calculate MAMA and FAMA from NumPy arrays (defaults 0.5 / 0.05):

import numpy as np
from vectorta import mama

prices = np.array([100.0, 101.5, 103.0, 102.2, 104.8])

# Returns (mama, fama) as NumPy arrays
m, f = mama(prices, fast_limit=0.5, slow_limit=0.05, kernel="auto")
print(m.shape, f.shape)
Streaming Real-time Updates

Use the streaming API to get the latest (MAMA, FAMA) as data arrives:

from vectorta import MamaStream

stream = MamaStream(0.5, 0.05)
for price in price_feed:
    pair = stream.update(price)
    if pair is not None:
        mama, fama = pair
        use(mama, fama)
Batch Parameter Optimization

Test multiple fast_limit/slow_limit combinations efficiently:

import numpy as np
from vectorta import mama_batch

prices = np.array([...], dtype=float)

res = mama_batch(
    prices,
    fast_limit_range=(0.30, 0.70, 0.10),
    slow_limit_range=(0.03, 0.10, 0.01),
    kernel="auto"
)
print(res["mama"].shape, res["fama"].shape)  # (rows, cols)
CUDA Acceleration

CUDA support is available in this project when built with the cuda feature:

# Requires building vectorta with Python + CUDA features enabled
from vectorta import mama_cuda_batch_dev, mama_cuda_many_series_one_param_dev

# Option 1: One series, parameter sweep (device arrays returned)
m_dev, f_dev = mama_cuda_batch_dev(
    data=prices.astype('float64'),
    fast_limit_range=(0.30, 0.70, 0.10),
    slow_limit_range=(0.03, 0.10, 0.01),
    device_id=0
)

# Option 2: Many series, one parameter set (time-major [T, N] f32)
portfolio_tm_f32 = some_data_tm.astype('float32')
m_dev, f_dev = mama_cuda_many_series_one_param_dev(
    portfolio_tm_f32, 0.5, 0.05, device_id=0
)

JavaScript/WASM Bindings

Basic Usage

Calculate MAMA in the browser or Node.js:

import { mama_js } from 'vectorta-wasm';

const prices = new Float64Array([100, 101.5, 103, 102.2, 104.8]);

// Returns an object: { values: Float64Array([mama..., fama...]), rows: 2, cols: len }
const res = mama_js(prices, 0.5, 0.05) as { values: Float64Array; rows: number; cols: number };
const { values, cols } = res;
const mama = values.slice(0, cols);
const fama = values.slice(cols);
console.log(mama, fama);
Memory-Efficient Operations

Use zero-copy compute directly into preallocated memory:

import { mama_alloc, mama_free, mama_into, memory } from 'vectorta-wasm';

const prices = new Float64Array([/* your data */]);
const n = prices.length;

const inPtr = mama_alloc(n);
const outMPtr = mama_alloc(n);
const outFPtr = mama_alloc(n);

new Float64Array(memory.buffer, inPtr, n).set(prices);
mama_into(inPtr, outMPtr, outFPtr, n, 0.5, 0.05);

const mama = new Float64Array(memory.buffer, outMPtr, n).slice();
const fama = new Float64Array(memory.buffer, outFPtr, n).slice();

mama_free(inPtr, n);
mama_free(outMPtr, n);
mama_free(outFPtr, n);
Batch Processing

Sweep parameter grids and reshape results:

import { mama_batch_js, mama_batch_metadata_js } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);

const meta = mama_batch_metadata_js(0.30, 0.70, 0.10, 0.03, 0.10, 0.01);
const numCombos = meta.length / 2;

const out = mama_batch_js(prices, 0.30, 0.70, 0.10, 0.03, 0.10, 0.01) as {
  mama: Float64Array;
  fama: Float64Array;
  rows: number;
  cols: number;
};

// Split into rows
const matrixM = [] as number[][];
for (let i = 0; i < out.rows; i++) {
  const start = i * out.cols;
  matrixM.push(Array.from(out.mama.slice(start, start + out.cols)));
}

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators