Normalized Average True Range (NATR)

Parameters: 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::EmptyData or NatrError::MismatchedLength.
  • period > 0 and period ≤ len; else NatrError::InvalidPeriod { period, data_len }.
  • First valid index is max of first finite values in high/low/close; if none, NatrError::AllValuesNaN.
  • Requires at least period valid points after first valid index; else NatrError::NotEnoughValidData { needed, valid }.
  • Warmup: indices before first_valid_idx + period - 1 are NaN.
  • If close is non‑finite or zero at an index, the output at that index is NaN.
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

Comparison:
View:

Across sizes, Rust CPU runs about 1.01× slower than Tulip C in this benchmark.

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