Moving Average Bands (MAB)
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; elseMabError::InvalidPeriod.- All inputs NaN →
MabError::AllValuesNaN; empty slice →MabError::EmptyData. - Required valid span from first finite value:
need = max(fast, slow) + fast - 1; elseMabError::NotEnoughValidData { need, valid }. - Warmup: indices ≤
warmup = first_valid + need - 1areNaN; first output atwarmup + 1. - Streaming:
MabStream::updatereturnsNoneuntil warmup is satisfied; subsequentNaNinputs are ignored. MabError::InvalidLengthif 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
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05