Normalized Moving Average (NMA)

Parameters: period = 40

Overview

The Normalized Moving Average (NMA) delivers adaptive smoothing by measuring price changes in logarithmic space. The algorithm computes absolute differences between consecutive log prices across the lookback window, then applies square root weights to emphasize recent variations. These weighted differences form a normalization ratio that interpolates between the two oldest points in the window. This approach scales dynamically with volatility, preventing large price swings from dominating the average while preserving sensitivity to smaller movements. Traders use NMA when they need a responsive trend filter that automatically adjusts to varying market conditions. The default 40-period setting balances reactivity with stability, though shorter periods increase responsiveness at the cost of additional noise.

Implementation Examples

Basic usage with slices and candles (default period = 40):

use vectorta::indicators::moving_averages::nma::{nma, NmaInput, NmaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = NmaParams { period: Some(40) };
let input = NmaInput::from_slice(&prices, params);
let result = nma(&input)?;

// From candles with default params (period=40; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = NmaInput::with_default_candles(&candles);
let result = nma(&input)?;

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

API Reference

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

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

// From candles with default params (period=40; source="close")
NmaInput::with_default_candles(&Candles) -> NmaInput
Parameters Structure
pub struct NmaParams {
    pub period: Option<usize>, // Default: 40
}
Output Structure
pub struct NmaOutput {
    pub values: Vec<f64>, // NMA values
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ len; otherwise NmaError::InvalidPeriod.
  • Requires at least period + 1 valid points after the first finite value; else NmaError::NotEnoughValidData.
  • Indices before first_finite + period are NaN (warmup). Streaming yields None until the ring buffer fills.
  • Subsequent NaNs in the input propagate into outputs; the first finite element is auto-detected.
Error Handling
use vectorta::indicators::moving_averages::nma::{nma, NmaError};

match nma(&input) {
    Ok(output) => process_results(output.values),
    Err(NmaError::EmptyInputData) =>
        println!("Input data is empty"),
    Err(NmaError::AllValuesNaN) =>
        println!("All input values are NaN"),
    Err(NmaError::InvalidPeriod { period, data_len }) =>
        println!("Invalid period {} for length {}", period, data_len),
    Err(NmaError::NotEnoughValidData { needed, valid }) =>
        println!("Need {} data points, only {} valid", needed, valid),
}

Python Bindings

Basic Usage

Calculate NMA using NumPy arrays (default period = 40):

import numpy as np
from vectorta import nma, NmaStream, nma_batch

prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5])

# Single run
values = nma(prices, period=40, kernel="auto")

# Streaming
stream = NmaStream(40)
for p in prices:
    y = stream.update(p)  # None until buffer full

# Batch sweep
result = nma_batch(prices, period_range=(20, 60, 10), kernel="auto")
vals2d = result['values']   # shape: (num_periods, len(prices))
periods = result['periods'] # tested periods
CUDA Acceleration

CUDA-enabled APIs are available when built with CUDA:

import numpy as np
from vectorta import nma_cuda_batch_dev, nma_cuda_many_series_one_param_dev

# One series, many parameters (F32 input for GPU)
prices_f32 = np.asarray(prices, dtype=np.float32)
dev_array, meta = nma_cuda_batch_dev(prices_f32, period_range=(20, 60, 10), device_id=0)
print(meta['periods'])  # GPU-computed rows correspond to these periods

# Many series, one parameter (time-major [T, N])
tm = np.random.rand(1000, 64).astype(np.float32)
dev_array2 = nma_cuda_many_series_one_param_dev(tm, period=40, device_id=0)

JavaScript/WASM Bindings

Basic Usage

Calculate NMA in JavaScript/TypeScript:

import { nma_js } from 'vectorta-wasm';

const prices = new Float64Array([100, 102, 101.5, 103, 105, 104.5]);
const values = nma_js(prices, 40);
console.log('NMA:', values);
Memory-Efficient Operations

Use zero-copy functions for large datasets:

import { nma_alloc, nma_free, nma_into, memory } from 'vectorta-wasm';

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

// Allocate WASM memory
const inPtr = nma_alloc(len);
const outPtr = nma_alloc(len);

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

// Compute NMA directly into output buffer
nma_into(inPtr, outPtr, len, 40);

// Read results
const out = new Float64Array(memory.buffer, outPtr, len).slice();

// Free memory
nma_free(inPtr, len);
nma_free(outPtr, len);
Batch Processing

Test multiple periods efficiently:

import { nma_batch_js, nma_batch_metadata_js } from 'vectorta-wasm';

const prices = new Float64Array([/* historical prices */]);
const start = 20, end = 60, step = 10; // 20,30,40,50,60

// Metadata: tested periods
const periods = nma_batch_metadata_js(start, end, step);

// Values for all combinations (flat array)
const flat = nma_batch_js(prices, start, end, step);

// Reshape into rows x cols
const rows = periods.length;
const cols = prices.length;
const matrix: number[][] = [];
for (let r = 0; r < rows; r++) {
  const off = r * cols;
  matrix.push(Array.from(flat.slice(off, off + cols)));
}

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators