Ultimate Moving Average (UMA)

Parameters: accelerator = 1 | min_length = 5 | max_length = 50 | smooth_length = 4

Overview

Ultimate Moving Average dynamically adjusts its effective lookback period based on current price position within volatility bands, creating an intelligent adaptive smoothing system. The indicator calculates a mean and standard deviation over its maximum window, then expands its effective length when price trades near the center to filter noise during consolidation, and contracts the length when price pushes toward band extremes to react quickly during breakouts. This volatility responsive behavior is further enhanced by a momentum component that uses Money Flow Index when volume data is available, or falls back to RSI otherwise, biasing the power weighted average toward recent values during strong directional moves. The adaptive mechanism allows UMA to automatically adjust between smooth trend following in quiet markets and responsive tracking during dynamic price action, eliminating the need for manual parameter optimization as conditions change. Traders value its ability to hug trends closely during momentum phases while remaining stable during choppy periods. Default settings use an accelerator of 1.0, minimum length of 5, maximum length of 50, and output smoothing of 4 periods, optimized for balanced performance across diverse market environments.

Implementation Examples

Compute UMA from price slices or candles (defaults: accelerator=1.0, min=5, max=50, smooth=4):

use vectorta::indicators::moving_averages::uma::{uma, UmaInput, UmaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Price + optional volume slices
let prices = vec![100.0, 101.0, 103.0, 102.5, 104.0, 105.5];
let volume = vec![1200.0, 1500.0, 1700.0, 1600.0, 1800.0, 2100.0];

let params = UmaParams { accelerator: Some(1.0), min_length: Some(5), max_length: Some(50), smooth_length: Some(4) };
let input = UmaInput::from_slice(&prices, Some(&volume), params);
let out = uma(&input)?;

// Using Candles (volume automatically provided; source defaults to "close" via with_default_candles)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = UmaInput::with_default_candles(&candles);
let out = uma(&input)?;

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

API Reference

Input Methods
// From slices (volume optional; enables MFI if provided)
UmaInput::from_slice(&[f64], Option<&[f64]>, UmaParams) -> UmaInput

// From candles with custom source (volume auto-wired)
UmaInput::from_candles(&Candles, &str, UmaParams) -> UmaInput

// From candles with defaults (close, accelerator=1.0, min=5, max=50, smooth=4)
UmaInput::with_default_candles(&Candles) -> UmaInput
Parameters Structure
pub struct UmaParams {
    pub accelerator: Option<f64>,      // Default: 1.0 (must be >= 1.0)
    pub min_length: Option<usize>,     // Default: 5  (must be >= 1)
    pub max_length: Option<usize>,     // Default: 50 (must be >= min_length and <= data_len)
    pub smooth_length: Option<usize>,  // Default: 4  (must be >= 1)
}
Output Structure
pub struct UmaOutput {
    pub values: Vec<f64>, // UMA values (optionally WMA-smoothed)
}
Validation, Warmup & NaNs
  • accelerator >= 1.0, min_length >= 1, smooth_length >= 1.
  • max_length >= 1 and min_length <= max_length; also max_length <= data_len.
  • Requires at least max_length valid points after the first finite value; else UmaError::NotEnoughValidData.
  • Warmup: prefix indices up to first + max_length - 1 are NaN. Smoothing (smooth_length > 1) is applied after core UMA.
  • Volume optional: provided volume enables an MFI-based momentum; otherwise RSI fallback or a simplified volume-momentum is used.
Error Handling
use vectorta::indicators::moving_averages::uma::{uma, UmaError};

match uma(&input) {
    Ok(output) => consume(output.values),
    Err(UmaError::EmptyInputData) => eprintln!("Input data is empty"),
    Err(UmaError::AllValuesNaN) => eprintln!("All input values are NaN"),
    Err(UmaError::InvalidMaxLength { max_length, data_len }) =>
        eprintln!("max_length {} invalid for data length {}", max_length, data_len),
    Err(UmaError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid points after first finite, got {}", needed, valid),
    Err(UmaError::InvalidAccelerator { accelerator }) =>
        eprintln!("accelerator must be >= 1.0 (got {})", accelerator),
    Err(UmaError::InvalidMinLength { min_length }) =>
        eprintln!("min_length must be >= 1 (got {})", min_length),
    Err(UmaError::InvalidSmoothLength { smooth_length }) =>
        eprintln!("smooth_length must be >= 1 (got {})", smooth_length),
    Err(UmaError::MinLengthGreaterThanMaxLength { min_length, max_length }) =>
        eprintln!("min_length {} must be <= max_length {}", min_length, max_length),
    Err(UmaError::DependencyError(e)) => eprintln!("Dependency error: {}", e),
}

Python Bindings

Basic Usage

Calculate UMA using NumPy arrays. Pass volume to enable MFI; otherwise RSI fallback is used:

import numpy as np
from vectorta import uma

prices = np.array([100, 101, 103, 102.5, 104, 105.5], dtype=np.float64)
volume = np.array([1200, 1500, 1700, 1600, 1800, 2100], dtype=np.float64)

# Defaults: accelerator=1.0, min_length=5, max_length=50, smooth_length=4
vals = uma(prices, accelerator=1.0, min_length=5, max_length=50, smooth_length=4,
           volume=volume, kernel="auto")

print(vals)  # NumPy array
Streaming Real-time Updates
from vectorta import UmaStream

stream = UmaStream(accelerator=1.0, min_length=5, max_length=50, smooth_length=4)

for (px, vol) in zip(price_feed, volume_feed):
    val = stream.update_with_volume(px, vol)
    if val is not None:
        on_signal(val)

# Without volume
val = stream.update(106.0)
Batch Parameter Optimization
import numpy as np
from vectorta import uma_batch

prices = np.array([...], dtype=np.float64)
volume = np.array([...], dtype=np.float64)

res = uma_batch(
    data=prices,
    accelerator_range=(1.0, 2.0, 0.5),
    min_length_range=(5, 15, 5),
    max_length_range=(30, 60, 10),
    smooth_length_range=(3, 6, 1),
    volume=volume,
    kernel="auto",
)

print(res["values"].shape)  # (rows, len(prices))
print(res["accelerators"], res["min_lengths"], res["max_lengths"], res["smooth_lengths"])
CUDA Acceleration

UMA includes CUDA exports when built with GPU support:

# GPU batch sweep on one series (F32 on device)
from vectorta import uma_cuda_batch_dev
res_dev = uma_cuda_batch_dev(
    data_f32=prices.astype(np.float32),
    accelerator_range=(1.0, 2.0, 0.5),
    min_length_range=(5, 15, 5),
    max_length_range=(30, 60, 10),
    smooth_length_range=(3, 6, 1),
    volume_f32=volume.astype(np.float32),
    device_id=0,
)

# Many series (time-major) with one parameter set
from vectorta import uma_cuda_many_series_one_param_dev
tm = portfolio_prices_tm.astype(np.float32)  # shape [T, N]
dev_out = uma_cuda_many_series_one_param_dev(
    prices_tm_f32=tm,
    accelerator=1.0,
    min_length=5,
    max_length=50,
    smooth_length=4,
    volume_tm_f32=portfolio_volume_tm.astype(np.float32),
    device_id=0,
)

JavaScript/WASM Bindings

Basic Usage

Compute UMA (volume optional):

import { uma_js } from 'vectorta-wasm';

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

// args: data, accelerator, min_length, max_length, smooth_length, volume?
const result = uma_js(prices, 1.0, 5, 50, 4, volume);
console.log('UMA values:', result.values);
Memory-Efficient Operations

Use zero-copy helpers for large arrays:

import { uma_alloc, uma_free, uma_into, memory } from 'vectorta-wasm';

const prices = new Float64Array([/* ... */]);
const len = prices.length;
const inPtr = uma_alloc(len);
const outPtr = uma_alloc(len);

new Float64Array(memory.buffer, inPtr, len).set(prices);

// in_ptr, out_ptr, len, accelerator, min_length, max_length, smooth_length
await uma_into(inPtr, outPtr, len, 1.0, 5, 50, 4);

const out = new Float64Array(memory.buffer, outPtr, len).slice();
uma_free(inPtr, len); uma_free(outPtr, len);
Batch Processing

Test parameter grids and inspect combos:

import { uma_batch_js } from 'vectorta-wasm';

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

// Each range: [start, end, step]
const out = uma_batch_js(
  prices,
  [1.0, 2.0, 0.5],
  [5, 15, 5],
  [30, 60, 10],
  [3, 6, 1],
  volume,
);

// out.values is flat [rows*len]; arrays for each parameter are provided
console.log(out.rows, out.cols);
console.log(out.accelerators, out.min_lengths, out.max_lengths, out.smooth_lengths);

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators