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 vectorta::indicators::aroon::{aroon, AroonInput, AroonParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using high/low slices
let high = vec![101.2, 102.5, 101.8, 103.6, 104.0];
let low = vec![99.8, 100.5, 100.2, 101.7, 102.9];
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 vectorta::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 vectorta 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 vectorta 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 vectorta 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)

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);

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-01-05

Related Indicators