Stochastic (STOCH)


The Stochastic Oscillator, developed by George C. Lane, is a popular momentum indicator that compares a particular closing price of a security to a range of its prices over a certain period of time. It is based on the premise that as prices rise, closing prices tend to be closer to the high of the recent range, and as prices fall, they tend to be closer to the low. The indicator oscillates between 0 and 100 and is primarily used to identify overbought (typically above 80) and oversold (typically below 20) conditions, which can signal potential momentum shifts and trend reversals..

STOCH

=STOCH(data, period)

Example Usage

=STOCH(A2:F500, 14)

Parameters

Parameter Type Description Status
data
Range
Range of columns containing the date, Open, high, Low, close, volume data.
Required
period
Number
The number of periods (days) used to calculate the Stochastic Oscillator.
Required

Returns

A three-column array of dates and their corresponding %K and %D values.

Stochastic Oscillator Formula Result in Google Sheets

Source Code

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

stochastic.js
/**
 * Calculates the Stochastic Oscillator for a given dataset and period.
 *
 * @param {array} data An array of historical stock data. Expected to have at least 5 columns (Date, Open, High, Low, Close).
 * @param {number} period - The number of periods for calculating the Stochastic Oscillator.
 * @returns {array} - A 2D array with headers: Date, %K, %D.
 * @customfunction
 */
function STOCH(data, period) {
  // Argument validation
  if (arguments.length !== 2) {
    throw new Error(`Wrong number of arguments. Expected 2, but got ${arguments.length}.`);
  }
  if (typeof period !== 'number' || period <= 0 || !Number.isInteger(period)) {
    throw new Error(`Invalid period. The period must be a positive integer. Got: ${period}`);
  }

  const processedData = getData(data);

  // --- NEW: Function-level validation ---
  const columnCount = processedData[0].length;
  if (columnCount < 5) {
    throw new Error(`Invalid data structure for Stochastic Oscillator. Expected at least 5 columns (Date, Open, High, Low, Close), but got ${columnCount}.`);
  }
  // --- END of validation ---

  const dates = processedData.slice(1).map(row => row[0]);
  const highs = processedData.slice(1).map(row => row[2]);
  const lows = processedData.slice(1).map(row => row[3]);
  const closes = processedData.slice(1).map(row => row[4]);

  if (period > closes.length) {
    throw new Error(`Invalid period. The period (${period}) cannot be greater than the number of data points (${closes.length}).`);
  }

  const kValues = [];
  for (let i = period - 1; i < closes.length; i++) {
    const periodHigh = Math.max(...highs.slice(i - period + 1, i + 1));
    const periodLow = Math.min(...lows.slice(i - period + 1, i + 1));
    const currentClose = closes[i];

    // Avoid division by zero if periodHigh === periodLow
    let kValue;
    if (periodHigh === periodLow) {
        kValue = 0; // Or handle as an error/NaN depending on desired behavior
    } else {
        kValue = ((currentClose - periodLow) / (periodHigh - periodLow)) * 100;
    }
    kValues.push([dates[i], kValue]);
  }

  // Calculate %D (3-period SMA of %K)
  // SMA expects data in [date, value] format, which kValues already is.
  const dValues = SMA(kValues, 3).slice(1); // Remove headers

  // Align %K and %D lengths
  const [alignedK, alignedD] = alignLENGTHS(kValues, dValues);

  // Combine %K and %D values into final output
  const stochValues = alignedK.map((k, i) => {
    return [k[0], k[1], alignedD[i] ? alignedD[i][1] : null];
  });

  return [["Date", "%K", "%D"], ...stochValues];
}