Stochastic Oscillator (Stoch)

Parameters: fastk_period = 14 | slowk_period = 3 | slowk_ma_type = sma | slowd_period = 3 | slowd_ma_type = sma

Overview

The Stochastic Oscillator evaluates momentum by positioning the current close within the recent high-low range. The raw %K calculation finds the highest high and lowest low over the fast period, then determines where the current close sits within that range on a 0-100 scale. This raw %K value undergoes smoothing to produce the slow %K line, which then receives additional smoothing to generate the %D signal line. The resulting dual-line oscillator identifies overbought conditions above 80 and oversold conditions below 20. Traders watch for %K and %D line crossovers to signal momentum shifts, with bullish crosses above 20 suggesting buying opportunities and bearish crosses below 80 indicating selling pressure. VectorTA provides flexible moving average type selection for both smoothing stages, defaulting to SMA(3) for both. The configurable smoothing allows adaptation from highly sensitive fast stochastics to smoother slow stochastics depending on trading timeframe and volatility environment.

Implementation Examples

Compute %K and %D from slices or candles:

use vector_ta::indicators::stoch::{stoch, StochInput, StochParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

// From high/low/close slices
let high = vec![/* ... */];
let low = vec![/* ... */];
let close = vec![/* ... */];
let params = StochParams { fastk_period: Some(14), slowk_period: Some(3), slowk_ma_type: Some("sma".into()), slowd_period: Some(3), slowd_ma_type: Some("sma".into()) };
let input = StochInput::from_slices(&high, &low, &close, params);
let out = stoch(&input)?; // out.k, out.d

// From Candles with defaults (fastk=14, slowk=3 SMA, slowd=3 SMA)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = StochInput::with_default_candles(&candles);
let out = stoch(&input)?;

API Reference

Input Methods
// From candles (uses high/low/close fields)
StochInput::from_candles(&Candles, StochParams) -> StochInput

// From high/low/close slices
StochInput::from_slices(&[f64], &[f64], &[f64], StochParams) -> StochInput

// Defaults: fastk=14, slowk=3 (SMA), slowd=3 (SMA)
StochInput::with_default_candles(&Candles) -> StochInput
Parameters Structure
pub struct StochParams {
    pub fastk_period: Option<usize>,   // Default: 14
    pub slowk_period: Option<usize>,   // Default: 3
    pub slowk_ma_type: Option<String>, // Default: "sma"
    pub slowd_period: Option<usize>,   // Default: 3
    pub slowd_ma_type: Option<String>, // Default: "sma"
}
Output Structure
pub struct StochOutput {
    pub k: Vec<f64>, // %K (smoothed by slowk)
    pub d: Vec<f64>, // %D (MA of K)
}
Validation, Warmup & NaNs
  • fastk_period > 0, slowk_period > 0, slowd_period > 0; otherwise StochError::InvalidPeriod.
  • All input series must have equal length; else StochError::MismatchedLength. Empty inputs => EmptyData.
  • First finite H/L/C index = first. Need at least fastk_period valid values after first or NotEnoughValidData.
  • Warmup indices are NaN by design: raw %K starts at first + fastk_period - 1, K at + slowk_period - 1, D at + slowd_period - 1.
  • If HH − LL ≈ 0, %K is set to 50.0 for that bar.
Error Handling
use vector_ta::indicators::stoch::{stoch, StochError};

match stoch(&input) {
    Ok(out) => consume(out.k, out.d),
    Err(StochError::EmptyData) => eprintln!("no data"),
    Err(StochError::MismatchedLength) => eprintln!("mismatched series lengths"),
    Err(StochError::AllValuesNaN) => eprintln!("all values are NaN"),
    Err(StochError::InvalidPeriod { period, data_len }) => eprintln!("invalid period {} for len {}", period, data_len),
    Err(StochError::NotEnoughValidData { needed, valid }) => eprintln!("need {} valid points after first finite, found {}", needed, valid),
    Err(StochError::Other(e)) => eprintln!("error: {}", e),
}

Python Bindings

Basic Usage

Compute %K and %D from NumPy arrays:

import numpy as np
from vector_ta import stoch

high = np.array([...], dtype=float)
low = np.array([...], dtype=float)
close = np.array([...], dtype=float)

k, d = stoch(
    high, low, close,
    fastk_period=14,
    slowk_period=3,
    slowk_ma_type='sma',
    slowd_period=3,
    slowd_ma_type='sma',
    kernel='auto'
)
print(k[-3:], d[-3:])
Streaming Real-time Updates
from vector_ta import StochStream

stream = StochStream(14, 3, 'sma', 3, 'sma')
for (h, l, c) in hlc_feed:
    upd = stream.update(h, l, c)
    if upd is not None:
        k, d = upd
        handle(k, d)
Batch Parameter Sweep
import numpy as np
from vector_ta import stoch_batch

hi = np.array([...], dtype=float)
lo = np.array([...], dtype=float)
cl = np.array([...], dtype=float)

res = stoch_batch(
    hi, lo, cl,
    fastk_range=(10, 20, 5),
    slowk_range=(3, 5, 1),
    slowk_ma_type='sma',
    slowd_range=(3, 5, 1),
    slowd_ma_type='sma',
    kernel='auto'
)

k = res['k']  # shape: (num_combos, len(cl))
d = res['d']
fk = res['fastk_periods']; sk = res['slowk_periods']
kt = res['slowk_types'];   sd = res['slowd_periods']; dt = res['slowd_types']

JavaScript/WASM Bindings

Basic Usage

Compute %K and %D in JavaScript/TypeScript:

import { stoch } from 'vectorta-wasm';

const high = new Float64Array([/* ... */]);
const low = new Float64Array([/* ... */]);
const close = new Float64Array([/* ... */]);

// Args: high, low, close, fastk, slowk, slowk_ma_type, slowd, slowd_ma_type
const out = stoch(high, low, close, 14, 3, 'sma', 3, 'sma');

// out: { values: Float64Array, rows: 2, cols: N }
const cols = out.cols;
const values = out.values; // [K row..., D row...]
const k = values.slice(0, cols);
const d = values.slice(cols, 2 * cols);
Memory-Efficient Operations

Use zero‑copy operations with explicit alloc/free:

import { stoch_alloc, stoch_free, stoch_into, memory } from 'vectorta-wasm';

const len = close.length;
const hiPtr = stoch_alloc(len);
const loPtr = stoch_alloc(len);
const clPtr = stoch_alloc(len);
const kPtr  = stoch_alloc(len);
const dPtr  = stoch_alloc(len);

new Float64Array(memory.buffer, hiPtr, len).set(high);
new Float64Array(memory.buffer, loPtr, len).set(low);
new Float64Array(memory.buffer, clPtr, len).set(close);

// Args: hi_ptr, lo_ptr, cl_ptr, len, fastk, slowk, slowk_ma, slowd, slowd_ma, out_k_ptr, out_d_ptr
stoch_into(hiPtr, loPtr, clPtr, len, 14, 3, 'sma', 3, 'sma', kPtr, dPtr);

const k = new Float64Array(memory.buffer, kPtr, len).slice();
const d = new Float64Array(memory.buffer, dPtr, len).slice();

stoch_free(hiPtr, len); stoch_free(loPtr, len); stoch_free(clPtr, len);
stoch_free(kPtr,  len); stoch_free(dPtr,  len);
Batch Processing

Test multiple parameter combinations efficiently:

import { stoch_batch } from 'vectorta-wasm';

const hi = new Float64Array([/* ... */]);
const lo = new Float64Array([/* ... */]);
const cl = new Float64Array([/* ... */]);

// Args: hi, lo, cl, fastk_start, fastk_end, fastk_step, slowk_start, slowk_end, slowk_step, slowk_type, slowd_start, slowd_end, slowd_step, slowd_type
const res = stoch_batch(hi, lo, cl, 10, 20, 5, 3, 5, 1, 'sma', 3, 5, 1, 'sma');

// res: { values, combos, rows_per_combo: 2, cols }
const cols = res.cols;
const combos = res.combos; // array of StochParams objects
const values = res.values; // flattened [all K rows..., then all D rows...]

// Example: extract the first combo's K and D
const k0 = values.slice(0, cols);
const d0 = values.slice(values.length/2, values.length/2 + cols);

CUDA Acceleration

CUDA helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible).

