Ehlers Adaptive Cyber Cycle

Parameters: 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.
  • alpha must be finite and stay inside the closed 0.0..=1.0 range.
  • The implementation requires at least 3 valid samples from the first finite value onward.
  • The standard series output is NaN-prefixed through first_valid + 2 for cycle and first_valid + 3 for trigger.
  • 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

Comparison:
View:
Placeholder data (no recorded benchmarks for this indicator)

Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)

Related Indicators