Candle Strength Oscillator

Parameters: period = 50 | atr_enabled = | atr_length = 50 | mode = bollinger

Overview

Candle Strength Oscillator starts with candle anatomy rather than closing-price momentum alone. Each bar is scored by comparing the candle body to the full session range, assigning a positive score to bullish bodies and a negative score to bearish bodies. That signed value is then smoothed with a Hull-style moving-average chain so the final oscillator responds faster than a simple average while still filtering noisy bar-to-bar flips.

The smoothed strength series is paired with dynamic levels. In bollinger mode the indicator builds a moving mean and a two-standard-deviation envelope around the oscillator. In donchian mode it uses rolling highs, lows, and a midpoint channel instead. Long and short markers appear when the oscillator crosses the mid line after the levels are established. An optional ATR factor can also scale the signed candle score before smoothing, which makes the oscillator more sensitive to volatility expansion.

Defaults: Candle Strength Oscillator uses `period = 50`, `atr_enabled = false`, `atr_length = 50`, and `mode = "bollinger"`.

Implementation Examples

Compute the smoothed candle-strength regime and its level structure from OHLC slices or candle data.

use vector_ta::indicators::candle_strength_oscillator::{
    candle_strength_oscillator,
    CandleStrengthOscillatorInput,
    CandleStrengthOscillatorParams,
};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

let open = vec![100.0, 101.0, 102.1, 101.4, 103.0, 103.6];
let high = vec![101.4, 102.2, 103.0, 102.9, 104.4, 104.8];
let low = vec![99.5, 100.2, 101.0, 100.8, 102.2, 102.9];
let close = vec![100.9, 101.8, 101.5, 102.7, 103.8, 104.1];

let output = candle_strength_oscillator(&CandleStrengthOscillatorInput::from_slices(
    &open,
    &high,
    &low,
    &close,
    CandleStrengthOscillatorParams::default(),
))?;

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

println!("strength = {:?}", output.strength.last());
println!("mid = {:?}", output.mid.last());
println!("long signal = {:?}", candle_output.long_signal.last());

API Reference

Input Methods
// From candles
CandleStrengthOscillatorInput::from_candles(&Candles, CandleStrengthOscillatorParams)
    -> CandleStrengthOscillatorInput

// From OHLC slices
CandleStrengthOscillatorInput::from_slices(
    &[f64], &[f64], &[f64], &[f64], CandleStrengthOscillatorParams
) -> CandleStrengthOscillatorInput

// From candles with default parameters
CandleStrengthOscillatorInput::with_default_candles(&Candles)
    -> CandleStrengthOscillatorInput
Parameters Structure
pub struct CandleStrengthOscillatorParams {
    pub period: Option<usize>,      // default 50
    pub atr_enabled: Option<bool>,  // default false
    pub atr_length: Option<usize>,  // default 50
    pub mode: Option<String>,       // default "bollinger"
}
Output Structure
pub struct CandleStrengthOscillatorOutput {
    pub strength: Vec<f64>,
    pub highs: Vec<f64>,
    pub lows: Vec<f64>,
    pub mid: Vec<f64>,
    pub long_signal: Vec<f64>,
    pub short_signal: Vec<f64>,
}
Validation, Warmup & NaNs
  • Open, high, low, and close must all be non-empty slices with identical lengths.
  • A bar is considered valid only when all four OHLC values are finite and the bar range is non-zero.
  • period and atr_length must both be greater than zero.
  • mode must be either bollinger or donchian, with the short aliases bb and dc also accepted.
  • The indicator needs enough history to finish both the Hull-style strength warmup and the level-building warmup; otherwise it returns NotEnoughValidData.
  • Strength warmup and levels warmup can be queried directly from the stream via get_strength_warmup_period() and get_levels_warmup_period().
  • If streaming encounters an invalid bar it resets its ATR, smoothing, and level state.
  • Batch mode rejects invalid integer ranges and non-batch kernels.
Builder, Streaming & Batch APIs
// Builder
CandleStrengthOscillatorBuilder::new()
    .period(usize)
    .atr_enabled(bool)
    .atr_length(usize)
    .mode(&'static str)
    .kernel(Kernel)
    .apply_slices(&[f64], &[f64], &[f64], &[f64])

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

CandleStrengthOscillatorBuilder::new()
    .into_stream()

// Stream
CandleStrengthOscillatorStream::try_new(CandleStrengthOscillatorParams)
CandleStrengthOscillatorStream::update(open, high, low, close)
    -> Option<CandleStrengthOscillatorPoint>
CandleStrengthOscillatorStream::reset()
CandleStrengthOscillatorStream::get_strength_warmup_period() -> usize
CandleStrengthOscillatorStream::get_levels_warmup_period() -> usize

// Batch
CandleStrengthOscillatorBatchBuilder::new()
    .period_range((start, end, step))
    .atr_enabled(bool)
    .atr_length_range((start, end, step))
    .mode(&'static str)
    .kernel(Kernel)
    .apply_slices(&[f64], &[f64], &[f64], &[f64])
Error Handling
pub enum CandleStrengthOscillatorError {
    EmptyInputData,
    InputLengthMismatch { open_len: usize, high_len: usize, low_len: usize, close_len: usize },
    AllValuesNaN,
    InvalidPeriod { period: usize },
    InvalidAtrLength { atr_length: usize },
    InvalidMode { mode: String },
    NotEnoughValidData { needed: usize, valid: usize },
    OutputLengthMismatch { expected: usize, got: usize },
    InvalidRange { field: &'static str, start: usize, end: usize, step: usize },
    InvalidKernelForBatch(Kernel),
    MismatchedOutputLen { dst_len: usize, expected_len: usize },
    InvalidInput { msg: String },
}

Python Bindings

Python exposes a tuple-returning single-run function, a streaming class, and a batch function. The single-run binding returns six NumPy arrays in order: strength, highs, lows, mid, long_signal, and short_signal. Batch returns those same channels as matrices, plus the tested periods, ATR flags, ATR lengths, modes, and the final rows and cols shape.

import numpy as np
from vector_ta import (
    candle_strength_oscillator,
    candle_strength_oscillator_batch,
    CandleStrengthOscillatorStream,
)

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

strength, highs, lows, mid, long_signal, short_signal = candle_strength_oscillator(
    open_,
    high,
    low,
    close,
    period=50,
    atr_enabled=False,
    atr_length=50,
    mode="bollinger",
    kernel="auto",
)

stream = CandleStrengthOscillatorStream(
    period=34,
    atr_enabled=True,
    atr_length=21,
    mode="donchian",
)

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

batch = candle_strength_oscillator_batch(
    open_,
    high,
    low,
    close,
    period_range=(34, 50, 8),
    atr_enabled=True,
    atr_length_range=(14, 28, 7),
    mode="bollinger",
    kernel="auto",
)

print(batch["periods"], batch["atr_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 wrapper returns an object with six arrays: strength, highs, lows, mid, long_signal, and short_signal. The batch wrapper returns those arrays, the tested combos, and the rows and cols shape.

import init, {
  candle_strength_oscillator_js,
  candle_strength_oscillator_batch_js,
} from "/pkg/vector_ta.js";

await init();

const open = new Float64Array(openValues);
const high = new Float64Array(highValues);
const low = new Float64Array(lowValues);
const close = new Float64Array(closeValues);

const result = candle_strength_oscillator_js(
  open,
  high,
  low,
  close,
  50,
  false,
  50,
  "bollinger",
);

console.log(result.strength, result.long_signal);

const batch = candle_strength_oscillator_batch_js(open, high, low, close, {
  period_range: [34, 50, 8],
  atr_enabled: true,
  atr_length_range: [14, 28, 7],
  mode: "donchian",
});

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