KST Oscillator (Know Sure Thing)


The Know Sure Thing (KST) is a momentum oscillator developed by Martin Pring to make it easier for traders to interpret rate-of-change data. It is a summed, weighted moving average of four different rate-of-change (ROC) periods, designed to capture major market cycles.

KST

=KST(data)

Example Usage

=KST(A2:F500)

Parameters

Parameter Type Description Status
data
Range
The input range of columns containing the Date, Open, High, Low, Close, and Volume data.
Required

Returns

A multi-column array containing:

  1. Date
  2. KST: The KST line.
  3. Signal: The 9-period SMA of the KST line.
KST Oscillator Formula Result in Google Sheets

Source Code

Copy the following code into your Apps Script editor (Extensions > Apps Script) to use the KST-OSCILLATOR function in your spreadsheet.

kst.js
/**
 * Calculates the KST Oscillator (Know Sure Thing).
 * A momentum oscillator based on smoothed ROCs (Rate of Change).
 * Formula:
 * RC1 = SMA(ROC(10), 10) * 1
 * RC2 = SMA(ROC(15), 10) * 2
 * RC3 = SMA(ROC(20), 10) * 3
 * RC4 = SMA(ROC(30), 15) * 4
 * KSTLine = RC1 + RC2 + RC3 + RC4
 * SignalLine = SMA(KSTLine, 9)
 *
 * @param {array} data - The input range. Must include at least 2 columns: Date, Value (Close).
 * @returns {array} A multi-column array with headers "Date", "KST", "Signal".
 * @customfunction
 */
function KST(data) {
    checkPremium();

    // Standard params
    const roc1Length = 10, sma1Length = 10;
    const roc2Length = 15, sma2Length = 10;
    const roc3Length = 20, sma3Length = 10;
    const roc4Length = 30, sma4Length = 15;
    const signalLength = 9;

    const processedData = getData(data);
    let valueIndex = 1;
    if (processedData[0].length >= 5) valueIndex = 4; // Close

    const dataRows = processedData.slice(1);
    const results = [["Date", "KST", "Signal"]];

    // We need price history for ROCs
    const maxLookback = 30; // Max ROC len
    const priceBuffer = [];

    // We need 4 buffers for the 4 ROC streams to calculate their SMAs
    const roc1Buffer = [], roc2Buffer = [], roc3Buffer = [], roc4Buffer = [];
    let sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0;

    // We need a buffer for KST Line to calculate Signal Line
    const kstBuffer = [];
    let kstSum = 0;

    for (let i = 0; i < dataRows.length; i++) {
        const row = dataRows[i];
        const date = row[0];
        const price = row[valueIndex];

        priceBuffer.push(price);
        if (priceBuffer.length > maxLookback + 1) priceBuffer.shift();

        // Need at least 30 periods history for ROC4
        if (priceBuffer.length <= maxLookback) {
            results.push([date, "", ""]);
            continue;
        }

        const currentPrice = priceBuffer[priceBuffer.length - 1];

        // Calculate ROCs
        // ROC = (Price - PriceAgo) / PriceAgo * 100
        const getROC = (len) => {
            const priceAgo = priceBuffer[priceBuffer.length - 1 - len];
            return ((currentPrice - priceAgo) / priceAgo) * 100;
        };

        const roc1 = getROC(roc1Length);
        const roc2 = getROC(roc2Length);
        const roc3 = getROC(roc3Length);
        const roc4 = getROC(roc4Length);

        // Update ROC SMAs
        // Helper to update SMA buffer
        const updateSMA = (buffer, val, len, currentSum) => {
            buffer.push(val);
            let newSum = currentSum + val;
            if (buffer.length > len) {
                newSum -= buffer.shift();
            }
            return { sum: newSum, isReady: buffer.length === len };
        };

        const s1 = updateSMA(roc1Buffer, roc1, sma1Length, sum1); sum1 = s1.sum;
        const s2 = updateSMA(roc2Buffer, roc2, sma2Length, sum2); sum2 = s2.sum;
        const s3 = updateSMA(roc3Buffer, roc3, sma3Length, sum3); sum3 = s3.sum;
        const s4 = updateSMA(roc4Buffer, roc4, sma4Length, sum4); sum4 = s4.sum;

        if (s1.isReady && s2.isReady && s3.isReady && s4.isReady) {
            const rc1 = (sum1 / sma1Length) * 1;
            const rc2 = (sum2 / sma2Length) * 2;
            const rc3 = (sum3 / sma3Length) * 3;
            const rc4 = (sum4 / sma4Length) * 4;

            const kstLine = rc1 + rc2 + rc3 + rc4;

            // Calculate Signal Line (SMA of KST)
            kstBuffer.push(kstLine);
            kstSum += kstLine;
            if (kstBuffer.length > signalLength) {
                kstSum -= kstBuffer.shift();
            }

            if (kstBuffer.length === signalLength) {
                const signalLine = kstSum / signalLength;
                results.push([date, kstLine, signalLine]);
            } else {
                results.push([date, kstLine, ""]);
            }

        } else {
            results.push([date, "", ""]);
        }
    }

    // Trim Output
    // Align dynamic: Wait for ALL signal lines to be ready.
    // KST Line appears first, Signal Line (SMA of KST) appears later.
    // We wait for the Signal Line to prevent ragged start.
    let firstValidIndex = -1;
    for (let i = 1; i < results.length; i++) {
        if (results[i][1] !== "" && results[i][1] !== null &&
            results[i][2] !== "" && results[i][2] !== null) {
            firstValidIndex = i;
            break;
        }
    }

    if (firstValidIndex !== -1) {
        return [results[0], ...results.slice(firstValidIndex)];
    } else {
        return [results[0]];
    }
}