Elder Ray Index (ERI)

Parameters: period = 13 | ma_type = ema

Overview

The Elder Ray Index reveals the underlying battle between bulls and bears by measuring how far prices extend beyond a moving average baseline, with Bull Power tracking highs above the average and Bear Power measuring lows below it. Bull Power captures the maximum price buyers could push above consensus value, while Bear Power shows how deeply sellers drove prices beneath equilibrium during each period. When Bull Power rises while Bear Power remains negative but improving, it signals accumulation as buyers gain strength while sellers lose conviction. Traders watch for divergences where declining Bull Power during uptrends warns of weakening buying pressure, or rising Bear Power in downtrends indicates reduced selling intensity. The indicator excels at confirming trend entries when both powers align directionally and identifying potential reversals when they diverge from price action. Unlike momentum oscillators that measure rate of change, Elder Ray uniquely quantifies the actual price extremes achieved by each side of the market relative to the average consensus price.

Implementation Examples

Get ERI bull/bear power in a few lines:

use vector_ta::indicators::eri::{eri, EriInput, EriParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with slices (high, low, and source e.g. close)
let high = vec![101.0, 103.0, 102.5, 104.0, 105.0];
let low =  vec![ 99.5, 100.8, 100.9, 102.0, 103.6];
let close = vec![100.5, 102.0, 101.8, 103.2, 104.7];
let params = EriParams { period: Some(13), ma_type: Some("ema".into()) };
let input = EriInput::from_slices(&high, &low, &close, params);
let out = eri(&input)?; // out.bull, out.bear

// Using with Candles (defaults: source="close", period=13, ma_type="ema")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = EriInput::with_default_candles(&candles);
let out = eri(&input)?;

API Reference

Input Methods
// From candles with custom source
EriInput::from_candles(&Candles, &str, EriParams) -> EriInput

// From high/low/source slices
EriInput::from_slices(&[f64], &[f64], &[f64], EriParams) -> EriInput

// From candles with defaults (source="close", period=13, ma_type="ema")
EriInput::with_default_candles(&Candles) -> EriInput
Parameters Structure
pub struct EriParams {
    pub period: Option<usize>,   // Default: 13
    pub ma_type: Option<String>, // Default: "ema"
}
Output Structure
pub struct EriOutput {
    pub bull: Vec<f64>, // High - MA (bull power)
    pub bear: Vec<f64>, // Low  - MA (bear power)
}
Validation, Warmup & NaNs
  • period > 0 and period ≤ data_len; otherwise EriError::InvalidPeriod { period, data_len }.
  • First valid index is the first i where high[i], low[i], source[i] are all finite; must have at least period valid points after it or EriError::NotEnoughValidData { needed, valid }.
  • Warmup: indices [0 .. first_valid + period − 1] are NaN in both outputs.
  • All inputs NaNEriError::AllValuesNaN. Empty inputs → EriError::EmptyData.
  • Unsupported or failing MA selection yields EriError::MaCalculationError(String).
  • Streaming: EriStream::update returns None until the window is filled, then Some((bull, bear)) each tick.
Error Handling
use vector_ta::indicators::eri::{eri, EriError, EriInput, EriParams};

match eri(&input) {
    Ok(out) => {
        process(out.bull);
        process(out.bear);
    }
    Err(EriError::AllValuesNaN) => eprintln!("eri: all inputs are NaN"),
    Err(EriError::EmptyData) => eprintln!("eri: empty input data"),
    Err(EriError::InvalidPeriod { period, data_len }) =>
        eprintln!("eri: invalid period={} for len={}", period, data_len),
    Err(EriError::NotEnoughValidData { needed, valid }) =>
        eprintln!("eri: need {} valid points, have {}", needed, valid),
    Err(EriError::MaCalculationError(msg)) => eprintln!("eri: MA error: {}", msg),
    Err(EriError::InvalidBatchKernel(_)) => eprintln!("eri: invalid batch kernel for batch function"),
}

Python Bindings

Basic Usage
import numpy as np
from vector_ta import eri, EriStream, eri_batch

high = np.array([101.0, 103.0, 102.5, 104.0], dtype=float)
low  = np.array([ 99.5, 100.8, 100.9, 102.0], dtype=float)
src  = np.array([100.5, 102.0, 101.8, 103.2], dtype=float)

# One-shot calculation (returns bull, bear arrays)
bull, bear = eri(high, low, src, period=13, ma_type="ema", kernel="auto")
print(bull.shape, bear.shape)
Streaming
from vector_ta import EriStream

stream = EriStream(period=13, ma_type="ema")
for h, l, s in zip(high, low, src):
    pair = stream.update(float(h), float(l), float(s))
    if pair is not None:
        bull, bear = pair
        handle(bull, bear)
Batch Processing
import numpy as np
from vector_ta import eri_batch

high = np.array([...], dtype=float)
low  = np.array([...], dtype=float)
src  = np.array([...], dtype=float)

res = eri_batch(high, low, src, period_range=(5, 30, 5), ma_type="ema", kernel="auto")
print(res.keys())  # bull_values, bear_values, periods, ma_types
print(res['bull_values'].shape, res['bear_values'].shape)  # (rows, len)
print(res['periods'])
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 eri_cuda_batch_dev, eri_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)
source_f32 = np.asarray(load_series(), dtype=np.float32)

