Average True Range (ATR)
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 > 0elseAtrError::InvalidLength.- Slice inputs must have equal lengths; otherwise
AtrError::InconsistentSliceLengths. - Data must contain at least
lengthvalid points after the first valid H/L/C triple; elseAtrError::NotEnoughData. - Leading outputs up to
first_valid + length − 1areNaN(warmup). Thereafter, values are finite if inputs are finite. - Streaming:
AtrStream::updatereturnsNoneuntil the buffer fills; then returnsSome(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
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05