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:
- Date
- KST: The KST line.
- Signal: The 9-period SMA of the KST line.
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]];
}
}