Ehlers Simple Cycle Indicator

Parameters: alpha = 0.07 (0–1)

Overview

Ehlers Simple Cycle Indicator is a lighter cycle detector built around a smoothed source series and a recursive cycle estimate. Like other Ehlers-style cycle studies, it focuses on oscillatory turning structure rather than on raw trend strength. The main output is the cycle line itself, while the second output is a trigger line created by lagging the cycle one step.

The implementation uses a simplified startup path on the early valid bars before it transitions into the full recursive cycle formula. That makes the warmup shorter than some heavier adaptive cycle models while still giving the indicator a distinct paired-line structure for cycle/trigger crosses. Candle mode defaults to `hl2`, but the builder can point the calculation at other candle sources when needed.

Defaults: Ehlers Simple Cycle Indicator uses `alpha = 0.07` and defaults candle input to `hl2`.

Implementation Examples

Compute the simple cycle line and trigger from a slice or from candle `hl2` data.

use vector_ta::indicators::ehlers_simple_cycle_indicator::{
    ehlers_simple_cycle_indicator,
    EhlersSimpleCycleIndicatorInput,
    EhlersSimpleCycleIndicatorParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let output = ehlers_simple_cycle_indicator(&EhlersSimpleCycleIndicatorInput::from_slice(
    &values,
    EhlersSimpleCycleIndicatorParams { alpha: Some(0.07) },
))?;

let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = ehlers_simple_cycle_indicator(
    &EhlersSimpleCycleIndicatorInput::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
EhlersSimpleCycleIndicatorInput::from_candles(
    &Candles,
    &str,
    EhlersSimpleCycleIndicatorParams,
) -> EhlersSimpleCycleIndicatorInput

// From a raw slice
EhlersSimpleCycleIndicatorInput::from_slice(&[f64], EhlersSimpleCycleIndicatorParams)
    -> EhlersSimpleCycleIndicatorInput

// From candles with default parameters
EhlersSimpleCycleIndicatorInput::with_default_candles(&Candles)
    -> EhlersSimpleCycleIndicatorInput
Parameters Structure
pub struct EhlersSimpleCycleIndicatorParams {
    pub alpha: Option<f64>, // default 0.07
}
Output Structure
pub struct EhlersSimpleCycleIndicatorOutput {
    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 output is NaN-prefixed through first_valid + 2 for cycle and first_valid + 3 for trigger.
  • Streaming returns a tuple on every call, but early warmup values can still be NaN.
  • The earliest valid bars use a fallback cycle formula before the full recursive cycle expression takes over.
Builder, Streaming & Batch APIs
// Builder
EhlersSimpleCycleIndicatorBuilder::new()
    .source(&'static str)
    .alpha(f64)
    .kernel(Kernel)
    .apply_slice(&[f64])

EhlersSimpleCycleIndicatorBuilder::new()
    .apply(&Candles)

EhlersSimpleCycleIndicatorBuilder::new()
    .into_stream()

// Stream
EhlersSimpleCycleIndicatorStream::try_new(EhlersSimpleCycleIndicatorParams)
EhlersSimpleCycleIndicatorStream::update(f64) -> (f64, f64)

// Batch
EhlersSimpleCycleIndicatorBatchBuilder::new()
    .source(&'static str)
    .alpha_range((f64, f64, f64))
    .kernel(Kernel)
    .apply_slice(&[f64])

EhlersSimpleCycleIndicatorBatchBuilder::new()
    .apply(&Candles)
Error Handling
pub enum EhlersSimpleCycleIndicatorError {
    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 both matrices plus the tested alpha values and the rows and cols shape.

import numpy as np
from vector_ta import (
    ehlers_simple_cycle_indicator,
    ehlers_simple_cycle_indicator_batch,
    EhlersSimpleCycleIndicatorStream,
)

data = np.asarray(values, dtype=np.float64)

single = ehlers_simple_cycle_indicator(
    data,
    alpha=0.07,
    kernel="auto",
)

stream = EhlersSimpleCycleIndicatorStream(alpha=0.07)
print(stream.update(data[-1]))

batch = ehlers_simple_cycle_indicator_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 single-run helper returns named cycle and trigger arrays, while the batch helper adds alpha metadata, rows, and cols.

import init, {
  ehlers_simple_cycle_indicator_js,
  ehlers_simple_cycle_indicator_batch_js,
} from "/pkg/vector_ta.js";

await init();

const data = new Float64Array(values);

const single = ehlers_simple_cycle_indicator_js(data, 0.07);
console.log(single.cycle, single.trigger);

const batch = ehlers_simple_cycle_indicator_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