Rate of Change (ROC)


Rate of Change (ROC) is a pure momentum oscillator that measures the percentage change in price from one period to the next. The ROC calculation compares the current price with the price “n” periods ago.

ROC

=ROC(data, period)

Example Usage

=ROC(A2:F500, 9)

Parameters

Parameter Type Description Status
data
Range
The input range of columns containing the Date, Open, High, Low, Close, and Volume data.
Required
period
Number
The number of periods to look back (e.g., 9 or 12).
Required

Returns

A two-column array of dates and their corresponding ROC percentage values.

Rate of Change (ROC) Formula Result in Google Sheets

Source Code

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

roc.js
/**
 * Calculates the Rate of Change (ROC) for a specified period.
 * ROC measures the percentage change in price between the current period and n periods ago.
 *
 * @param {array} data - The input range containing metrics. Can be a multi-column range (Date, Open, High, Low, Close, Volume) or a two-column range (Date, Value).
 * @param {number} period - The number of periods to look back (e.g., 9).
 * @returns {array} A two-column array with headers "Date" and "ROC".
 * @customfunction
 */
function ROC(data, period) {
    checkPremium();

  // 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 > 2 && columnCount < 5) {
    throw new Error(`Invalid data structure for ROC. For multi-column data, expected at least 5 columns (Date, O, H, L, C), but got ${columnCount}. For simple data, expected 2 columns (Date, Value).`);
  }
  // --- END of validation ---

  const dates = processedData.slice(1).map(row => row[0]);
  const values = getValues(processedData);

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

  const results = [["Date", `ROC(${period})`]];

  for (let i = period; i < values.length; i++) {
    const prevValue = values[i - period];
    const currentValue = values[i];

    if (prevValue === 0) {
      results.push([dates[i], 0]); // Or handle as an error, depending on desired behavior for zero previous value
      continue;
    }

    const roc = ((currentValue - prevValue) / prevValue) * 100;
    results.push([dates[i], roc]);
  }

  return results;
}