Elder Ray Index (ERI)
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 > 0andperiod ≤ data_len; otherwiseEriError::InvalidPeriod { period, data_len }.- First valid index is the first
iwherehigh[i], low[i], source[i]are all finite; must have at leastperiodvalid points after it orEriError::NotEnoughValidData { needed, valid }. - Warmup: indices
[0 .. first_valid + period − 1]areNaNin both outputs. - All inputs
NaN→EriError::AllValuesNaN. Empty inputs →EriError::EmptyData. - Unsupported or failing MA selection yields
EriError::MaCalculationError(String). - Streaming:
EriStream::updatereturnsNoneuntil the window is filled, thenSome((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
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.