Volatility Adjusted Moving Average (VAMA)

Parameters: base_period = 113 | vol_period = 51 | smoothing = true | smooth_type = 3 (1–3) | smooth_period = 5

Overview

Volatility Adjusted Moving Average enhances a traditional exponential moving average by incorporating real time volatility measurements to create a more responsive trend following tool. The indicator begins with a base EMA calculation, then measures deviations from this EMA over a volatility window, tracking both the highest positive and lowest negative deviations during the period. It adds half the sum of these extreme deviations to the base EMA, effectively shifting the moving average toward areas where price has been ranging most actively. This volatility adjustment helps VAMA adapt its position to reflect actual price distribution rather than just average price, making it more relevant for support and resistance identification. The indicator optionally applies a final smoothing stage using SMA, EMA, or WMA to reduce jitter in the volatility adjusted output. Traders appreciate VAMA for its ability to automatically adjust to changing volatility regimes without sacrificing the trending characteristics of exponential averages. Default parameters use a 113 period base EMA, 51 period volatility window, and enabled WMA smoothing over 5 periods, optimized for intermediate to longer term trend analysis.

Defaults: base_period=113, vol_period=51, smoothing=true, smooth_type=3 (WMA), smooth_period=5.

Implementation Examples

Compute VAMA from a price slice or candles:

use vectorta::indicators::moving_averages::volatility_adjusted_ma::{vama, VamaInput, VamaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using a price slice
let prices = vec![100.0, 101.2, 102.4, 101.0, 103.5];
let params = VamaParams {  // defaults: base=113, vol=51, smoothing=true, type=3 (WMA), period=5
    base_period: Some(113),
    vol_period: Some(51),
    smoothing: Some(true),
    smooth_type: Some(3),
    smooth_period: Some(5),
};
let input = VamaInput::from_slice(&prices, params);
let result = vama(&input)?;

// Using Candles with default parameters (source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = VamaInput::with_default_candles(&candles);
let result = vama(&input)?;

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

API Reference

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

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

// Default candles (source="close") with default parameters
VamaInput::with_default_candles(&Candles) -> VamaInput
Parameters Structure
#[derive(Debug, Clone)]
pub struct VamaParams {
    pub base_period: Option<usize>,   // Default: 113
    pub vol_period: Option<usize>,    // Default: 51
    pub smoothing: Option<bool>,      // Default: true
    pub smooth_type: Option<usize>,   // Default: 3 (1=SMA, 2=EMA, 3=WMA)
    pub smooth_period: Option<usize>, // Default: 5
}
Output Structure
#[derive(Debug, Clone)]
pub struct VamaOutput {
    pub values: Vec<f64>, // VAMA values
}
Validation, Warmup & NaNs
  • base_period > 0 and vol_period > 0; both must be ≤ input length, otherwise VamaError::InvalidPeriod.
  • If smoothing=true, smooth_type must be 1, 2, or 3; otherwise VamaError::InvalidSmoothType.
  • Warmup: indices before first_valid + max(base_period, vol_period) − 1 are NaN.
  • If all input values are NaN, returns VamaError::AllValuesNaN.
  • If valid length after first finite value is less than max(base_period, vol_period), returns VamaError::NotEnoughValidData.
  • vama_into_slice requires dst.len() == data.len() or returns VamaError::InvalidPeriod.
Error Handling
use vectorta::indicators::moving_averages::volatility_adjusted_ma::{vama, VamaError};

match vama(&input) {
    Ok(out) => process(out.values),
    Err(VamaError::EmptyInputData) => eprintln!("vama: empty input"),
    Err(VamaError::AllValuesNaN) => eprintln!("vama: all values NaN"),
    Err(VamaError::InvalidPeriod { period, data_len }) =>
        eprintln!("vama: invalid period {} for len {}", period, data_len),
    Err(VamaError::NotEnoughValidData { needed, valid }) =>
        eprintln!("vama: need {} valid points, have {}", needed, valid),
    Err(VamaError::InvalidSmoothType { smooth_type }) =>
        eprintln!("vama: invalid smooth_type {} (use 1,2,3)", smooth_type),
    Err(e) => eprintln!("vama error: {}", e),
}

Python Bindings

Basic Usage

Compute VAMA using NumPy arrays (defaults match Rust):

import numpy as np
from vectorta import vama, vama_batch

prices = np.array([100.0, 101.2, 102.4, 101.0, 103.5])

# Defaults: base=113, vol=51, smoothing=True, smooth_type=3 (WMA), smooth_period=5
vals = vama(prices)

# Custom parameters and kernel selection
vals = vama(
    prices,
    base_period=113,
    vol_period=51,
    smoothing=True,
    smooth_type=3,
    smooth_period=5,
    kernel='auto'
)
Streaming
from vectorta import VamaStream

stream = VamaStream(base_period=113, vol_period=51, smoothing=True, smooth_type=3, smooth_period=5)
for px in live_prices:
    y = stream.update(px)
    if y is not None:
        handle(y)
Batch Processing
import numpy as np
from vectorta import vama_batch

prices = np.array([...], dtype=np.float64)
out = vama_batch(
    prices,
    base_period_range=(100, 130, 10),  # 100,110,120,130
    vol_period_range=(40, 60, 10),     # 40,50,60
    kernel='auto'
)

print(out['values'].shape)     # (num_combos, len(prices))
print(out['base_periods'])     # tested base periods
print(out['vol_periods'])      # tested vol periods
CUDA Acceleration

CUDA kernels are available when built with Python+CUDA features.

import numpy as np
from vectorta import vama_cuda_batch_dev, vama_cuda_many_series_one_param_dev

# One series, many parameter combinations (device result wrapper)
prices_f32 = np.asarray(prices, dtype=np.float32)
dev_arr = vama_cuda_batch_dev(
    prices_f32,
    base_period_range=(100,130,10),
    vol_period_range=(40,60,10),
    device_id=0
)

# Many series (time-major [T, N]), one parameter set
tm = np.asarray(portfolio_tm, dtype=np.float32)  # shape [T, N]
dev_arr2 = vama_cuda_many_series_one_param_dev(
    tm,
    base_period=113,
    vol_period=51,
    device_id=0
)

JavaScript/WASM Bindings

Basic Usage

Compute VAMA in JS/TS:

import { vama_js } from 'vectorta-wasm';

const prices = new Float64Array([100.0, 101.2, 102.4, 101.0, 103.5]);
const values = vama_js(prices, 113, 51, true, 3, 5);
console.log(values);
Memory-Efficient Operations

Use zero-copy buffers for large datasets:

import { vama_alloc, vama_free, vama_into, memory } from 'vectorta-wasm';

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

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

// in_ptr, out_ptr, len, base, vol, smoothing, smooth_type, smooth_period
vama_into(inPtr, outPtr, n, 113, 51, true, 3, 5);
const out = new Float64Array(memory.buffer, outPtr, n).slice();

vama_free(inPtr, n);
vama_free(outPtr, n);
Batch Processing

Sweep parameter ranges and get a flat matrix + metadata:

import { vama_batch as vama_batch_js } from 'vectorta-wasm';

const prices = new Float64Array(/* historical prices */);
const config = {
  base_period_range: [100, 130, 10], // start, end, step
  vol_period_range: [40, 60, 10]
};

// Returns a serialized object with values (flat), combos, rows, cols
const out = vama_batch_js(prices, config);
// out.values is flat array of length rows*cols
// reshape if needed

Performance Analysis

Comparison:
View:
Placeholder data (no recorded benchmarks for this indicator)

Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)

Related Indicators