import numpy as np
  from vector_ta import stoch_cuda_batch_dev, stoch_cuda_many_series_one_param_dev

  # One series (float32)
  high_f32 = np.asarray(load_high(), dtype=np.float32)
  low_f32 = np.asarray(load_low(), dtype=np.float32)
  close_f32 = np.asarray(load_close(), dtype=np.float32)

  dev = stoch_cuda_batch_dev(
      high_f32=high_f32,
      low_f32=low_f32,
      close_f32=close_f32,
      fastk_period=(5, 30, 5),
      slowk_period=(5, 30, 5),
      slowd_period=(5, 30, 5),
      slowk_ma_type=14,
      slowd_ma_type=14,
      device_id=0,
  )

  # Many series (time-major)
  high_tm_f32 = np.asarray(load_high_time_major_matrix(), dtype=np.float32)
  rows, cols = high_tm_f32.shape
  high_tm_f32 = high_tm_f32.ravel()
  low_tm_f32 = np.asarray(load_low_time_major_matrix(), dtype=np.float32)
  low_tm_f32 = low_tm_f32.ravel()
  close_tm_f32 = np.asarray(load_close_time_major_matrix(), dtype=np.float32)
  close_tm_f32 = close_tm_f32.ravel()

  dev_tm = stoch_cuda_many_series_one_param_dev(
      high_tm_f32=high_tm_f32,
      low_tm_f32=low_tm_f32,
      close_tm_f32=close_tm_f32,
      cols=cols,
      rows=rows,
      fastk_period=14,
      slowk_period=14,
      slowd_period=14,
      slowk_ma_type=14,
      slowd_ma_type=14,
      device_id=0,
  )

CUDA Bindings (Rust)

use vector_ta::cuda::CudaStoch;
use vector_ta::indicators::stoch::StochBatchRange;

let cuda = CudaStoch::new(0)?;

let high_f32: [f32] = /* ... */;
let low_f32: [f32] = /* ... */;
let close_f32: [f32] = /* ... */;
let sweep = StochBatchRange::default();

let out = cuda.stoch_batch_dev(&high_f32, &low_f32, &close_f32, &sweep)?;
let _ = out;

Performance Analysis

Comparison:
View:

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

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-02-28

Related Indicators