Average True Range (ATR)

Parameters: period = 14 (2–200)

Overview

The Average True Range (ATR) smooths the candle-to-candle true range using a running moving average to reveal how much price typically moves within a bar. Because true range considers gaps and large overnight moves, ATR captures volatility that simple high-low ranges miss, making it a reliable backbone for tactical risk controls.

Rising ATR values flag expanding volatility regimes where wider stops and reduced position sizes are appropriate. Falling ATR values signal contraction and quieter markets. Traders rely on ATR multiples to size trades, trail stops, and identify when markets transition from compression to breakout conditions.

Implementation Examples

Compute ATR from OHLC arrays or candle collections in just a few lines:

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

// 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 = load_workspace_candles()?;
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
Parameter Structure
#[derive(Default, Clone)]
pub struct AtrParams {
    pub length: Option<usize>, // defaults to Some(14)
}
Output Structure
pub struct AtrOutput {
    pub values: Vec<f64>, // ATR per bar aligned with the input length
}
Builder Utilities
use vectorta::indicators::atr::AtrBuilder;
use vectorta::utilities::enums::Kernel;

let builder = AtrBuilder::new()
    .length(21)
    .kernel(Kernel::Auto);

let slice_output = builder.apply_slices(&high, &low, &close)?;
let candle_output = builder.apply(&candles)?;
let stream = builder.into_stream()?;
Streaming API
use vectorta::indicators::atr::{AtrParams, AtrStream};

let mut stream = AtrStream::try_new(AtrParams::default())?;
while let Some(bar) = next_bar() {
    if let Some(atr_value) = stream.update(bar.high, bar.low, bar.close) {
        push_indicator(bar.timestamp, atr_value);
    }
}
Batch Operations
use vectorta::indicators::atr::{AtrBatchBuilder, AtrParams};

let batch = AtrBatchBuilder::new()
    .length_range(5, 40, 5)
    .apply_slices(&high, &low, &close)?;

println!("rows={}, cols={}", batch.rows, batch.cols);

if let Some(values) = batch.values_for(&AtrParams { length: Some(15) }) {
    analyze_volatility_window(values);
}
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,
# )
Return Structure
# atr(...)
np.ndarray  # shape: (len(high),)

# atr_batch(...)
{
    "values": np.ndarray,   # shape: (rows, len(high))
    "lengths": np.ndarray,  # window length per row
}

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

Related Indicators