KDJ Indicator

Parameters: fast_k_period = 9 | slow_k_period = 3 | slow_k_ma_type = sma | slow_d_period = 3 | slow_d_ma_type = sma

Overview

The KDJ indicator enhances the traditional stochastic oscillator by adding a third J line that amplifies momentum signals through the formula J = 3K - 2D, creating earlier and more aggressive reversal signals than standard stochastic crossovers. While the K line shows raw momentum and D provides smoothed confirmation, the J line swings beyond normal oscillator bounds, often exceeding 100 or dropping below 0 to highlight extreme overbought and oversold conditions before price reverses. When J crosses above 80 or below 20 ahead of the K and D lines, it provides advance warning of potential turning points, giving traders a timing edge for entries and exits. The indicator excels at catching short term reversals in ranging markets where the amplified J line identifies exhaustion points that standard stochastics miss due to their bounded nature. Traders particularly value KDJ for scalping and swing trading because the J line's exaggerated swings create clear, actionable signals at price extremes without waiting for slower K/D crossovers. Additionally, divergences between the J line and price action provide powerful reversal signals, as the amplified nature of J makes these divergences more pronounced and easier to identify than with traditional momentum oscillators.

Implementation Examples

Compute K, D, J from highs/lows/closes or candles:

use vectorta::indicators::kdj::{kdj, KdjInput, KdjParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using raw H/L/C slices
let high = vec![..];
let low = vec![..];
let close = vec![..];
let params = KdjParams { fast_k_period: Some(9), slow_k_period: Some(3), slow_k_ma_type: Some("sma".into()), slow_d_period: Some(3), slow_d_ma_type: Some("sma".into()) };
let input = KdjInput::from_slices(&high, &low, &close, params);
let out = kdj(&input)?; // out.k, out.d, out.j

// Using Candles with defaults (fast_k=9, slow_k=3/sma, slow_d=3/sma)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = KdjInput::with_default_candles(&candles);
let out = kdj(&input)?;

// Iterate values
for i in 0..out.k.len() {
    println!("K: {} D: {} J: {}", out.k[i], out.d[i], out.j[i]);
}

API Reference

Input Methods
// From raw H/L/C slices
KdjInput::from_slices(&[f64], &[f64], &[f64], KdjParams) -> KdjInput

// From candles (uses high/low/close fields)
KdjInput::from_candles(&Candles, KdjParams) -> KdjInput

// With default params (fast_k=9, slow_k=3/sma, slow_d=3/sma)
KdjInput::with_default_candles(&Candles) -> KdjInput
Parameters Structure
pub struct KdjParams {
    pub fast_k_period: Option<usize>,   // Default: 9
    pub slow_k_period: Option<usize>,   // Default: 3
    pub slow_k_ma_type: Option<String>, // Default: "sma"
    pub slow_d_period: Option<usize>,   // Default: 3
    pub slow_d_ma_type: Option<String>, // Default: "sma"
}
Output Structure
pub struct KdjOutput {
    pub k: Vec<f64>,
    pub d: Vec<f64>,
    pub j: Vec<f64>,
}
Validation, Warmup & NaNs
  • fast_k_period > 0 and not greater than valid data after the first finite H/L/C; otherwise KdjError::InvalidPeriod or KdjError::NotEnoughValidData.
  • Leading NaNs in any of H/L/C delay the first value; KdjError::AllValuesNaN if no finite triple exists.
  • Warmup: let first be first valid index, then stoch_warm = first + fast_k - 1, k_warm = stoch_warm + slow_k - 1, d_warm = k_warm + slow_d - 1. Outputs before these are NaN.
  • In-place API kdj_into_slices: all output buffers must match input length; otherwise KdjError::BufferSizeMismatch.
  • MA types: "sma" and "ema" have optimized paths; other strings dispatch via the moving-average module.
Error Handling
use vectorta::indicators::kdj::{kdj, KdjError};

match kdj(&input) {
    Ok(out) => process(out.k, out.d, out.j),
    Err(KdjError::EmptyData) => eprintln!("No input data"),
    Err(KdjError::InvalidPeriod { period, data_len }) =>
        eprintln!("Invalid period {} for length {}", period, data_len),
    Err(KdjError::NotEnoughValidData { needed, valid }) =>
        eprintln!("Need {} valid points, have {}", needed, valid),
    Err(KdjError::AllValuesNaN) => eprintln!("All values are NaN"),
    Err(KdjError::BufferSizeMismatch { expected, got }) =>
        eprintln!("Buffer size mismatch: expected {}, got {}", expected, got),
    Err(KdjError::RollingError(e)) => eprintln!("Rolling calc error: {}", e),
    Err(KdjError::MaError(e)) => eprintln!("MA error: {}", e),
}

Python Bindings

Basic Usage

Calculate KDJ using NumPy arrays (defaults: 9, 3/sma, 3/sma):

import numpy as np
from vectorta import kdj

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

# Defaults
k, d, j = kdj(high, low, close)

# Custom parameters and kernel selection
k, d, j = kdj(high, low, close,
              fast_k_period=9,
              slow_k_period=3,
              slow_k_ma_type="sma",
              slow_d_period=3,
              slow_d_ma_type="sma",
              kernel="auto")

print(k.shape, d.shape, j.shape)
Streaming Real-time Updates

Maintain rolling state and update on each H/L/C tick:

from vectorta import KdjStream

stream = KdjStream(fast_k_period=9, slow_k_period=3,
                   slow_k_ma_type="sma", slow_d_period=3,
                   slow_d_ma_type="sma")

for h, l, c in hlc_feed:
    values = stream.update(h, l, c)
    if values is not None:
        k, d, j = values
        use(k, d, j)
Batch Parameter Optimization

Test multiple combinations across ranges:

import numpy as np
from vectorta import kdj_batch

H = np.array([...], dtype=float)
L = np.array([...], dtype=float)
C = np.array([...], dtype=float)

res = kdj_batch(H, L, C,
                fast_k_range=(7, 14, 1),
                slow_k_range=(3, 5, 1),
                slow_k_ma_type="sma",
                slow_d_range=(3, 5, 1),
                slow_d_ma_type="sma",
                kernel="auto")

# res['k'], res['d'], res['j'] are shaped (rows, len)
print(res['k'].shape, res['d'].shape, res['j'].shape)
print(res['fast_k_periods'], res['slow_k_periods'], res['slow_d_periods'])
CUDA Acceleration

CUDA support for KDJ is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated KDJ calculations
# from vectorta import kdj_cuda_batch, kdj_cuda_many_series_one_param
# import numpy as np
#
# # One Series, Many Parameters
# out = kdj_cuda_batch(
#     high=H, low=L, close=C,
#     fast_k_range=(5, 20, 1),
#     slow_k_range=(3, 6, 1),
#     slow_k_ma_type="sma",
#     slow_d_range=(3, 6, 1),
#     slow_d_ma_type="sma",
#     device_id=0
# )
#
# # Many Series, One Parameter Set
# tm_high = np.array([...], dtype=np.float32)  # [T, N]
# tm_low = np.array([...], dtype=np.float32)
# tm_close = np.array([...], dtype=np.float32)
# out = kdj_cuda_many_series_one_param(
#     high_tm_f32=tm_high, low_tm_f32=tm_low, close_tm_f32=tm_close,
#     fast_k_period=9, slow_k_period=3, slow_k_ma_type="sma",
#     slow_d_period=3, slow_d_ma_type="sma",
#     device_id=0
# )

JavaScript/WASM Bindings

Basic Usage

Compute K/D/J in JavaScript/TypeScript:

import { kdj } from 'vectorta-wasm';

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

const res = kdj(high, low, close, 9, 3, 'sma', 3, 'sma');
// res: { values: Float64Array, rows: 3, cols: N }

// Split into K/D/J slices
const { values, rows, cols } = res as { values: Float64Array, rows: number, cols: number };
const k = values.slice(0, cols);
const d = values.slice(cols, 2 * cols);
const j = values.slice(2 * cols, 3 * cols);
Memory-Efficient Operations

Use zero-copy buffers for large datasets:

import { kdj_alloc, kdj_free, kdj_into, memory } from 'vectorta-wasm';

const N = close.length;
const hPtr = kdj_alloc(N);
const lPtr = kdj_alloc(N);
const cPtr = kdj_alloc(N);
const kPtr = kdj_alloc(N);
const dPtr = kdj_alloc(N);
const jPtr = kdj_alloc(N);

new Float64Array(memory.buffer, hPtr, N).set(high);
new Float64Array(memory.buffer, lPtr, N).set(low);
new Float64Array(memory.buffer, cPtr, N).set(close);

kdj_into(hPtr, lPtr, cPtr, kPtr, dPtr, jPtr, N, 9, 3, 'sma', 3, 'sma');

const K = new Float64Array(memory.buffer, kPtr, N).slice();
const D = new Float64Array(memory.buffer, dPtr, N).slice();
const J = new Float64Array(memory.buffer, jPtr, N).slice();

[hPtr, lPtr, cPtr, kPtr, dPtr, jPtr].forEach(ptr => kdj_free(ptr, N));
Batch Processing

Calculate multiple KDJ configurations at once:

import { kdj_batch } from 'vectorta-wasm';

const cfg = {
  fast_k_period: [7, 14, 1],
  slow_k_period: [3, 5, 1],
  slow_k_ma_type: 'sma',
  slow_d_period: [3, 5, 1],
  slow_d_ma_type: 'sma',
};

const out = kdj_batch(high, low, close, cfg);
// out: { values: Float64Array, combos: KdjParams[], rows: 3*combos, cols: N }
// First rows are all K blocks, then D, then J per combo

Performance Analysis

Comparison:
View:
Loading chart...

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

Related Indicators