Grover-Llorens Cycle Oscillator
length = 100 | mult = 10 | source = close | smooth = true | rsi_period = 20 Overview
Grover-Llorens Cycle Oscillator tracks a chosen price source against a dynamic trailing reference that reacts to breakout and reversal conditions. The indicator watches rolling source highs and lows, uses ATR to size the repositioning distance, and advances the trailing reference between events in the direction of the current move. The resulting source-minus-trailing spread acts as the raw oscillator drive.
That raw spread can be optionally EMA-smoothed and is then transformed through RSI, producing the final oscillator output. Because of that structure, the indicator blends breakout state, volatility normalization, and momentum normalization into a single cycle-oriented reading. Source selection is flexible, so it can be driven from close, midpoint-style composites, or OHLC blends depending on the strategy.
Defaults: Grover-Llorens Cycle Oscillator uses `length = 100`, `mult = 10.0`, `source = "close"`, `smooth = true`, and `rsi_period = 20`.
Implementation Examples
Compute the oscillator from OHLC slices or candle data.
use vector_ta::indicators::grover_llorens_cycle_oscillator::{
grover_llorens_cycle_oscillator,
GroverLlorensCycleOscillatorInput,
GroverLlorensCycleOscillatorParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
let open = vec![100.0, 100.9, 101.2, 101.6, 102.4, 102.1];
let high = vec![101.0, 101.8, 102.1, 102.7, 103.2, 102.9];
let low = vec![99.5, 100.2, 100.8, 101.1, 101.8, 101.4];
let close = vec![100.7, 101.5, 101.9, 102.4, 102.7, 102.0];
let output = grover_llorens_cycle_oscillator(
&GroverLlorensCycleOscillatorInput::from_slices(
&open,
&high,
&low,
&close,
GroverLlorensCycleOscillatorParams {
length: Some(100),
mult: Some(10.0),
source: Some("close".to_string()),
smooth: Some(true),
rsi_period: Some(20),
},
),
)?;
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = grover_llorens_cycle_oscillator(
&GroverLlorensCycleOscillatorInput::with_default_candles(&candles),
)?;
println!("latest glco = {:?}", output.values.last());
println!("series length = {}", candle_output.values.len()); API Reference
Input Methods ▼
// From candles
GroverLlorensCycleOscillatorInput::from_candles(&Candles, GroverLlorensCycleOscillatorParams)
-> GroverLlorensCycleOscillatorInput
// From OHLC slices
GroverLlorensCycleOscillatorInput::from_slices(&[f64], &[f64], &[f64], &[f64], GroverLlorensCycleOscillatorParams)
-> GroverLlorensCycleOscillatorInput
// From candles with default parameters
GroverLlorensCycleOscillatorInput::with_default_candles(&Candles)
-> GroverLlorensCycleOscillatorInput Parameters Structure ▼
pub struct GroverLlorensCycleOscillatorParams {
pub length: Option<usize>, // default 100
pub mult: Option<f64>, // default 10.0
pub source: Option<String>, // default "close"
pub smooth: Option<bool>, // default true
pub rsi_period: Option<usize>,// default 20
} Output Structure ▼
pub struct GroverLlorensCycleOscillatorOutput {
pub values: Vec<f64>,
} Validation, Warmup & NaNs ▼
- All OHLC inputs must be non-empty and have matching lengths.
lengthmust be positive and valid for the supplied data size.multmust be finite.sourcemust resolve to one of:open,high,low,close,hl2,hlc3,ohlc4, orhlcc4.rsi_periodmust be greater than0.- The indicator needs at least
max(length, rsi_period)valid bars. - Streaming resets on invalid source bars and reports a warmup of
max(length, rsi_period) - 1. - Batch mode validates all axes and rejects unsupported batch kernels through
InvalidKernelForBatch.
Builder, Streaming & Batch APIs ▼
// Builder
GroverLlorensCycleOscillatorBuilder::new()
.length(usize)
.mult(f64)
.source(&str)
.smooth(bool)
.rsi_period(usize)
.kernel(Kernel)
.apply(&Candles)
GroverLlorensCycleOscillatorBuilder::new()
.apply_slices(&[f64], &[f64], &[f64], &[f64])
GroverLlorensCycleOscillatorBuilder::new()
.into_stream()
// Stream
GroverLlorensCycleOscillatorStream::try_new(GroverLlorensCycleOscillatorParams)
GroverLlorensCycleOscillatorStream::update(f64, f64, f64, f64) -> Option<f64>
GroverLlorensCycleOscillatorStream::reset()
GroverLlorensCycleOscillatorStream::get_warmup_period() -> usize
// Batch
GroverLlorensCycleOscillatorBatchBuilder::new()
.length_range((usize, usize, usize))
.mult_range((f64, f64, f64))
.source(&str)
.smooth(bool)
.rsi_period_range((usize, usize, usize))
.kernel(Kernel)
.apply_slices(&[f64], &[f64], &[f64], &[f64]) Error Handling ▼
pub enum GroverLlorensCycleOscillatorError {
EmptyInputData,
AllValuesNaN,
InconsistentSliceLengths { open_len: usize, high_len: usize, low_len: usize, close_len: usize },
InvalidLength { length: usize, data_len: usize },
InvalidMult { mult: f64 },
InvalidSource { source_name: String },
InvalidRsiPeriod { rsi_period: usize },
NotEnoughValidData { needed: usize, valid: usize },
OutputLengthMismatch { expected: usize, got: usize },
InvalidRange { start: String, end: String, step: String },
InvalidKernelForBatch(Kernel),
} Python Bindings
Python exposes a scalar OHLC function, a streaming class, and a batch sweep. The scalar path returns one NumPy oscillator array. Streaming returns one floating-point value or `None`, while batch returns the flattened matrix plus the tested lengths, ATR multipliers, sources, smoothing flags, RSI periods, and output dimensions.
import numpy as np
from vector_ta import (
grover_llorens_cycle_oscillator,
grover_llorens_cycle_oscillator_batch,
GroverLlorensCycleOscillatorStream,
)
open_ = np.asarray(open_values, dtype=np.float64)
high = np.asarray(high_values, dtype=np.float64)
low = np.asarray(low_values, dtype=np.float64)
close = np.asarray(close_values, dtype=np.float64)
values = grover_llorens_cycle_oscillator(
open_,
high,
low,
close,
length=100,
mult=10.0,
source="close",
smooth=True,
rsi_period=20,
kernel="auto",
)
stream = GroverLlorensCycleOscillatorStream(
length=80,
mult=8.0,
source="hlc3",
smooth=True,
rsi_period=14,
)
print(stream.update(open_[-1], high[-1], low[-1], close[-1]))
batch = grover_llorens_cycle_oscillator_batch(
open_,
high,
low,
close,
length_range=(60, 100, 20),
mult_range=(6.0, 10.0, 2.0),
source="close",
smooth=True,
rsi_period_range=(10, 20, 10),
kernel="auto",
)
print(batch["values"].shape)
print(batch["mults"])
print(batch["rsi_periods"]) JavaScript/WASM Bindings
The WASM layer exposes a scalar function, a batch function, and raw allocation and into-buffer helpers. The scalar binding returns an object with a single `values` array. The batch binding returns the flattened values matrix plus the tested lengths, multipliers, sources, smoothing flags, RSI periods, and matrix dimensions.
import init, {
grover_llorens_cycle_oscillator_js,
grover_llorens_cycle_oscillator_batch_js,
} from "@vectoralpha/vector_ta";
await init();
const single = grover_llorens_cycle_oscillator_js(
open,
high,
low,
close,
100,
10.0,
"close",
true,
20,
);
console.log(single.values);
const batch = grover_llorens_cycle_oscillator_batch_js(open, high, low, close, {
length_range: [60, 100, 20],
mult_range: [6.0, 10.0, 2.0],
source: "close",
smooth: true,
rsi_period_range: [10, 20, 10],
});
console.log(batch.values);
console.log(batch.mults);
console.log(batch.rows, batch.cols); CUDA Bindings (Rust)
Additional details for the CUDA bindings can be found inside the VectorTA repository.
Performance Analysis
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)