Laguerre RSI (LRSI)

Parameters: alpha = 0.2 (0.01–0.99)

Overview

The Laguerre RSI, developed by John F. Ehlers in his work on cybernetic analysis, applies recursive calculations through four Laguerre filter stages (L0 through L3) to the mid price, creating a momentum oscillator that ranges from 0 to 1 with exceptional noise reduction and minimal lag. Each Laguerre level depends on the previous level and current price input through an IIR filter structure controlled by the alpha parameter, with smaller alpha values producing smoother but laggier results. After filtering, the indicator calculates the ratio of upward movement to total movement across these stages, essentially measuring whether bullish or bearish pressure dominates the filtered price action. Traders typically watch for crosses above 0.15 to signal oversold bounces and crosses below 0.85 to indicate overbought reversals, while horizontal movement at 1 suggests strong uptrends and movement at 0 indicates strong downtrends. Unlike traditional RSI that can generate whipsaws during ranging markets, Laguerre RSI's polynomial filter design captures the essence of price movements while filtering out insignificant fluctuations, making it particularly effective for identifying genuine momentum shifts without false signals.

Implementation Examples

Compute LRSI from candle highs/lows or slices:

use vectorta::indicators::lrsi::{lrsi, LrsiInput, LrsiParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From candles (uses mid-price = (high+low)/2), default alpha=0.2
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = LrsiInput::with_default_candles(&candles);
let result = lrsi(&input)?;

// From high/low slices with explicit alpha
let high = vec![101.0, 102.0, 103.0, 103.5];
let low  = vec![ 99.0, 100.5, 101.5, 102.5];
let params = LrsiParams { alpha: Some(0.2) };
let input = LrsiInput::from_slices(&high, &low, params);
let result = lrsi(&input)?;

// Access values (NaN during warmup, then in [0,1])
for v in result.values {
    println!("LRSI: {}", v);
}

API Reference

Input Methods
// From candles (uses high/low to compute mid-price)
LrsiInput::from_candles(&Candles, LrsiParams) -> LrsiInput

// From separate high/low slices
LrsiInput::from_slices(&[f64], &[f64], LrsiParams) -> LrsiInput

// Default params with candles (alpha=0.2)
LrsiInput::with_default_candles(&Candles) -> LrsiInput
Parameters Structure
pub struct LrsiParams {
    pub alpha: Option<f64>, // Default: 0.2 (must satisfy 0<alpha<1)
}
Output Structure
pub struct LrsiOutput {
    pub values: Vec<f64>, // LRSI values (NaN during warmup, else in [0,1])
}
Validation, Warmup & NaNs
  • 0 < alpha < 1 or LrsiError::InvalidAlpha.
  • Requires at least 4 valid points after the first finite mid-price; otherwise LrsiError::NotEnoughValidData.
  • Before warmup (first_valid_idx + 3), outputs are NaN. After warmup, NaN inputs propagate.
  • Candles input uses high and low; slices must have equal length or LrsiError::EmptyData.
  • lrsi_into_slice enforces exact output length or LrsiError::OutputLengthMismatch.
Error Handling
use vectorta::indicators::lrsi::{lrsi, LrsiInput, LrsiParams, LrsiError};

let input = LrsiInput::from_slices(&high, &low, LrsiParams { alpha: Some(0.2) });
match lrsi(&input) {
    Ok(output) => process(output.values),
    Err(LrsiError::EmptyData) => eprintln!("Empty or mismatched input data"),
    Err(LrsiError::InvalidAlpha { alpha }) => eprintln!("Invalid alpha: {}", alpha),
    Err(LrsiError::AllValuesNaN) => eprintln!("All values are NaN"),
    Err(LrsiError::NotEnoughValidData { needed, valid }) => {
        eprintln!("Need {} valid points after first finite; got {}", needed, valid)
    }
    Err(LrsiError::OutputLengthMismatch { expected, provided }) => {
        eprintln!("Output len {} != expected {}", provided, expected)
    }
}

Python Bindings

Basic Usage

Calculate LRSI from NumPy arrays of high/low:

import numpy as np
from vectorta import lrsi

high = np.array([101.0, 102.0, 103.0, 103.5])
low  = np.array([ 99.0, 100.5, 101.5, 102.5])

# alpha in (0,1); kernel optional: 'auto', 'scalar', 'avx2', 'avx512', ...
values = lrsi(high, low, alpha=0.2, kernel='auto')
print(values)  # 1D numpy array, NaN during warmup then [0,1]
Streaming

Use LrsiStream for real-time updates:

from vectorta import LrsiStream

stream = LrsiStream(alpha=0.2)
for h, l in zip(high_series, low_series):
    v = stream.update(h, l)  # returns None during warmup
    if v is not None:
        handle(v)  # value in [0,1]
Batch Processing

Sweep multiple alpha values:

import numpy as np
from vectorta import lrsi_batch

high = np.array([...])
low  = np.array([...])

# (start, end, step)
result = lrsi_batch(high, low, alpha_range=(0.05, 0.5, 0.05), kernel='auto')

# Dict keys: 'values' (2D [rows, cols]) and 'alphas' (1D)
matrix = result['values']
alphas = result['alphas']
print(matrix.shape, alphas)
CUDA Acceleration

CUDA support for LRSI is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated LRSI calculations
# (Patterns will mirror other indicators: *_cuda_batch, *_cuda_many_series_one_param, etc.)

JavaScript/WASM Bindings

Basic Usage

Calculate LRSI in JavaScript/TypeScript:

import { lrsi_js } from 'vectorta-wasm';

// High/Low arrays as Float64Array or regular arrays
const high = new Float64Array([101.0, 102.0, 103.0, 103.5]);
const low  = new Float64Array([ 99.0, 100.5, 101.5, 102.5]);

// Calculate LRSI with alpha in (0,1)
const values = lrsi_js(high, low, 0.2);
console.log('LRSI:', values); // Float64Array
Memory-Efficient Operations

Use zero-copy operations with explicit memory management:

import { lrsi_alloc, lrsi_free, lrsi_into, memory } from 'vectorta-wasm';

const high = new Float64Array([/* your data */]);
const low  = new Float64Array([/* your data */]);
const n = high.length;

// Allocate WASM memory for inputs and output
const highPtr = lrsi_alloc(n);
const lowPtr  = lrsi_alloc(n);
const outPtr  = lrsi_alloc(n);

// Copy inputs
new Float64Array(memory.buffer, highPtr, n).set(high);
new Float64Array(memory.buffer, lowPtr,  n).set(low);

// Compute into pre-allocated buffer: (high_ptr, low_ptr, out_ptr, len, alpha)
lrsi_into(highPtr, lowPtr, outPtr, n, 0.2);

// Read back and free
const out = new Float64Array(memory.buffer, outPtr, n).slice();
lrsi_free(highPtr, n); lrsi_free(lowPtr, n); lrsi_free(outPtr, n);
console.log(out);
Batch Processing

Calculate many alpha values at once:

import { lrsi_batch } from 'vectorta-wasm';

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

// Config uses (start, end, step) for alpha
const config = { alpha_range: [0.05, 0.5, 0.05] };
const out = lrsi_batch(high, low, config);

// out: { values: Float64Array, combos: { alpha: number | null }[], rows: number, cols: number }
console.log(out.rows, out.cols, out.combos);

// Flattened values are [row0..., row1..., ...] where row corresponds to a combo
// To extract a row by alpha:
const idx = out.combos.findIndex(c => (c.alpha ?? 0.2) === 0.2);
const row = out.values.slice(idx * out.cols, (idx + 1) * out.cols);

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators