Chande Kroll Stop (CKSP)

Parameters: p = 10 | x = 1 (0.5–5) | q = 9

Overview

The Chande Kroll Stop (CKSP), developed by Tushar Chande and Stanley Kroll, provides volatility adjusted stop levels that help traders stay with trends while protecting against adverse moves. The indicator calculates two distinct stop lines using a combination of Average True Range (ATR) and rolling price extremes. For long positions, CKSP finds the highest high over the initial ATR period, subtracts the ATR multiplied by your chosen factor, then takes the highest value of this calculation over the smoothing period. Short stops work inversely, using the lowest low plus ATR and then finding the minimum over the smoothing window.

The dual smoothing approach makes CKSP more stable than single period stops while maintaining responsiveness to significant price moves. The first ATR period determines volatility measurement sensitivity, while the secondary smoothing period controls how quickly stops adjust to new market conditions. This two stage process filters out minor fluctuations that might trigger premature exits while allowing the stops to tighten during sustained trends. Typical settings use a 10 period ATR with a 9 period smoothing window, though traders often adjust these based on their holding periods.

Traders primarily use CKSP for position management and trend confirmation. When price remains above the long stop line, the uptrend remains intact. Dropping below signals potential trend weakness or reversal. The distance between the two stop lines also provides valuable information about market volatility and trend strength. Wider spreads indicate higher volatility or stronger trends, while narrowing stops suggest consolidation or trend exhaustion. Many systematic traders incorporate CKSP into their exit strategies, using the appropriate stop line based on their position direction to time exits objectively.

Implementation Examples

Compute CKSP long/short stops from OHLC inputs:

use vector_ta::indicators::cksp::{cksp, CkspInput, CkspParams};
use vector_ta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using with OHLC slices
let high = vec![101.0, 102.5, 103.0, 102.0, 104.0];
let low = vec![ 99.0, 100.5, 100.8, 100.0, 101.5];
let close = vec![100.5, 101.7, 102.2, 101.0, 103.0];
let params = CkspParams { p: Some(10), x: Some(1.0), q: Some(9) };
let input = CkspInput::from_slices(&high, &low, &close, params);
let out = cksp(&input)?;

// Using with Candles (defaults: p=10, x=1.0, q=9; source=high/low/close)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = CkspInput::with_default_candles(&candles);
let out = cksp(&input)?;

// Access the stop lines
for (ls, ss) in out.long_values.iter().zip(out.short_values.iter()) {
    println!("long_stop={} short_stop={}", ls, ss);
}

API Reference

Input Methods
// From slices (high, low, close)
CkspInput::from_slices(&[f64], &[f64], &[f64], CkspParams) -> CkspInput

// From candles (fields: high/low/close)
CkspInput::from_candles(&Candles, CkspParams) -> CkspInput

// From candles with defaults (p=10, x=1.0, q=9)
CkspInput::with_default_candles(&Candles) -> CkspInput
Parameters Structure
#[derive(Debug, Clone)]
pub struct CkspParams {
    pub p: Option<usize>, // Default: 10
    pub x: Option<f64>,   // Default: 1.0
    pub q: Option<usize>, // Default: 9
}
Output Structure
#[derive(Debug, Clone)]
pub struct CkspOutput {
    pub long_values: Vec<f64>,  // Long stop line
    pub short_values: Vec<f64>, // Short stop line
}
Validation, Warmup & NaNs
  • p > 0, q > 0; x must be finite (non-NaN).
  • Input lengths must match; otherwise CkspError::InconsistentLengths.
  • Empty input ⇒ CkspError::EmptyInputData. All NaNCkspError::AllValuesNaN.
  • CkspError::NotEnoughValidData { needed, valid } if the series is too short after the first finite value (needed = p + q).
  • When using Candles, field selection errors map to CkspError::CandleFieldError.
  • Warmup indices are NaN; first valid index is at first_valid + p + q - 1.
  • Streaming: update returns None until warmup completes; then yields finite stops.
Error Handling
use vector_ta::indicators::cksp::{cksp, CkspError};

	match cksp(&input) {
	    Ok(output) => process(output.long_values, output.short_values),
	    Err(CkspError::EmptyInputData) => eprintln!("cksp: no data provided"),
	    Err(CkspError::AllValuesNaN) => eprintln!("cksp: all values were NaN"),
	    Err(CkspError::NotEnoughValidData { needed, valid }) =>
	        eprintln!("cksp: need {needed} valid points, only {valid} available"),
	    Err(CkspError::InconsistentLengths) => eprintln!("cksp: input length mismatch"),
	    Err(CkspError::InvalidMultiplier { x }) => eprintln!("cksp: invalid x: {}", x),
	    Err(CkspError::InvalidParam { param }) => eprintln!("cksp: invalid param: {}", param),
	    Err(CkspError::CandleFieldError(msg)) => eprintln!("cksp: candle field: {}", msg),
	}

Python Bindings

Basic Usage

Calculate CKSP using NumPy arrays (defaults: p=10, x=1.0, q=9):

import numpy as np
from vector_ta import cksp

# Prepare OHLC data as NumPy arrays
high = np.array([...], dtype=np.float64)
low = np.array([...], dtype=np.float64)
close = np.array([...], dtype=np.float64)

# Calculate CKSP with defaults
long, short = cksp(high, low, close)

# Or specify custom parameters and kernel
long, short = cksp(high, low, close, p=10, x=1.0, q=9, kernel="auto")

print(long.shape, short.shape)
Streaming Real-time Updates

Process OHLC updates efficiently:

import numpy as np
from vector_ta import CkspStream

stream = CkspStream(p=10, x=1.0, q=9)
for (h, l, c) in ohlc_feed:
    val = stream.update(h, l, c)
    if val is not None:
        long_stop, short_stop = val
        do_something(long_stop, short_stop)
Batch Parameter Optimization

Test multiple combinations for p, x, q:

import numpy as np
from vector_ta import cksp_batch

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

results = cksp_batch(
    high, low, close,
    p_range=(10, 20, 5),
    x_range=(0.5, 2.0, 0.5),
    q_range=(9, 15, 3),
    kernel="auto"
)

long_vals = results['long_values']  # shape: (num_combos, len)
short_vals = results['short_values']
ps = results['p']
xs = results['x']
qs = results['q']
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). Rust CUDA wrappers are documented below.

import numpy as np
from vector_ta import cksp_cuda_batch_dev, cksp_cuda_many_series_one_param_dev

h_f32 = np.asarray(high, dtype=np.float32)
l_f32 = np.asarray(low, dtype=np.float32)
c_f32 = np.asarray(close, dtype=np.float32)
res = cksp_cuda_batch_dev(
    h_f32,
    l_f32,
    c_f32,
    p_range=(10, 20, 5),
    x_range=(0.5, 2.0, 0.5),
    q_range=(9, 15, 3),
    device_id=0,
)
long_dev = res["long_values"]
short_dev = res["short_values"]
print(res["rows"], res["cols"], res["p"], res["x"], res["q"])

high_tm = np.asarray(load_time_major_high(), dtype=np.float32)  # shape: (rows, cols)
low_tm = np.asarray(load_time_major_low(), dtype=np.float32)
close_tm = np.asarray(load_time_major_close(), dtype=np.float32)
res_tm = cksp_cuda_many_series_one_param_dev(high_tm, low_tm, close_tm, p=10, x=1.0, q=9, device_id=0)
print(res_tm["rows"], res_tm["cols"])

JavaScript/WASM Bindings

Basic Usage

Compute CKSP from OHLC arrays:

import { cksp_js } from 'vectorta-wasm';

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

// Returns a flat array: [long..., short...]
const values = cksp_js(high, low, close, 10, 1.0, 9);
const n = close.length;
const longStops = values.slice(0, n);
const shortStops = values.slice(n);
Memory-Efficient Operations

Use zero-copy into pre-allocated WASM memory:

import { cksp_alloc, cksp_free, cksp_into, memory } from 'vectorta-wasm';

const n = close.length;
const longPtr = cksp_alloc(n);
const shortPtr = cksp_alloc(n);

// Compute directly into WASM memory
cksp_into(high, low, close, longPtr, shortPtr, n, 10, 1.0, 9);

// Read out results
const longStops = new Float64Array(memory.buffer, longPtr, n).slice();
const shortStops = new Float64Array(memory.buffer, shortPtr, n).slice();

// Free when done
cksp_free(longPtr, n);
cksp_free(shortPtr, n);
Batch Processing

Test multiple parameter combinations:

import { cksp_batch } from 'vectorta-wasm';

const config = { p_range: [10, 20, 5], x_range: [0.5, 2.0, 0.5], q_range: [9, 15, 3] };
const out = cksp_batch(high, low, close, config);

// out: { long_values, short_values, combos, rows, cols }
const { long_values, short_values, combos, rows, cols } = out;

// Access row i (flattened by rows)
const row = 0;
const longRow = long_values.slice(row * cols, (row + 1) * cols);
const shortRow = short_values.slice(row * cols, (row + 1) * cols);
const params = combos[row];

CUDA Bindings (Rust)

use vector_ta::cuda::CudaCksp;
use vector_ta::indicators::cksp::CkspBatchRange;

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

let high: [f32] = /* ... */;
let low: [f32] = /* ... */;
let close: [f32] = /* ... */;
let sweep = CkspBatchRange::default();

let out = cuda.cksp_batch_dev(&high, &low, &close, &sweep)?;
let _ = out;

Performance Analysis

Comparison:
View:
Loading chart...

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

CUDA note

In our benchmark workload, the Rust CPU implementation is faster than CUDA for this indicator. Prefer the Rust/CPU path unless your workload differs.

Related Indicators