Know Sure Thing (KST)

Parameters: sma_period1 = 10 | sma_period2 = 10 | sma_period3 = 10 | sma_period4 = 15 | roc_period1 = 10 | roc_period2 = 15 | roc_period3 = 20 | roc_period4 = 30 | signal_period = 9

Overview

Know Sure Thing blends multiple timeframe momentum into a single oscillator by combining four weighted rate of change calculations, each smoothed and scaled to capture different market cycles from short term to long term perspectives. The indicator calculates rate of change over 10, 15, 20, and 30 periods, smooths each with moving averages, then weights them progressively from 1x to 4x with longer periods receiving higher weights to emphasize major trend changes over minor fluctuations. This multi timeframe approach prevents the whipsaws common in single period momentum indicators while still maintaining responsiveness to genuine trend shifts across all time horizons. When KST crosses above its signal line from negative territory, it confirms bullish momentum building across multiple timeframes, providing higher conviction entry signals than simple momentum crossovers. The oscillator excels at identifying major market turns because significant reversals require momentum shifts across all four timeframes, filtering out false signals from temporary single timeframe movements. Professional swing traders particularly value KST for position timing because its weighted structure naturally aligns with the fractal nature of markets, where longer term trends carry more significance than short term noise, creating signals that capture substantial moves while avoiding premature exits.

Implementation Examples

Get started with KST using slices or candles:

use vectorta::indicators::kst::{kst, KstInput, KstParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// From a price slice
let prices = vec![100.0, 102.0, 101.5, 103.0, 105.0, 104.5];
let params = KstParams {
    sma_period1: Some(10),
    sma_period2: Some(10),
    sma_period3: Some(10),
    sma_period4: Some(15),
    roc_period1: Some(10),
    roc_period2: Some(15),
    roc_period3: Some(20),
    roc_period4: Some(30),
    signal_period: Some(9),
};
let input = KstInput::from_slice(&prices, params);
let out = kst(&input)?; // out.line, out.signal

// From candles (defaults; source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = KstInput::with_default_candles(&candles);
let out = kst(&input)?;

println!("KST line last={}, signal last={}", out.line.last().unwrap(), out.signal.last().unwrap());

API Reference

Input Methods
// From price slice
KstInput::from_slice(&[f64], KstParams) -> KstInput

// From candles with custom source
KstInput::from_candles(&Candles, &str, KstParams) -> KstInput

// From candles with defaults (close, s1=10, s2=10, s3=10, s4=15, r1=10, r2=15, r3=20, r4=30, sig=9)
KstInput::with_default_candles(&Candles) -> KstInput
Parameters Structure
pub struct KstParams {
    pub sma_period1: Option<usize>, // Default: 10
    pub sma_period2: Option<usize>, // Default: 10
    pub sma_period3: Option<usize>, // Default: 10
    pub sma_period4: Option<usize>, // Default: 15
    pub roc_period1: Option<usize>, // Default: 10
    pub roc_period2: Option<usize>, // Default: 15
    pub roc_period3: Option<usize>, // Default: 20
    pub roc_period4: Option<usize>, // Default: 30
    pub signal_period: Option<usize>, // Default: 9
}
Output Structure
pub struct KstOutput {
    pub line: Vec<f64>,   // KST line
    pub signal: Vec<f64>, // SMA of KST line
}
Validation, Warmup & NaNs
  • Errors: KstError::EmptyInputData, KstError::AllValuesNaN, KstError::InvalidPeriod, KstError::NotEnoughValidData, plus wrapped KstError::Roc(RocError) and KstError::Sma(SmaError).
  • All periods must be > 0. Requires enough valid data after the first finite input to satisfy each ri + si − 1 and the signal warmup.
  • Warmup: KST line is NaN up to index first + max(ri+si−1). Signal is NaN up to first + max(ri+si−1) + sig − 1.
  • first is the index of the first finite price; leading NaNs are skipped and outputs prefixed with NaN accordingly.
Error Handling
use vectorta::indicators::kst::{kst, KstError};

match kst(&input) {
    Ok(out) => consume(out.line, out.signal),
    Err(KstError::EmptyInputData) => eprintln!("no data"),
    Err(KstError::AllValuesNaN) => eprintln!("all values are NaN"),
    Err(KstError::InvalidPeriod { period, data_len }) => eprintln!("invalid period {} for len {}", period, data_len),
    Err(KstError::NotEnoughValidData { needed, valid }) => eprintln!("need {} valid points, found {}", needed, valid),
    Err(KstError::Roc(e)) => eprintln!("roc error: {}", e),
    Err(KstError::Sma(e)) => eprintln!("sma error: {}", e),
}

Python Bindings

Basic Usage
import numpy as np
from vectorta import kst, KstParams

prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5], dtype=float)
params = KstParams(sma1=10, sma2=10, sma3=10, sma4=15, roc1=10, roc2=15, roc3=20, roc4=30, sig=9)

