Normalized Average True Range (NATR)
period = 14 Overview
The Normalized Average True Range expresses volatility as a percentage of price rather than absolute dollar amounts, enabling direct comparison of volatility across assets trading at vastly different price levels. NATR calculates the standard ATR value then divides it by the closing price and multiplies by 100, transforming raw volatility into a percentage that remains consistent whether examining a $10 stock or a $1000 stock. This normalization proves invaluable for position sizing, where traders need to assess relative risk across diverse portfolios, and for screening markets to find assets with similar volatility characteristics regardless of price. The indicator retains all of ATR's advantages in handling gaps and limit moves through its use of true range, while adding the ability to meaningfully compare a penny stock's 5% daily movement against a blue chip's 1% swing. Portfolio managers particularly value NATR for risk parity strategies where equal volatility weighting across positions requires normalized rather than absolute volatility measurements.
Implementation Examples
Compute NATR from OHLC arrays or candles in a few lines:
use vectorta::indicators::natr::{natr, NatrInput, NatrParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// OHLC slices (all equal length)
let high = vec![101.2, 102.6, 103.1, 101.8, 100.9];
let low = vec![ 99.8, 100.7, 101.4, 99.9, 99.6];
let close= vec![100.5, 102.1, 101.9, 100.8, 100.2];
let params = NatrParams { period: Some(14) }; // default
let input = NatrInput::from_slices(&high, &low, &close, params);
let out = natr(&input)?;
for (i, v) in out.values.iter().enumerate() {
println!("NATR[%d] = %.4f", i, v);
}
// With Candles and defaults (period = 14)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = NatrInput::with_default_candles(&candles);
let out = natr(&input)?; API Reference
Input Methods ▼
// From candles (default period = 14)
NatrInput::from_candles(&Candles, NatrParams) -> NatrInput
// From explicit OHLC slices (equal length)
NatrInput::from_slices(&[f64], &[f64], &[f64], NatrParams) -> NatrInput
// Shortcut with defaults (period = 14)
NatrInput::with_default_candles(&Candles) -> NatrInput Parameters Structure ▼
#[derive(Debug, Clone, Default)]
pub struct NatrParams {
pub period: Option<usize>, // Default: Some(14)
} Output Structure ▼
pub struct NatrOutput {
pub values: Vec<f64>, // NATR percentage values, aligned to input length
} Validation, Warmup & NaNs ▼
- Inputs must be non-empty and equal length; otherwise
NatrError::EmptyDataorNatrError::MismatchedLength. period > 0andperiod ≤ len; elseNatrError::InvalidPeriod { period, data_len }.- First valid index is max of first finite values in
high/low/close; if none,NatrError::AllValuesNaN. - Requires at least
periodvalid points after first valid index; elseNatrError::NotEnoughValidData { needed, valid }. - Warmup: indices before
first_valid_idx + period - 1areNaN. - If
closeis non‑finite or zero at an index, the output at that index isNaN.
Error Handling ▼
use vectorta::indicators::natr::{natr, NatrError};
match natr(&input) {
Ok(output) => process(output.values),
Err(NatrError::EmptyData) => eprintln!("natr: empty data"),
Err(NatrError::InvalidPeriod { period, data_len }) => {
eprintln!("natr: invalid period {} for length {}", period, data_len);
}
Err(NatrError::NotEnoughValidData { needed, valid }) => {
eprintln!("natr: need {} valid values, found {}", needed, valid);
}
Err(NatrError::AllValuesNaN) => eprintln!("natr: all inputs are NaN"),
Err(NatrError::MismatchedLength { expected, actual }) => {
eprintln!("natr: length mismatch, expected {}, actual {}", expected, actual);
}
} Python Bindings
Basic Usage ▼
Calculate NATR using NumPy arrays (defaults: period=14). Kernel selection is optional.
import numpy as np
from vectorta import natr
high = np.array([101.2, 102.6, 103.1, 101.8, 100.9], dtype=float)
low = np.array([ 99.8, 100.7, 101.4, 99.9, 99.6], dtype=float)
close= np.array([100.5, 102.1, 101.9, 100.8, 100.2], dtype=float)
# Default period = 14
values = natr(high, low, close, period=14) # returns np.ndarray
# Specify kernel for performance (optional): 'auto', 'scalar', 'avx2', 'avx512'
values = natr(high, low, close, period=14, kernel='auto') Batch Processing ▼
Sweep period ranges and get a 2D matrix of results:
import numpy as np
from vectorta import natr_batch
high = np.asarray([...], dtype=float)
low = np.asarray([...], dtype=float)
close= np.asarray([...], dtype=float)
res = natr_batch(high, low, close, period_range=(7, 28, 1))
# res['values']: 2D array [rows x cols], res['periods']: 1D periods tested
matrix = res['values']
periods = res['periods'] Streaming ▼
from vectorta import NatrStream
stream = NatrStream(14)
for h, l, c in realtime_ohlc_feed():
value_or_none = stream.update(h, l, c)
if value_or_none is not None:
handle(value_or_none) CUDA Acceleration ▼
CUDA support for NATR is coming soon. Interfaces will mirror other CUDA-enabled indicators.
# Coming soon: CUDA-accelerated NATR APIs JavaScript/WASM Bindings
Basic Usage ▼
Compute NATR from OHLC arrays:
import { natr_js } from 'vectorta-wasm';
const high = new Float64Array([101.2, 102.6, 103.1, 101.8, 100.9]);
const low = new Float64Array([ 99.8, 100.7, 101.4, 99.9, 99.6]);
const close = new Float64Array([100.5, 102.1, 101.9, 100.8, 100.2]);
// period = 14
const values = natr_js(high, low, close, 14);
console.log('NATR:', values); Memory-Efficient Operations ▼
Zero-copy into pre-allocated WASM memory:
import { natr_alloc, natr_free, natr_into, memory } from 'vectorta-wasm';
const len = close.length;
const hPtr = natr_alloc(len);
const lPtr = natr_alloc(len);
const cPtr = natr_alloc(len);
const outPtr = natr_alloc(len);
// Copy inputs into WASM memory
new Float64Array(memory.buffer, hPtr, len).set(high);
new Float64Array(memory.buffer, lPtr, len).set(low);
new Float64Array(memory.buffer, cPtr, len).set(close);
// Write results into outPtr
natr_into(hPtr, lPtr, cPtr, outPtr, len, 14);
const out = new Float64Array(memory.buffer, outPtr, len).slice();
natr_free(hPtr, len); natr_free(lPtr, len); natr_free(cPtr, len); natr_free(outPtr, len); Batch Processing ▼
Sweep period ranges using a unified config object:
import { natr_batch } from 'vectorta-wasm';
const cfg = { period_range: [7, 28, 1] };
const { values, combos, rows, cols } = natr_batch(high, low, close, cfg);
// 'values' is flat [rows * cols]; 'combos' are NatrParams objects
const firstRow = values.slice(0, cols); Performance Analysis
Across sizes, Rust CPU runs about 1.01× slower than Tulip C in this benchmark.
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.