Normalized Moving Average (NMA)
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 > 0andperiod ≤ len; otherwiseNmaError::InvalidPeriod.- Requires at least
period + 1valid points after the first finite value; elseNmaError::NotEnoughValidData. - Indices before
first_finite + periodareNaN(warmup). Streaming yieldsNoneuntil 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
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
Related Indicators
Arnaud Legoux Moving Average
Moving average indicator
Compound Ratio Moving Average (CoRa Wave)
Moving average indicator
Centered Weighted Moving Average
Moving average indicator
Double Exponential Moving Average
Moving average indicator
Ehlers Distance Coefficient Filter
Moving average indicator
Ehlers Error-Correcting EMA (ECEMA)
Moving average indicator