Moving Average Bands (MAB)

Parameters: fast_period = 10 | slow_period = 50 | devup = 1 | devdn = 1 | fast_ma_type = sma | slow_ma_type = sma

Overview

Moving Average Bands (MAB) creates dynamic support and resistance levels by calculating the standard deviation of the difference between fast and slow moving averages, then offsetting the slow MA by this deviation to form bands. The middle band tracks the fast moving average for responsive price following, while the upper and lower bands expand and contract based on the root mean square of the fast/slow MA differential over the fast period window. When the two moving averages diverge significantly, indicating strong directional movement, the bands widen to accommodate increased volatility, whereas convergence of the averages causes band contraction that signals consolidation. Traders utilize MAB for breakout strategies when price penetrates the bands during expansion phases, mean reversion trades when bands are wide and price reaches extremes, and trend confirmation when price consistently hugs one band while the middle line provides dynamic support or resistance. Unlike Bollinger Bands which measure deviation from a single average, MAB's dual-average approach captures both trend direction through the fast/slow relationship and volatility through their divergence, making it particularly effective in trending markets where traditional bands may give premature reversal signals.

Defaults (VectorTA): fast=10, slow=50, devup=1.0, devdn=1.0, fast_ma_type="sma", slow_ma_type="sma".

Implementation Examples

Get started with MAB using slices or candles:

