Stochastic RSI (STOCH-RSI)


The Stochastic RSI (StochRSI) is a technical indicator that measures the momentum of the Relative Strength Index (RSI), rather than price itself. It is created by applying the Stochastic Oscillator formula to a set of RSI values. The result is a more sensitive oscillator that fluctuates between 0 and 100, designed to identify short-term overbought and oversold conditions with greater frequency than the standard RSI. This increased sensitivity allows traders to pinpoint potential entry and exit points within a broader trend identified by the RSI.

STOCH_RSI

=STOCH_RSI(data, period, smoothing)

Example Usage

=STOCH_RSI(A2:F500, 14, 3)

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 RSI.
Required
smoothing
Number
The number of periods used to smooth the %K values into the %D signal line.
Required

Returns

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

Stochastic RSI Formula Result in Google Sheets

Source Code

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

stochasticRSI.js
/**
 * Calculates the Stochastic RSI 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) for RSI calculation.
 * @param {number} period - The number of periods for calculating the Stochastic RSI.
 * @param {number} smoothing - The number of periods for the SMA smoothing of %K and %D.
 * @returns {array} - A 2D array with headers: Date, %K, %D.
 * @customfunction
 */
function STOCH_RSI(data, period, smoothing) {
  // Argument validation
  if (arguments.length !== 3) {
    throw new Error(`Wrong number of arguments. Expected 3, 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}`);
  }
  if (typeof smoothing !== 'number' || smoothing <= 0 || !Number.isInteger(smoothing)) {
    throw new Error(`Invalid smoothing period. The smoothing period must be a positive integer. Got: ${smoothing}`);
  }

  // RSI handles its own data validation
  const rsiValues = RSI(data, period).slice(1); // Remove headers

  // Extract RSI values for %K calculation
  const dates = rsiValues.map(row => row[0]);
  const rsiOnly = rsiValues.map(row => row[1]);

  if (period >= rsiOnly.length) {
    throw new Error(`Invalid period. The period (${period}) cannot be greater than or equal to the number of RSI data points (${rsiOnly.length}).`);
  }

  // Calculate %K for each period using RSI values
  const stochRSIValues = [];
  for (let i = period - 1; i < rsiOnly.length; i++) {
    const periodHigh = Math.max(...rsiOnly.slice(i - period + 1, i + 1));
    const periodLow = Math.min(...rsiOnly.slice(i - period + 1, i + 1));
    const currentRSI = rsiOnly[i];

    let rsiValue;
    if (periodHigh === periodLow) {
        rsiValue = 0; // Avoid division by zero
    } else {
        rsiValue = ((currentRSI - periodLow) / (periodHigh - periodLow)) * 100;
    }
    stochRSIValues.push([dates[i], rsiValue]);
  }

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

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

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

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