dev = eri_cuda_batch_dev(
    high_f32=high_f32,
    low_f32=low_f32,
    source_f32=source_f32,
    period_range=(5, 30, 5),
    ma_type="ema",
    device_id=0,
)

# Many series (time-major)
high_tm_f32 = np.asarray(load_high_time_major_matrix(), dtype=np.float32)
rows, cols = high_tm_f32.shape
high_tm_f32 = high_tm_f32.ravel()
low_tm_f32 = np.asarray(load_low_time_major_matrix(), dtype=np.float32)
low_tm_f32 = low_tm_f32.ravel()
source_tm_f32 = np.asarray(load_series_time_major_matrix(), dtype=np.float32)
source_tm_f32 = source_tm_f32.ravel()

dev_tm = eri_cuda_many_series_one_param_dev(
    high_tm_f32=high_tm_f32,
    low_tm_f32=low_tm_f32,
    source_tm_f32=source_tm_f32,
    cols=cols,
    rows=rows,
    period=14,
    ma_type="ema",
    device_id=0,
)

JavaScript/WASM Bindings

Basic Usage

Compute ERI in JavaScript/TypeScript:

import { eri_js } from 'vectorta-wasm';

const high = new Float64Array([/* ... */]);
const low  = new Float64Array([/* ... */]);
const src  = new Float64Array([/* close prices */]);

// Returns a flat array [bull..., bear...] length = 2*len
const out = eri_js(high, low, src, 13, 'ema');
const len = src.length;
const bull = out.slice(0, len);
const bear = out.slice(len);
Memory-Efficient Operations
import { eri_alloc, eri_free, eri_into, memory } from 'vectorta-wasm';

const len = src.length;
const highPtr = eri_alloc(len);
const lowPtr  = eri_alloc(len);
const srcPtr  = eri_alloc(len);
const bullPtr = eri_alloc(len);
const bearPtr = eri_alloc(len);

new Float64Array(memory.buffer, highPtr, len).set(high);
new Float64Array(memory.buffer, lowPtr,  len).set(low);
new Float64Array(memory.buffer, srcPtr,  len).set(src);

// Args: high_ptr, low_ptr, source_ptr, bull_ptr, bear_ptr, len, period, ma_type
eri_into(highPtr, lowPtr, srcPtr, bullPtr, bearPtr, len, 13, 'ema');

const bull = new Float64Array(memory.buffer, bullPtr, len).slice();
const bear = new Float64Array(memory.buffer, bearPtr, len).slice();

eri_free(highPtr, len); eri_free(lowPtr, len); eri_free(srcPtr, len);
eri_free(bullPtr, len); eri_free(bearPtr, len);
Batch Processing

Run many period values and get bull and bear matrices (row‑major):

import { eri_batch } from 'vectorta-wasm';

const cfg = { period_range: [5, 30, 5], ma_type: 'ema' };
const res = eri_batch(high, low, src, cfg);
// res: { values, rows, cols, periods }

const rows = res.rows;            // combos * 2 (bull rows then bear rows)
const cols = res.cols;            // len
const combos = rows / 2;          // number of parameter combos

// Extract first bull row and first bear row
const flat = res.values;          // Float64Array or array
const bullRow0 = flat.slice(0 * cols, 1 * cols);
const bearRow0 = flat.slice(combos * cols, combos * cols + cols);

CUDA Bindings (Rust)

use vector_ta::cuda::CudaEri;
use vector_ta::indicators::eri::EriBatchRange;

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

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

let out = cuda.eri_batch_dev(&high, &low, &source, &sweep)?;
let _ = out;

Performance Analysis

Comparison:
View:
Loading chart...

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

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