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 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 > 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 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
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.