line, signal = kst(prices, params)
print(line[-1], signal[-1])
Streaming
from vectorta import KstStream, KstParams

stream = KstStream(KstParams())
for price in prices_stream:
    res = stream.update(price)
    if res is not None:
        line, signal = res
        do_something(line, signal)
Batch Processing

Run a focused grid; extract target combination rows:

import numpy as np
from vectorta import kst_batch

prices = np.array([100.0, 102.0, 101.5, 103.0, 105.0, 104.5], dtype=float)
res = kst_batch(
    prices,
    sma1_range=(10, 10, 0),
    sma2_range=(10, 10, 0),
    sma3_range=(10, 10, 0),
    sma4_range=(15, 15, 0),
    roc1_range=(10, 10, 0),
    roc2_range=(15, 15, 0),
    roc3_range=(20, 20, 0),
    roc4_range=(30, 30, 0),
    sig_range=(9, 9, 0),
    kernel='auto'
)

# Access result matrices and metadata
line = res['line']      # shape: (num_combos, len(prices))
signal = res['signal']  # shape: (num_combos, len(prices))
sma1 = res['sma1']; sma2 = res['sma2']; sma3 = res['sma3']; sma4 = res['sma4']
roc1 = res['roc1']; roc2 = res['roc2']; roc3 = res['roc3']; roc4 = res['roc4']
sig  = res['sig']

JavaScript/WASM Bindings

Basic Usage

Compute KST line and signal in JavaScript/TypeScript:

import { kst } from 'vectorta-wasm';

const prices = new Float64Array([100, 102, 101.5, 103, 105, 104.5]);
// Args: data, s1, s2, s3, s4, r1, r2, r3, r4, sig
const out = kst(prices, 10, 10, 10, 15, 10, 15, 20, 30, 9);

// out is an object: { values: Float64Array, rows: 2, cols: prices.length }
// First row = KST line, second row = signal
const rows = out.rows; // 2
const cols = out.cols;
const values = out.values; // length = rows * cols
const line = values.slice(0, cols);
const signal = values.slice(cols, 2 * cols);
Memory-Efficient Operations

Use zero‑copy operations with explicit alloc/free:

import { kst_alloc, kst_free, kst_into, memory } from 'vectorta-wasm';

const len = prices.length;
const inPtr = kst_alloc(len);
const linePtr = kst_alloc(len);
const sigPtr = kst_alloc(len);

new Float64Array(memory.buffer, inPtr, len).set(prices);

// Args: in_ptr, line_out_ptr, signal_out_ptr, len, s1, s2, s3, s4, r1, r2, r3, r4, sig
kst_into(inPtr, linePtr, sigPtr, len, 10, 10, 10, 15, 10, 15, 20, 30, 9);

const line = new Float64Array(memory.buffer, linePtr, len).slice();
const signal = new Float64Array(memory.buffer, sigPtr,  len).slice();

kst_free(inPtr, len);
kst_free(linePtr, len);
kst_free(sigPtr, len);
Batch Processing

Sweep ranges; output flattens [line rows..., signal rows...]:

import { kst_batch } from 'vectorta-wasm';

const config = {
  sma_period1_range: [10, 10, 0],
  sma_period2_range: [10, 10, 0],
  sma_period3_range: [10, 10, 0],
  sma_period4_range: [15, 15, 0],
  roc_period1_range: [10, 10, 0],
  roc_period2_range: [15, 15, 0],
  roc_period3_range: [20, 20, 0],
  roc_period4_range: [30, 30, 0],
  signal_period_range: [9, 9, 0],
};

const out = kst_batch(prices, config);
// out: { values: Float64Array, combos: KstParams[], rows: combos*2, cols }
const comboCount = out.rows / 2;
const cols = out.cols;
const linesFlat = out.values.slice(0, comboCount * cols);
const sigsFlat  = out.values.slice(comboCount * cols);

// Extract first combo
const firstLine = linesFlat.slice(0, cols);
const firstSignal = sigsFlat.slice(0, cols);

Performance Analysis

Comparison:
View:
Loading chart...

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

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