Ehlers Adaptive Cyber Cycle
alpha = 0.07 (0–1) Overview
Ehlers Adaptive Cyber Cycle is a two-line cycle detector built for turning-point analysis rather than broad trend following. The input is first smoothed and then passed through an adaptive cyber-cycle style calculation, so the main cycle line emphasizes cyclical swings and oscillatory structure. The default candle source is `hl2`, which keeps the indicator centered on median price rather than on closes alone.
The second output is a trigger line derived from the cycle line itself. That gives the indicator the familiar paired-line structure often used for cycle-cross and turning-point confirmation. The alpha setting controls how aggressively the internal adaptive cycle responds. Lower values tend to stay smoother, while higher values make the cycle react more quickly, although the early warmup region is still intentionally NaN-prefixed in the standard series output.
Defaults: Ehlers Adaptive Cyber Cycle uses `alpha = 0.07` and defaults candle input to `hl2`.
Implementation Examples
Compute the adaptive cycle and trigger lines from a slice or from candle `hl2` data.
use vector_ta::indicators::ehlers_adaptive_cyber_cycle::{
ehlers_adaptive_cyber_cycle,
EhlersAdaptiveCyberCycleInput,
EhlersAdaptiveCyberCycleParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};
let output = ehlers_adaptive_cyber_cycle(&EhlersAdaptiveCyberCycleInput::from_slice(
&values,
EhlersAdaptiveCyberCycleParams { alpha: Some(0.07) },
))?;
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = ehlers_adaptive_cyber_cycle(
&EhlersAdaptiveCyberCycleInput::with_default_candles(&candles)
)?;
println!("cycle = {:?}", output.cycle.last());
println!("trigger = {:?}", candle_output.trigger.last()); API Reference
Input Methods ▼
// From candles and a named source field
EhlersAdaptiveCyberCycleInput::from_candles(
&Candles,
&str,
EhlersAdaptiveCyberCycleParams,
) -> EhlersAdaptiveCyberCycleInput
// From a raw slice
EhlersAdaptiveCyberCycleInput::from_slice(&[f64], EhlersAdaptiveCyberCycleParams)
-> EhlersAdaptiveCyberCycleInput
// From candles with default parameters
EhlersAdaptiveCyberCycleInput::with_default_candles(&Candles)
-> EhlersAdaptiveCyberCycleInput Parameters Structure ▼
pub struct EhlersAdaptiveCyberCycleParams {
pub alpha: Option<f64>, // default 0.07
} Output Structure ▼
pub struct EhlersAdaptiveCyberCycleOutput {
pub cycle: Vec<f64>,
pub trigger: Vec<f64>,
} Validation, Warmup & NaNs ▼
- The input slice must be non-empty and contain at least one finite value.
alphamust be finite and stay inside the closed0.0..=1.0range.- The implementation requires at least
3valid samples from the first finite value onward. - The standard series output is NaN-prefixed through
first_valid + 2forcycleandfirst_valid + 3fortrigger. - Streaming does not return
Option; it always returns a tuple, with warmup represented through NaN values instead. - Batch mode validates alpha ranges and rejects unsupported kernels.
Builder, Streaming & Batch APIs ▼
// Builder
EhlersAdaptiveCyberCycleBuilder::new()
.source(&'static str)
.alpha(f64)
.kernel(Kernel)
.apply_slice(&[f64])
EhlersAdaptiveCyberCycleBuilder::new()
.apply(&Candles)
EhlersAdaptiveCyberCycleBuilder::new()
.into_stream()
// Stream
EhlersAdaptiveCyberCycleStream::try_new(EhlersAdaptiveCyberCycleParams)
EhlersAdaptiveCyberCycleStream::update(f64) -> (f64, f64)
// Batch
EhlersAdaptiveCyberCycleBatchBuilder::new()
.source(&'static str)
.alpha_range((f64, f64, f64))
.kernel(Kernel)
.apply_slice(&[f64])
EhlersAdaptiveCyberCycleBatchBuilder::new()
.apply(&Candles) Error Handling ▼
pub enum EhlersAdaptiveCyberCycleError {
EmptyInputData,
AllValuesNaN,
InvalidAlpha { alpha: f64 },
NotEnoughValidData { needed: usize, valid: usize },
OutputLengthMismatch { expected: usize, got: usize },
InvalidRange { start: String, end: String, step: String },
InvalidKernelForBatch(Kernel),
} Python Bindings
Python exposes an object-returning single-run function, a streaming class, and a batch function. The single-run
binding returns a dict with cycle and trigger. Batch returns the cycle matrix, the
trigger matrix, the tested alpha values, and the rows and cols shape.
import numpy as np
from vector_ta import (
ehlers_adaptive_cyber_cycle,
ehlers_adaptive_cyber_cycle_batch,
EhlersAdaptiveCyberCycleStream,
)
data = np.asarray(values, dtype=np.float64)
single = ehlers_adaptive_cyber_cycle(
data,
alpha=0.07,
kernel="auto",
)
stream = EhlersAdaptiveCyberCycleStream(alpha=0.07)
print(stream.update(data[-1]))
batch = ehlers_adaptive_cyber_cycle_batch(
data,
alpha_range=(0.07, 0.21, 0.07),
kernel="auto",
)
print(batch["alphas"], batch["rows"], batch["cols"]) JavaScript/WASM Bindings
The WASM layer exposes an object-returning single-run wrapper, a batch wrapper, and pointer-oriented helpers.
The standard JavaScript helper returns named cycle and trigger arrays, while the
batch helper returns cycle, trigger, alphas, rows, and cols.
import init, {
ehlers_adaptive_cyber_cycle_js,
ehlers_adaptive_cyber_cycle_batch_js,
} from "/pkg/vector_ta.js";
await init();
const data = new Float64Array(values);
const single = ehlers_adaptive_cyber_cycle_js(data, 0.07);
console.log(single.cycle, single.trigger);
const batch = ehlers_adaptive_cyber_cycle_batch_js(data, {
alpha_range: [0.07, 0.21, 0.07],
});
console.log(batch.cycle, batch.trigger, batch.alphas, 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)
Related Indicators
Acceleration Oscillator
Technical analysis indicator
Accumulation/Distribution
Technical analysis indicator
Awesome Oscillator
Technical analysis indicator
Absolute Price Oscillator
Technical analysis indicator
Commodity Channel Index
Technical analysis indicator
CCI Cycle
Technical analysis indicator