Cycle Channel Oscillator

Parameters: short_cycle_length = 10 | medium_cycle_length = 30 | short_multiplier = 1 | medium_multiplier = 3

Overview

Cycle Channel Oscillator produces a paired fast and slow channel readout instead of a single oscillator line. It starts from a chosen source series and builds short and medium rolling averages whose effective periods are half of the configured cycle lengths. A medium-period ATR path is then used as the scale reference for both channels, which keeps the final values tied to recent market range rather than to raw price distance alone.

Each channel also uses a delay derived from half of its own averaging period. That delayed anchor, combined with the ATR-scaled multiplier, creates an upper or lower channel-style displacement around the smoothed source. The fast line reacts to shorter cycle structure, while the slow line reflects the broader rhythm implied by the medium cycle configuration. Because the slow path depends on the medium period, that setting defines the practical warmup requirement for the whole indicator.

Defaults: Cycle Channel Oscillator uses `short_cycle_length = 10`, `medium_cycle_length = 30`, `short_multiplier = 1.0`, and `medium_multiplier = 3.0`.

Implementation Examples

Compute the fast and slow channel lines from a source slice or from a candle source field.

use vector_ta::indicators::cycle_channel_oscillator::{
    cycle_channel_oscillator,
    CycleChannelOscillatorInput,
    CycleChannelOscillatorParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let output = cycle_channel_oscillator(&CycleChannelOscillatorInput::from_slices(
    &source,
    &high,
    &low,
    &close,
    CycleChannelOscillatorParams::default(),
))?;

let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let candle_output = cycle_channel_oscillator(
    &CycleChannelOscillatorInput::with_default_candles(&candles)
)?;

println!("fast = {:?}", output.fast.last());
println!("slow = {:?}", candle_output.slow.last());

API Reference

Input Methods
// From candles and a named source field
CycleChannelOscillatorInput::from_candles(
    &Candles,
    &str,
    CycleChannelOscillatorParams,
) -> CycleChannelOscillatorInput

// From source/high/low/close slices
CycleChannelOscillatorInput::from_slices(
    &[f64], &[f64], &[f64], &[f64], CycleChannelOscillatorParams
) -> CycleChannelOscillatorInput

// From candles with default parameters
CycleChannelOscillatorInput::with_default_candles(&Candles)
    -> CycleChannelOscillatorInput
Parameters Structure
pub struct CycleChannelOscillatorParams {
    pub short_cycle_length: Option<usize>, // default 10
    pub medium_cycle_length: Option<usize>, // default 30
    pub short_multiplier: Option<f64>, // default 1.0
    pub medium_multiplier: Option<f64>, // default 3.0
}
Output Structure
pub struct CycleChannelOscillatorOutput {
    pub fast: Vec<f64>,
    pub slow: Vec<f64>,
}
Validation, Warmup & NaNs
  • The source, high, low, and close inputs must all be non-empty and have identical lengths.
  • The indicator requires at least one index where all four inputs are finite, otherwise it returns AllValuesNaN.
  • short_cycle_length and medium_cycle_length must both be at least 2.
  • short_multiplier and medium_multiplier must be finite and non-negative.
  • The practical warmup is driven by the medium period, which is medium_cycle_length / 2. The output prefixes values as NaN through first_valid_index + medium_period - 1.
  • If the contiguous valid run after the first valid bar is shorter than the medium period, the indicator returns NotEnoughValidData.
  • Batch range expansion rejects invalid integer and floating-point ranges, and non-batch kernels are rejected for the batch entry point.
Builder, Streaming & Batch APIs
// Builder
CycleChannelOscillatorBuilder::new()
    .source(&'static str)
    .short_cycle_length(usize)
    .medium_cycle_length(usize)
    .short_multiplier(f64)
    .medium_multiplier(f64)
    .kernel(Kernel)
    .apply_slices(&[f64], &[f64], &[f64], &[f64])

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

CycleChannelOscillatorBuilder::new()
    .into_stream()

// Stream
CycleChannelOscillatorStream::try_new(CycleChannelOscillatorParams)
CycleChannelOscillatorStream::update(source, high, low, close) -> (f64, f64)

// Batch
CycleChannelOscillatorBatchBuilder::new()
    .source(&'static str)
    .short_cycle_length_range((start, end, step))
    .medium_cycle_length_range((start, end, step))
    .short_multiplier_range((start, end, step))
    .medium_multiplier_range((start, end, step))
    .kernel(Kernel)
    .apply_slices(&[f64], &[f64], &[f64], &[f64])
Error Handling
pub enum CycleChannelOscillatorError {
    EmptyInputData,
    AllValuesNaN,
    InconsistentSliceLengths { source_len: usize, high_len: usize, low_len: usize, close_len: usize },
    InvalidCycleLength { name: &'static str, value: usize },
    InvalidMultiplier { name: &'static str, value: f64 },
    NotEnoughValidData { needed: usize, valid: usize },
    OutputLengthMismatch { expected: usize, got: usize },
    InvalidRange { start: String, end: String, step: String },
    InvalidKernelForBatch(Kernel),
}

Python Bindings

Python exposes a dictionary-returning single-run function, a streaming class, and a batch function. The single-run binding returns NumPy arrays for fast and slow. Batch returns those same matrices plus the tested cycle lengths, multipliers, and the final rows and cols shape.

import numpy as np
from vector_ta import (
    cycle_channel_oscillator,
    cycle_channel_oscillator_batch,
    CycleChannelOscillatorStream,
)

source = np.asarray(source_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)

result = cycle_channel_oscillator(
    source,
    high,
    low,
    close,
    short_cycle_length=10,
    medium_cycle_length=30,
    short_multiplier=1.0,
    medium_multiplier=3.0,
    kernel="auto",
)

print(result["fast"], result["slow"])

stream = CycleChannelOscillatorStream(
    short_cycle_length=10,
    medium_cycle_length=30,
    short_multiplier=1.0,
    medium_multiplier=3.0,
)

print(stream.update(source[-1], high[-1], low[-1], close[-1]))

batch = cycle_channel_oscillator_batch(
    source,
    high,
    low,
    close,
    short_cycle_length_range=(10, 14, 2),
    medium_cycle_length_range=(30, 40, 10),
    short_multiplier_range=(1.0, 1.5, 0.5),
    medium_multiplier_range=(2.5, 3.5, 1.0),
    kernel="auto",
)

print(batch["short_cycle_lengths"], batch["rows"], batch["cols"])

JavaScript/WASM Bindings

The WASM layer exposes object-returning single-run and batch wrappers plus lower-level allocation and in-place exports. The standard JavaScript path returns an object with fast and slow arrays. The batch wrapper adds the tested cycle lengths, multiplier axes, and the final rows and cols shape.

import init, {
  cycle_channel_oscillator_js,
  cycle_channel_oscillator_batch_js,
} from "/pkg/vector_ta.js";

await init();

const source = new Float64Array(sourceValues);
const high = new Float64Array(highValues);
const low = new Float64Array(lowValues);
const close = new Float64Array(closeValues);

const result = cycle_channel_oscillator_js(
  source,
  high,
  low,
  close,
  10,
  30,
  1.0,
  3.0,
);

console.log(result.fast, result.slow);

const batch = cycle_channel_oscillator_batch_js(source, high, low, close, {
  short_cycle_length_range: [10, 14, 2],
  medium_cycle_length_range: [30, 40, 10],
  short_multiplier_range: [1.0, 1.5, 0.5],
  medium_multiplier_range: [2.5, 3.5, 1.0],
});

console.log(batch.short_cycle_lengths, batch.medium_multipliers, 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