Median Absolute Deviation (MEDIUM_AD)

Parameters: period = 5

Overview

Median Absolute Deviation (MEDIUM_AD) provides the most robust statistical measure of price volatility by calculating the median of absolute deviations from the median price within a rolling window, achieving a 50% breakdown point that remains reliable even when up to half the data points are outliers. Unlike Mean Absolute Deviation which uses arithmetic averages, MEDIUM_AD employs medians at both stages of calculation, first finding the median price in the window, then computing each price's absolute deviation from that median, and finally taking the median of those deviations, creating double protection against extreme values. In normally distributed markets, standard deviation equals approximately 1.4826 times the MEDIUM_AD (the consistency constant), but during market crises with fat-tailed distributions, MEDIUM_AD provides dramatically more stable readings by completely ignoring outliers beyond the middle 50% of data. Traders apply MEDIUM_AD for fraud detection in transaction monitoring where unusual price movements need identification without false positives from legitimate spikes, for portfolio risk management where episodic market shocks shouldn't distort long-term volatility estimates, and for adaptive stop-loss systems that remain stable during flash crashes. The indicator excels in asymmetric market conditions where separate upper and lower MAD calculations can capture different volatility characteristics for upward versus downward price movements, making it particularly valuable for options traders analyzing skewed risk profiles.

Defaults: period = 5.

Implementation Examples

Compute robust dispersion in a few lines:

use vectorta::indicators::medium_ad::{medium_ad, MediumAdInput, MediumAdParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using a price/series slice
let prices = vec![100.0, 101.0, 98.0, 99.5, 102.0, 101.5];
let params = MediumAdParams { period: Some(5) }; // default = 5
let input = MediumAdInput::from_slice(&prices, params);
let result = medium_ad(&input)?;

// Using Candles (defaults: source="close", period=5 via with_default_candles)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = MediumAdInput::with_default_candles(&candles);
let result = medium_ad(&input)?;

// Access MAD values
for v in result.values {
    println!("MAD: {}", v);
}

API Reference

Input Methods
// From slice
MediumAdInput::from_slice(&[f64], MediumAdParams) -> MediumAdInput

// From candles with explicit source
MediumAdInput::from_candles(&Candles, &str, MediumAdParams) -> MediumAdInput

// From candles with defaults (source = "close", period = 5)
MediumAdInput::with_default_candles(&Candles) -> MediumAdInput
Parameters Structure
pub struct MediumAdParams {
    pub period: Option<usize>, // Default: 5
}
Output Structure
pub struct MediumAdOutput {
    pub values: Vec<f64>, // Per-index MAD (>= 0)
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ data.len() else MediumAdError::InvalidPeriod.
  • If all inputs are NaNMediumAdError::AllValuesNaN; empty slice → MediumAdError::EmptyData.
  • Require at least period valid points after the first finite value; otherwise MediumAdError::NotEnoughValidData.
  • Warmup: indices [0 .. first_valid + period − 1) are NaN.
  • Windows containing any NaN produce NaN at that index.
  • Special case: period=1 → output is 0.0 for finite inputs; NaN where inputs are NaN.
  • Streaming: returns None until the window fills and contains no NaN; subsequent NaNs delay outputs.
Error Handling
use vectorta::indicators::medium_ad::{medium_ad, MediumAdError};

match medium_ad(&input) {
    Ok(output) => process(output.values),
    Err(MediumAdError::EmptyData) => eprintln!("Input data is empty"),
    Err(MediumAdError::AllValuesNaN) => eprintln!("All input values are NaN"),
    Err(MediumAdError::InvalidPeriod { period, data_len }) =>
        eprintln!("Invalid period={} for data_len={}", period, data_len),
    Err(MediumAdError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid points, only {}", needed, valid),
}

Python Bindings

Basic Usage

Calculate MEDIUM_AD using NumPy arrays:

import numpy as np
from vectorta import medium_ad

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

# Default period=5
values = medium_ad(prices, period=5)

# Specify kernel for performance ("auto", "avx2", "avx512", "scalar")
values = medium_ad(prices, period=7, kernel="auto")

print(values)
Streaming Real-time Updates

Process real-time updates efficiently:

from vectorta import MediumAdStream

stream = MediumAdStream(period=5)

for price in price_feed:
    mad = stream.update(price)
    if mad is not None:
        print(f"MAD: {mad}")
Batch Parameter Sweep

Evaluate multiple periods at once:

import numpy as np
from vectorta import medium_ad_batch

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

results = medium_ad_batch(
    prices,
    period_range=(3, 20, 1),  # start, end, step
    kernel="auto"
)

print(results['values'].shape)  # (rows, cols) = (num_periods, len(prices))
print(results['periods'])       # periods tested
CUDA Acceleration

CUDA support for MEDIUM_AD is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated MEDIUM_AD calculations
# Expected APIs will mirror existing CUDA-enabled indicators.

JavaScript/WASM Bindings

Basic Usage

Calculate MEDIUM_AD in JavaScript/TypeScript:

import { medium_ad_js } from 'vectorta-wasm';

const prices = new Float64Array([100, 101, 98, 99.5, 102, 101.5]);
const values = medium_ad_js(prices, 5); // period=5
console.log('MAD values:', values);
Memory-Efficient Operations

Use zero-copy buffers for large datasets:

import { medium_ad_alloc, medium_ad_free, medium_ad_into, memory } from 'vectorta-wasm';

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

const inPtr = medium_ad_alloc(n);
const outPtr = medium_ad_alloc(n);

// Copy input into WASM memory
new Float64Array(memory.buffer, inPtr, n).set(prices);

// Compute MAD directly into output buffer
medium_ad_into(inPtr, outPtr, n, 5);

// Read results back (slice to copy)
const mad = new Float64Array(memory.buffer, outPtr, n).slice();

medium_ad_free(inPtr, n);
medium_ad_free(outPtr, n);
console.log('MAD:', mad);
Batch Processing

Sweep periods and analyze many outputs:

import { medium_ad_batch, medium_ad_batch_into, medium_ad_alloc, medium_ad_free, memory } from 'vectorta-wasm';

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

// Option A: Unified batch returning an object
const result = medium_ad_batch(prices, { period_range: [3, 20, 1] });
// result: { values: Float64Array, combos: Array&lt;{ period: number | null }&gt;, rows: number, cols: number }
console.log(result.rows, result.cols);

// Option B: Zero-copy into a pre-allocated buffer
const rows = 20 - 3 + 1; // for start=3, end=20, step=1
const outPtr = medium_ad_alloc(rows * prices.length);
const inPtr = medium_ad_alloc(prices.length);
new Float64Array(memory.buffer, inPtr, prices.length).set(prices);

const numRows = medium_ad_batch_into(inPtr, outPtr, prices.length, 3, 20, 1);
const flat = new Float64Array(memory.buffer, outPtr, numRows * prices.length).slice();

medium_ad_free(inPtr, prices.length);
medium_ad_free(outPtr, rows * prices.length);

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators