Average True Range (ATR)

Parameters: length = 14

Overview

ATR measures market volatility by calculating the greatest distance among three ranges: the current high minus low, the absolute difference between current high and previous close, and the absolute difference between current low and previous close. J. Welles Wilder developed this true range concept to capture gaps and limit moves that traditional range calculations miss. The indicator then smooths these true range values using a 14 period modified moving average that weights recent volatility more heavily. Unlike percentage based indicators, ATR expresses volatility in absolute price terms, making it ideal for position sizing where a 2 ATR stop means the same risk regardless of share price. Traders multiply ATR by factors like 1.5 or 2 to set dynamic stop losses that expand during volatile periods and tighten during quiet markets. The indicator also identifies compression patterns when ATR drops to multi period lows, signaling potential breakouts as volatility cycles from contraction to expansion.

Implementation Examples

Compute ATR from OHLC arrays or Candles:

use vectorta::indicators::atr::{atr, AtrInput, AtrParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Sample OHLC data
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 = AtrParams { length: Some(14) };
let input = AtrInput::from_slices(&high, &low, &close, params);
let output = atr(&input)?;

for (idx, value) in output.values.iter().enumerate() {
    println!("ATR[{idx}] = {value:.4}");
}

// Using Candles with default configuration (length = 14)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = AtrInput::with_default_candles(&candles);
let output = atr(&input)?;

API Reference

Input Methods
// From candles
AtrInput::from_candles(&Candles, AtrParams) -> AtrInput
// Using explicit OHLC slices (all must be equal length)
AtrInput::from_slices(&[f64], &[f64], &[f64], AtrParams) -> AtrInput
// Shortcut with defaults (length = 14)
AtrInput::with_default_candles(&Candles) -> AtrInput
Parameters Structure
pub struct AtrParams {
    pub length: Option<usize>, // Default: 14
}
Output Structure
pub struct AtrOutput {
    pub values: Vec<f64>, // ATR per bar aligned with the input length
}
Validation, Warmup & NaNs
  • length > 0 else AtrError::InvalidLength.
  • Slice inputs must have equal lengths; otherwise AtrError::InconsistentSliceLengths.
  • Data must contain at least length valid points after the first valid H/L/C triple; else AtrError::NotEnoughData.
  • Leading outputs up to first_valid + length − 1 are NaN (warmup). Thereafter, values are finite if inputs are finite.
  • Streaming: AtrStream::update returns None until the buffer fills; then returns Some(atr) each update.
Error Handling
use vectorta::indicators::atr::AtrError;

match atr(&input) {
    Ok(output) => consume(output),
    Err(AtrError::InvalidLength { length }) => log::error!("ATR length {length} is invalid"),
    Err(AtrError::InconsistentSliceLengths { high_len, low_len, close_len }) =>
        panic!("Slice length mismatch: high={high_len}, low={low_len}, close={close_len}"),
    Err(AtrError::NoCandlesAvailable) => log::warn!("Candles source returned no rows"),
    Err(AtrError::NotEnoughData { length, data_len }) =>
        log::warn!("Need {length} bars but only have {data_len}"),
}

Python Bindings

Basic Usage

Work with NumPy arrays for high, low, and close series:

import numpy as np
from vectorta import atr

high = np.asarray([101.2, 102.6, 103.1, 101.8, 100.9], dtype=np.float64)
low = np.asarray([ 99.8, 100.7, 101.4,  99.9,  99.6], dtype=np.float64)
close = np.asarray([100.5, 102.1, 101.9, 100.8, 100.2], dtype=np.float64)

values = atr(high, low, close, length=14)
print("ATR values", values)

# Prefer explicit kernel selection when benchmarking
values = atr(high, low, close, length=21, kernel="scalar")
Streaming Updates

Maintain rolling ATR values with the PyO3-backed stream:

from vectorta import AtrStream

stream = AtrStream(length=14)

for candle in ohlc_generator():
    atr_value = stream.update(candle.high, candle.low, candle.close)
    if atr_value is not None:
        use_stream_value(candle.timestamp, atr_value)
Batch Sweeps

Evaluate multiple window sizes without leaving Python:

from vectorta import atr_batch

result = atr_batch(
    high,
    low,
    close,
    length_range=(5, 30, 5),
    kernel="auto",
)

values = result["values"]      # shape: [rows, len(high)]
lengths = result["lengths"]    # vector of window lengths
CUDA Acceleration

CUDA support for ATR is currently under development. The API will mirror other CUDA-enabled indicators once released.

# Coming soon: CUDA-accelerated ATR calculations
# from vectorta import atr_cuda_batch, atr_cuda_series_many_params
# import numpy as np
#
# results = atr_cuda_batch(
#     high=data_high,
#     low=data_low,
#     close=data_close,
#     length_range=(5, 40, 1),
#     device_id=0,
# )
#
# Or process many assets with a single ATR configuration:
# atr_cuda_many_series_one_param(
#     high_matrix,
#     low_matrix,
#     close_matrix,
#     length=14,
#     device_id=0,
# )

JavaScript / WASM Bindings

Quick Usage

Call ATR directly from TypeScript or JavaScript with Float64 arrays:

import { atr_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]);

const values = atr_js(high, low, close, 14);
console.log('ATR values', Array.from(values));
Memory-Efficient Operations

Reuse WASM memory by allocating buffers once and streaming data into them:

import { atr_alloc, atr_free, atr_into, memory } from 'vectorta-wasm';

const len = highs.length;

const highPtr = atr_alloc(len);
const lowPtr = atr_alloc(len);
const closePtr = atr_alloc(len);
const outPtr = atr_alloc(len);

new Float64Array(memory.buffer, highPtr, len).set(highs);
new Float64Array(memory.buffer, lowPtr, len).set(lows);
new Float64Array(memory.buffer, closePtr, len).set(closes);

atr_into(highPtr, lowPtr, closePtr, outPtr, len, 14);

const atrValues = new Float64Array(memory.buffer, outPtr, len).slice();

atr_free(highPtr, len);
atr_free(lowPtr, len);
atr_free(closePtr, len);
atr_free(outPtr, len);
Batch & Metadata

Sweep window sizes and align results with metadata describing every configuration:

import { atr_batch_js, atr_batch_metadata_js, atr_batch_unified_js } from 'vectorta-wasm';

const high = new Float64Array(loadHighs());
const low = new Float64Array(loadLows());
const close = new Float64Array(loadCloses());

const metadata = atr_batch_metadata_js(5, 30, 5); // returns [len1, len2, ...]
const combos = metadata.length;

const flattened = atr_batch_js(high, low, close, 5, 30, 5);
const block = high.length;
const firstResult = flattened.slice(0, block);

const unified = atr_batch_unified_js(high, low, close, {
  length_range: [5, 30, 5],
});

console.log(unified.values.length, unified.lengths.length);

Performance Analysis

Comparison:
View:

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) | Benchmarks: 2026-01-05

Related Indicators