use vectorta::indicators::mab::{mab, MabInput, MabParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using a price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = MabParams { fast_period: Some(10), slow_period: Some(50), devup: Some(1.0), devdn: Some(1.0), fast_ma_type: Some("sma".into()), slow_ma_type: Some("sma".into()) };
let input = MabInput::from_slice(&prices, params);
let out = mab(&input)?; // MabOutput { upperband, middleband, lowerband }

// Using candles with defaults (close, 10/50, devup=1, devdn=1, SMA/SMA)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = MabInput::with_default_candles(&candles);
let out = mab(&input)?;

// Access bands
for i in 0..out.upperband.len() {
    println!("upper={}, middle={}, lower={}", out.upperband[i], out.middleband[i], out.lowerband[i]);
}

API Reference

Input Methods
// From price slice
MabInput::from_slice(&[f64], MabParams) -> MabInput

// From candles with custom source (e.g., "close")
MabInput::from_candles(&Candles, &str, MabParams) -> MabInput

// From candles with defaults (source="close", 10/50, dev=1/1, SMA/SMA)
MabInput::with_default_candles(&Candles) -> MabInput

// With default parameters for any MabData
MabInput::with_default_params(MabData) -> MabInput
Parameters Structure
pub struct MabParams {
    pub fast_period: Option<usize>,  // Default: 10
    pub slow_period: Option<usize>,  // Default: 50
    pub devup: Option<f64>,          // Default: 1.0
    pub devdn: Option<f64>,          // Default: 1.0
    pub fast_ma_type: Option<String>,// Default: "sma"
    pub slow_ma_type: Option<String>,// Default: "sma"
}
Output Structure
pub struct MabOutput {
    pub upperband: Vec<f64>,
    pub middleband: Vec<f64>,
    pub lowerband: Vec<f64>,
}
Validation, Warmup & NaNs
  • fast_period > 0, slow_period > 0, and each ≤ input length; else MabError::InvalidPeriod.
  • All inputs NaN → MabError::AllValuesNaN; empty slice → MabError::EmptyData.
  • Required valid span from first finite value: need = max(fast, slow) + fast - 1; else MabError::NotEnoughValidData { need, valid }.
  • Warmup: indices ≤ warmup = first_valid + need - 1 are NaN; first output at warmup + 1.
  • Streaming: MabStream::update returns None until warmup is satisfied; subsequent NaN inputs are ignored.
  • MabError::InvalidLength if caller-provided output buffers have mismatched sizes for mab_into_slice/mab_batch_inner_into.
Error Handling
use vectorta::indicators::mab::{mab, MabError, MabInput, MabParams};

let prices: Vec<f64> = vec![];
let input = MabInput::from_slice(&prices, MabParams::default());
match mab(&input) {
    Ok(out) => /* use out.upperband/middleband/lowerband */ (),
    Err(MabError::EmptyData) => eprintln!("input is empty"),
    Err(MabError::AllValuesNaN) => eprintln!("all values are NaN"),
    Err(MabError::InvalidPeriod { fast, slow, len }) =>
        eprintln!("invalid periods: fast={} slow={} len={}", fast, slow, len),
    Err(MabError::NotEnoughValidData { need, valid }) =>
        eprintln!("need {} valid points, found {}", need, valid),
    Err(MabError::InvalidLength { .. }) => eprintln!("output buffer sizes mismatch"),
}

Python Bindings

Basic Usage

Compute MAB with NumPy arrays:

import numpy as np
from vectorta import mab, MabStream, mab_batch

prices = np.array([100.0, 101.0, 102.0, 101.5, 103.0], dtype=float)

# Defaults: fast=10, slow=50, devup=1.0, devdn=1.0, SMA/SMA
upper, middle, lower = mab(prices)

# Custom parameters and kernel selection
upper, middle, lower = mab(prices, fast_period=10, slow_period=50, devup=1.5, devdn=1.0, fast_ma_type='sma', slow_ma_type='ema', kernel='auto')

# Streaming
stream = MabStream(fast_period=10, slow_period=50, devup=1.0, devdn=1.0, fast_ma_type='sma', slow_ma_type='sma')
for price in prices:
    val = stream.update(price)  # None during warmup; then (upper, middle, lower)
    if val is not None:
        u, m, l = val

# Batch sweep (returns dict with arrays)
res = mab_batch(
    prices,
    fast_period_range=(5, 20, 5),
    slow_period_range=(30, 60, 10),
    devup_range=(1.0, 2.0, 0.5),
    devdn_range=(1.0, 2.0, 0.5),
    fast_ma_type='sma', slow_ma_type='sma', kernel='auto'
)
upper_grid = res['upperbands']  # shape: (rows, len(prices))
middle_grid = res['middlebands']
lower_grid = res['lowerbands']
fasts = res['fast_periods']; slows = res['slow_periods']
CUDA Acceleration

CUDA support for MAB is coming soon.

JavaScript/WASM Bindings

Basic Usage

Compute MAB in JS/TS via WASM:

import { mab_js, mab, mab_batch, memory, mab_alloc, mab_free, mab_into } from 'vectorta-wasm';

const prices = new Float64Array([100, 101, 102, 101.5, 103]);

// 1) Simple flattened result (upper..., middle..., lower...)
const flat = mab_js(prices, 10, 50, 1.0, 1.0, 'sma', 'sma');

// 2) Structured result with rows/cols metadata
const packed = mab(prices, 10, 50, 1.0, 1.0, 'sma', 'sma');
// packed = { values: Float64Array, rows: 3, cols: prices.length }

// 3) Zero-copy into preallocated WASM buffers
const len = prices.length;
const inPtr = mab_alloc(len);
const upPtr = mab_alloc(len);
const midPtr = mab_alloc(len);
const lowPtr = mab_alloc(len);
new Float64Array(memory.buffer, inPtr, len).set(prices);
mab_into(inPtr, upPtr, midPtr, lowPtr, len, 10, 50, 1.0, 1.0, 'sma', 'sma');
const upper = new Float64Array(memory.buffer, upPtr, len).slice();
const middle = new Float64Array(memory.buffer, midPtr, len).slice();
const lower = new Float64Array(memory.buffer, lowPtr, len).slice();
mab_free(inPtr, len); mab_free(upPtr, len); mab_free(midPtr, len); mab_free(lowPtr, len);
Batch Processing

Parameter sweeps with a single call:

import { mab_batch } from 'vectorta-wasm';

const prices = new Float64Array([/* data */]);
const cfg = {
  fast_period_range: [5, 20, 5],
  slow_period_range: [30, 60, 10],
  devup_range: [1.0, 2.0, 0.5],
  devdn_range: [1.0, 2.0, 0.5],
  fast_ma_type: 'sma',
  slow_ma_type: 'sma',
};
const out = mab_batch(prices, cfg);
// out: { upperbands: Float64Array, middlebands: Float64Array, lowerbands: Float64Array, combos, rows, cols }
// Each band is flat with length rows*cols; reshape as needed.

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators