Variable Length Moving Average (VLMA)

Parameters: min_period = 5 | max_period = 50 | matype = sma | devtype = 0

Overview

Variable Length Moving Average intelligently adjusts its effective smoothing period based on how far current price deviates from a reference moving average, creating an adaptive system that responds appropriately to different market states. The indicator calculates a reference mean and deviation measure over the maximum period, then uses price distance from this mean to modulate the smoothing length between minimum and maximum bounds. When price pushes beyond wide deviation bands, indicating potential breakouts or strong trends, VLMA shortens its period to track the move more responsively. Conversely, when price hovers near the mean within narrow bands during consolidation, the period extends to filter noise and prevent whipsaw signals. This distance based adaptation helps traders avoid the twin pitfalls of lag during trends and false signals during ranges. The exponential style update preserves smooth output characteristics while the variable period provides the adaptive edge. Default parameters use minimum and maximum periods of 5 and 50 bars with SMA as the reference mean and standard deviation for banding, optimized for broad applicability across different trading styles.

Implementation Examples

Get started with VLMA in just a few lines:

use vectorta::indicators::vlma::{vlma, VlmaInput, VlmaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with price data slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = VlmaParams {
    min_period: Some(5),
    max_period: Some(50),
    matype: Some("sma".to_string()),
    devtype: Some(0),
};
let input = VlmaInput::from_slice(&prices, params);
let result = vlma(&input)?;

// Using with Candles data structure
// Quick and simple with default parameters (min=5, max=50; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = VlmaInput::with_default_candles(&candles);
let result = vlma(&input)?;

// Access the VLMA values
for value in result.values {
    println!("VLMA: {}", value);
}

API Reference

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

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

// From candles with default params (close, min=5, max=50)
VlmaInput::with_default_candles(&Candles) -> VlmaInput
Parameters Structure
#[derive(Debug, Clone)]
pub struct VlmaParams {
    pub min_period: Option<usize>, // Default: 5
    pub max_period: Option<usize>, // Default: 50
    pub matype: Option<String>,    // Default: "sma"
    pub devtype: Option<usize>,    // Default: 0 (0=std, 1=mad, 2=median)
}
Output Structure
#[derive(Debug, Clone)]
pub struct VlmaOutput {
    pub values: Vec<f64>, // Adaptive moving average values
}
Validation, Warmup & NaNs
  • VlmaError::EmptyData if input slice is empty.
  • VlmaError::InvalidPeriodRange when min_period > max_period.
  • VlmaError::InvalidPeriod when max_period == 0 or max_period > data_len.
  • VlmaError::AllValuesNaN if all inputs are NaN.
  • VlmaError::NotEnoughValidData { needed, valid } if fewer than max_period valid points exist after the first finite value.
  • Warmup: output at first_valid equals that input; indices until first_valid + max_period − 1 are NaN. Values start at warmup end.
  • Post‑warmup: if an input at index i is NaN, the output at i is NaN (pass‑through).
  • Period adapts by ±1 per bar and is clamped to [min_period, max_period]. Smoothing uses α = 2/(p+1).
Error Handling
use vectorta::indicators::vlma::{vlma, VlmaError};

match vlma(&input) {
    Ok(output) => process_results(output.values),
    Err(VlmaError::EmptyData) =>
        eprintln!("vlma: empty data"),
    Err(VlmaError::InvalidPeriodRange { min_period, max_period }) =>
        eprintln!("min_period {} must be <= max_period {}", min_period, max_period),
    Err(VlmaError::InvalidPeriod { min_period, max_period, data_len }) =>
        eprintln!("invalid period (min={}, max={}) for data_len={}", min_period, max_period, data_len),
    Err(VlmaError::AllValuesNaN) =>
        eprintln!("all input values are NaN"),
    Err(VlmaError::NotEnoughValidData { needed, valid }) =>
        eprintln!("need {} valid points after first finite, only {}", needed, valid),
    Err(VlmaError::MaError(e)) =>
        eprintln!("reference MA error: {}", e),
    Err(VlmaError::DevError(e)) =>
        eprintln!("deviation error: {}", e),
}

Python Bindings

Basic Usage

Calculate VLMA using NumPy arrays (defaults: min=5, max=50, matype="sma", devtype=0):

import numpy as np
from vectorta import vlma

# Prepare price data as NumPy array
prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5])

