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 vectorta::indicators::eri::{eri, EriInput, EriParams};
use vectorta::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 vectorta::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 vectorta 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 vectorta 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 vectorta 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-backed Python bindings for ERI are planned.

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

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