Aroon Indicator (Aroon)

Parameters: length = 14

Overview

Aroon measures trend strength by calculating how recently price reached its highest high and lowest low within a specified lookback period, converting time observations into momentum signals. Tushar Chande developed this system to identify emerging trends before traditional indicators by focusing on the recency of extremes rather than their magnitude. Aroon Up calculates as ((period - bars since highest high) / period) × 100, while Aroon Down uses the same formula for the lowest low, producing two lines that oscillate between 0 and 100. When Aroon Up stays above 70, it signals consistent new highs and strong upward momentum; similarly, Aroon Down above 70 indicates persistent new lows and bearish pressure. Crossovers between the lines mark potential trend changes, with Aroon Up crossing above Aroon Down suggesting bullish shifts. The indicator excels during trending markets where one line dominates above 70 while the other lingers below 30, but both lines dropping below 50 warns of consolidation phases where breakout strategies may fail.

Implementation Examples

Compute Aroon Up and Down from highs/lows slices or candle data:

use vector_ta::indicators::aroon::{aroon, AroonInput, AroonParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using high/low slices (need at least length + 1 bars)
let high: Vec<f64> = vec![/* ... */];
let low: Vec<f64> = vec![/* ... */];
let params = AroonParams { length: Some(14) };
let input = AroonInput::from_slices_hl(&high, &low, params);
let output = aroon(&input)?;

for (up, down) in output.aroon_up.iter().zip(output.aroon_down.iter()) {
    println!("up={up:.2}, down={down:.2}");
}

// Using Candles with default parameters (length=14; uses high/low columns)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = AroonInput::with_default_candles(&candles);
let output = aroon(&input)?;

API Reference

Input Methods
// From high/low slices
AroonInput::from_slices_hl(&high, &low, AroonParams) -> AroonInput

// From Candles with explicit parameters
AroonInput::from_candles(&candles, AroonParams) -> AroonInput

// Convenience helper (length = 14, uses high/low columns)
AroonInput::with_default_candles(&candles) -> AroonInput
Parameters Structure
pub struct AroonParams {
    pub length: Option<usize>, // Default: 14
}
Output Structure
pub struct AroonOutput {
    pub aroon_up: Vec<f64>,   // Time since highest high, scaled to 0-100
    pub aroon_down: Vec<f64>, // Time since lowest low, scaled to 0-100
}
Validation, Warmup & NaNs
  • length > 0 and length <= data_len; otherwise AroonError::InvalidLength.
  • High/low slices must have equal length; else AroonError::MismatchSliceLength.
  • There must be at least length valid pairs after the first finite pair; else AroonError::NotEnoughValidData.
  • If no finite high/low pair exists, returns AroonError::AllValuesNaN.
  • Warmup: indices before first_valid_index + length are set to NaN.
  • Any window containing a NaN yields NaN at that index; streaming returns None in that case.
Error Handling
use vector_ta::indicators::aroon::{aroon, AroonError};

match aroon(&input) {
    Ok(output) => consume(output),
    Err(AroonError::AllValuesNaN) => {
        eprintln!("All values are NaN");
    }
    Err(AroonError::EmptyInputData) => {
        eprintln!("Input data slice is empty");
    }
    Err(AroonError::MismatchSliceLength { high_len, low_len }) => {
        eprintln!("High/low length mismatch: {high_len} vs {low_len}");
    }
    Err(AroonError::InvalidLength { length, data_len }) => {
        eprintln!("Invalid length {length} for data_len {data_len}");
    }
    Err(AroonError::NotEnoughValidData { needed, valid }) => {
        eprintln!("Need {needed} non-NaN bars, only {valid} provided");
    }
    Err(e) => eprintln!("Aroon failed: {e}"),
}

Python Bindings

Basic Usage

Compute Aroon from NumPy arrays and receive both legs:

import numpy as np
from vector_ta import aroon

high = np.array([101.2, 102.5, 101.8, 103.6, 104.0], dtype=np.float64)
low = np.array([99.8, 100.5, 100.2, 101.7, 102.9], dtype=np.float64)

# Returns a tuple (up, down)
up, down = aroon(high, low, length=14)

print("Aroon Up:", up)
print("Aroon Down:", down)

# Optional: kernel selection ('auto', 'avx2', 'avx512' when available)
up, down = aroon(high, low, length=20, kernel="avx2")
Streaming Real-time Updates

Maintain a rolling window and react as soon as new values stabilize:

from vector_ta import AroonStream

stream = AroonStream(length=14)

for candle in realtime_feed:
    result = stream.update(candle.high, candle.low)

    if result is None:
        continue  # warmup period or NaN in window

    up, down = result
    if up > 70 and down < 30:
        on_trend_confirmation()
    elif down > 70 and up < 30:
        on_downtrend_confirmation()
Batch Parameter Optimization

Scan many lookback lengths and inspect each result efficiently:

import numpy as np
from vector_ta import aroon_batch

high = np.asarray(high_series, dtype=np.float64)
low = np.asarray(low_series, dtype=np.float64)

result = aroon_batch(
    high,
    low,
    length_range=(10, 40, 5),  # start, end, step
    kernel="auto"
)

# result['up'] and result['down'] are (num_lengths, len(series)) arrays
lengths = result["lengths"]
for idx, length in enumerate(lengths):
    up_row = result["up"][idx]
    down_row = result["down"][idx]
    evaluate(length, up_row, down_row)
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 aroon_cuda_batch_dev, aroon_cuda_many_series_one_param_dev

    # One series (float32)
    high_f32 = np.asarray(load_high(), dtype=np.float32)
    low_f32 = np.asarray(load_low(), dtype=np.float32)

    dev = aroon_cuda_batch_dev(
    high_f32=high_f32,
    low_f32=low_f32,
    length_range=(5, 30, 5),
    device_id=0,
    )

    # Many series (time-major)
    high_tm_f32 = np.asarray(load_high_time_major_matrix(), dtype=np.float32)
    low_tm_f32 = np.asarray(load_low_time_major_matrix(), dtype=np.float32)

    dev_tm = aroon_cuda_many_series_one_param_dev(
    high_tm_f32=high_tm_f32,
    low_tm_f32=low_tm_f32,
    length=14,
    device_id=0,
    )

JavaScript/WASM Bindings

Basic Usage

Calculate Aroon in JavaScript/TypeScript:

import { aroon_js } from 'vectorta-wasm';

const high = new Float64Array([101.2, 102.5, 103.6, 104.0]);
const low = new Float64Array([99.8, 100.5, 101.7, 102.9]);

const { up, down } = aroon_js(high, low, 14);
console.log('Aroon Up:', up);
console.log('Aroon Down:', down);

// Provide helper typings for clarity
interface AroonResult {
  up: Float64Array;
  down: Float64Array;
}

const result = aroon_js(high, low, 20) as AroonResult;
Memory-Efficient Operations

Work directly inside WASM memory to avoid extra copies for large datasets (output stores [up, down] back-to-back):

import { aroon_alloc, aroon_free, aroon_into, memory } from 'vectorta-wasm';

const high = new Float64Array([...]);
const low = new Float64Array([...]);
const length = high.length;

// Allocate three buffers; aroon_alloc(len) reserves 2*len f64 slots
const highPtr = aroon_alloc(length);
const lowPtr = aroon_alloc(length);
const outPtr = aroon_alloc(length); // output stores [up, down] of length len each

// Copy inputs into WASM memory
new Float64Array(memory.buffer, highPtr, length).set(high);
new Float64Array(memory.buffer, lowPtr, length).set(low);

// Compute into preallocated memory: args (high_ptr, low_ptr, out_ptr, len, length)
aroon_into(highPtr, lowPtr, outPtr, length, 14);

// Read results: up in [0..len), down in [len..2*len)
const up = new Float64Array(memory.buffer, outPtr, length).slice();
const down = new Float64Array(
  memory.buffer,
  outPtr + length * Float64Array.BYTES_PER_ELEMENT,
  length
).slice();

console.log('Up leg', up);
console.log('Down leg', down);

// Free buffers
aroon_free(highPtr, length);
aroon_free(lowPtr, length);
aroon_free(outPtr, length);
Batch Processing

Evaluate many configurations and track the resulting parameter metadata:

import { aroon_batch_js, aroon_batch_metadata_js } from 'vectorta-wasm';

const high = new Float64Array([...]);
const low = new Float64Array([...]);

const metadata = aroon_batch_metadata_js(10, 40, 5); // [10, 15, 20, ...]
const { up, down, rows, cols, combos } = aroon_batch_js(
  high,
  low,
  10,
  40,
  5
);

// up/down are flattened: reshape if needed
const numCombos = combos.length;
const upRows = [];
for (let i = 0; i < numCombos; i++) {
  const start = i * cols;
  const end = start + cols;
  upRows.push(Array.from(up.slice(start, end)));
}

console.log('Length sweep:', metadata);

CUDA Bindings (Rust)

use vector_ta::cuda::CudaAroonBatchResult;
use vector_ta::indicators::aroon::AroonBatchRange;

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

let high_f32: [f32] = /* ... */;
let low_f32: [f32] = /* ... */;
let sweep = AroonBatchRange::default();

let out = cuda.aroon_batch_dev(&high_f32, &low_f32, &sweep)?;
let _ = out;

Performance Analysis

Comparison:
View:

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

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-02-28

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