# Calculate VLMA with defaults
result = vlma(prices)

# Or specify custom parameters and kernel
result = vlma(prices, min_period=5, max_period=50, matype="sma", devtype=0, kernel="avx2")

print(f"VLMA values: {result}")
Streaming Real-time Updates

Process real-time price updates efficiently:

from vectorta import VlmaStream

# Initialize streaming VLMA calculator
stream = VlmaStream(min_period=5, max_period=50, matype="sma", devtype=0)

for price in price_feed:
    vlma_value = stream.update(price)
    if vlma_value is not None:
        print(f"Current VLMA: {vlma_value}")
Batch Parameter Optimization

Test multiple parameter combinations for optimization:

import numpy as np
from vectorta import vlma_batch

prices = np.array([...])

# Define ranges as (start, end, step)
min_range = (5, 15, 5)
max_range = (30, 60, 10)
dev_range = (0, 2, 1)

results = vlma_batch(
    prices,
    min_period_range=min_range,
    max_period_range=max_range,
    devtype_range=dev_range,
    matype="sma",
    kernel="auto",
)

print(results["values"].shape)     # (num_combos, len(prices))
print(results["min_periods"])      # list of min periods per row
print(results["max_periods"])      # list of max periods per row
print(results["devtypes"])         # list of devtypes per row
print(results["matypes"])          # list of matypes per row
CUDA Acceleration

CUDA support for VLMA is coming soon.

JavaScript/WASM Bindings

Basic Usage

Calculate VLMA in JavaScript/TypeScript:

import { vlma_js } from 'vectorta-wasm';

const prices = new Float64Array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5]);

// Args: data, min_period, max_period, matype, devtype
const values = vlma_js(prices, 5, 50, 'sma', 0);
console.log('VLMA values:', values);
Memory-Efficient Operations

Use zero-copy operations for large datasets:

import { vlma_alloc, vlma_free, vlma_into, memory } from 'vectorta-wasm';

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

const inPtr = vlma_alloc(n);
const outPtr = vlma_alloc(n);
new Float64Array(memory.buffer, inPtr, n).set(prices);

// in_ptr, out_ptr, len, min_period, max_period, matype, devtype
vlma_into(inPtr, outPtr, n, 5, 50, 'sma', 0);

const out = new Float64Array(memory.buffer, outPtr, n).slice();
vlma_free(inPtr, n);
vlma_free(outPtr, n);
console.log('VLMA values:', out);
Batch Processing

Compute multiple parameter combinations:

import { vlma_batch } from 'vectorta-wasm';

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

// Unified batch API: config object
const cfg = {
  min_period_range: [5, 15, 5],   // start, end, step
  max_period_range: [30, 60, 10],
  devtype_range: [0, 2, 1],
  matype: 'sma'
};

// Returns { values, combos, rows, cols }
const res = vlma_batch(prices, cfg);

// values is flat [row0..., row1..., ...] with rows = combos.length
const matrix = [];
for (let r = 0; r < res.rows; r++) {
  const start = r * res.cols;
  matrix.push(res.values.slice(start, start + res.cols));
}

// Access combo parameters
console.log(res.combos[0]); // { min_period, max_period, matype, devtype }
console.log(matrix[0]);     // VLMA for first combo

Performance Analysis

Comparison:
View:
Loading chart...

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

CUDA note

In our benchmark workload, the Rust CPU implementation is faster than CUDA for this indicator. Prefer the Rust/CPU path unless your workload differs.

Related Indicators