Cycle Channel Oscillator
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_lengthandmedium_cycle_lengthmust both be at least2.short_multiplierandmedium_multipliermust be finite and non-negative.- The practical warmup is driven by the medium period, which is
medium_cycle_length / 2. The output prefixes values asNaNthroughfirst_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
Across sizes, Rust CPU runs about 1.14× faster than Tulip C in this benchmark.
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU)