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 vector_ta::indicators::atr::{atr, AtrInput, AtrParams};
use vector_ta::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 vector_ta::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 vector_ta 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 vector_ta 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 vector_ta 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 helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible).

import numpy as np
from vector_ta import atr_cuda_batch_dev, atr_cuda_many_series_one_param_dev

# One series (float32)
high = np.asarray(load_high(), dtype=np.float32)
low = np.asarray(load_low(), dtype=np.float32)
close = np.asarray(load_close(), dtype=np.float32)

dev = atr_cuda_batch_dev(
    high=high,
    low=low,
    close=close,
    length_range=(5, 30, 5),
    device_id=0,
)

# Many series (time-major)
high_tm = np.asarray(load_high_time_major_matrix(), dtype=np.float32)
rows, cols = high_tm.shape
high_tm = high_tm.ravel()
low_tm = np.asarray(load_low_time_major_matrix(), dtype=np.float32)
low_tm = low_tm.ravel()
close_tm = np.asarray(load_close_time_major_matrix(), dtype=np.float32)
close_tm = close_tm.ravel()

dev_tm = atr_cuda_many_series_one_param_dev(
    high_tm=high_tm,
    low_tm=low_tm,
    close_tm=close_tm,
    cols=cols,
    rows=rows,
    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);

CUDA Bindings (Rust)

use vector_ta::cuda::CudaAtr;
use vector_ta::indicators::atr::AtrBatchRange;

let cuda = CudaAtr::new(0)?;

let high: [f32] = /* ... */;
let low: [f32] = /* ... */;
let close: [f32] = /* ... */;
let sweep = AtrBatchRange::default();

let out = cuda.atr_batch_dev(&high, &low, &close, &sweep)?;
let _ = out;

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-02-28

Related Indicators