Average True Range (ATR)

Average True Range (ATR)

Function Syntax

=ATR(data, period)  
  • data (array):
    Range of columns containing the date, Open, high, Low, close, volume data.
  • period (number):
    Number of (periods) days over which to calculate the ATR.

Returns:
A two-column array of dates and their corresponding ATR values.

Google Sheets Output Example

Below is an example of the resulting array when applying the custom ATR() function in Google Sheets

atr return array
ATR Return Array

Spreadsheet

Apps Scripts Code

Here’s the full Google Apps Script code.
Paste this directly into the Google Sheets Apps Script editor (Extensions → Apps Script):

atr.gs
/**
 * Calculates the Average True Range (ATR) for a given data set and period.
 *
 * @param {array} data - an array where the first column is the  , and subsequent columns for  open, high, low, close and volume.  
 * @param {number} period - the number of days over which the ATR is calculated.
 * @returns {array} -the ATR values along with corresponding dates.
 * @customfunction
 */

function ATR(data, period) {


  //Check number of arguments
  if (arguments.length !== ATR.length) {
    throw new Error(`Wrong number of arguments. Expected ${ATR.length} arguments, but got ${arguments.length} arguments.`);
  }


  // Get True Range values using the TR function
  const trueRanges = TR(data); // Already includes headers
  
  // Calculate the ATR using the SMA function
  const atrValues = SMA(trueRanges, period); // SMA handles averaging

  // Update the ATR header for clarity
  atrValues[0][1] = `ATR(${period})`;

  return atrValues;
}


/**
 *
 * @param {Array<Array>} data - A 2D array of cell values to process. The first row should contain headers.
 * @returns {Array<Array>} - A 2D array where the first row contains headers and subsequent rows contain processed data.
 *
 * @customfunction
 * 
 */

function getData(data) {
  if (!Array.isArray(data) || data.length === 0) {
    throw new Error("Input data must be a non-empty array.");
  }


  // Check if the first row contains headers
  const headers = Array.isArray(data[0]) && data[0].every(item => typeof item === "string")
    ? data[0]
    : null;

  if (!headers) {
    // If no headers, assume the first column is the date column and process data
    return data.map(row => row.map((value, index) => {
      return index === 0 ? new Date(value) : parseFloat(value);
    }));
  }

  // Process rows with headers
  return [headers, ...data.slice(1).map(row => headers.map((header, index) => {
    const key = header.toLowerCase();
    return key === "date" ? new Date(row[index]) : parseFloat(row[index]) || row[index];
  }))];
}


/**
 * Calculates the True Range (TR) values
 *
 * @param {array} data - an array where the first column is the  dates, and subsequent columns for  open, high, low, close and volume.  
 * @returns {Array<Array>} - A 2D array containing dates and their corresponding True Range values.
 *
 * @customfunction
 */

function TR(data) {
  // Preprocess data using getData
  const processedData = getData(data);
  
  // Extract columns from the processed data
  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]);

  // Calculate True Range values
  let trueRanges = [["Date", "True Range"]]; // Include headers
  for (let i = 1; i < highs.length; i++) {
    let tr = Math.max(highs[i], closes[i - 1]) - Math.min(lows[i], closes[i - 1]);
    trueRanges.push([dates[i], tr]);
  }

  return trueRanges;
}


/**
 * Calculates the Simple Moving Average (SMA) for a given dataset and period.
 *
 * @param {array} data - an array where the first column is the  dates, and subsequent columns for  open, high, low, close and volume.  
 * @param {number} period The number of periods to calculate the SMA, e.g., 14 for a 14-day SMA.
 * @returns {Array<Array>} The Simple Moving Average (SMA) of the range.
 * @customfunction
 */

function SMA(data, period) {

  //Check number of arguments
  if (arguments.length !== SMA.length) {
    throw new Error(`Wrong number of arguments. Expected ${SMA.length} arguments, but got ${arguments.length} arguments.`);
  }
  const processedData = getData(data);
  const dates = processedData.slice(1).map(row => row[0]); // Extract dates
  const closePrices = processedData[0].length === 2
    ? processedData.slice(1).map(row => row[1]) // For 2-column arrays, use the second column
    : processedData.slice(1).map(row => row[4]); // Default: use index 4 for close price

  // Check if the period is greater than the length of the data
  if (period > closePrices.length) {
    throw new Error("Error: The specified period exceeds the length of the data.");
  }

  // Initialize an array to store SMA values with corresponding dates
  let results = [["Date", `SMA(${period})`]]; // Include headers

  // Calculate SMA for the given period
  for (let i = period - 1; i < closePrices.length; i++) {
    const periodPrices = closePrices.slice(i - period + 1, i + 1); // Get the slice for the period
    const sma = periodPrices.reduce((acc, val) => acc + val, 0) / period; // Calculate average

    // Append the current date and SMA to the results array
    results.push([dates[i], sma]);
  }

  return results;
}
Last updated on