Aroon Indicator


The Aroon indicator is used to identify whether a security is trending and the strength of the trend. It consists of two lines: “Aroon Up” measures how long it has been since the highest high, and “Aroon Down” measures how long it has been since the lowest low.

AROON

=AROON(data, period)

Example Usage

=AROON(A2:F500, 25)

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 lookback period. Default is 25.
Optional

Returns

A multi-column array containing:

  1. Date
  2. Aroon Up: (periods since high / period) * 100.
  3. Aroon Down: (periods since low / period) * 100.
Aroon Formula Result in Google Sheets

Source Code

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

aroon.js
/**
 * Calculates the Aroon Indicator (Aroon Up, Aroon Down).
 * Aroon Up = ((period - Days Since N-day High) / period) * 100
 * Aroon Down = ((period - Days Since N-day Low) / period) * 100
 *
 * @param {array} data - The input range. Must include at least 4 columns: Date, Open, High, Low.
 * @param {number} [period=25] - The lookback period (default 25).
 * @returns {array} A multi-column array with headers "Date", "Aroon Up", "Aroon Down".
 * @customfunction
 */
function AROON(data, period = 25) {
    checkPremium();

    // Argument validation
    if (arguments.length < 1 || arguments.length > 2) {
        throw new Error(`Wrong number of arguments. Expected 1 or 2, but got ${arguments.length}.`);
    }
    if (period !== undefined) {
        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);

    // --- Validate Data Structure ---
    const columnCount = processedData[0].length;
    if (columnCount < 4) {
        throw new Error(`Invalid data structure. Expected at least 4 columns (Date, O, H, L), but got ${columnCount}.`);
    }

    const dataRows = processedData.slice(1);
    const results = [["Date", `Aroon Up (${period})`, `Aroon Down (${period})`]];

    // We need a buffer of past Highs and Lows of size (period + 1) to find "days since".
    // Actually, we need to look back 'period' days.
    // The formula usually implies looking relatively.

    const highBuffer = [];
    const lowBuffer = [];

    for (let i = 0; i < dataRows.length; i++) {
        const row = dataRows[i];
        const date = row[0];
        const high = row[2];
        const low = row[3];

        highBuffer.push(high);
        lowBuffer.push(low);

        // Maintain buffer size: period + 1 (to include current? Or just period?)
        // Standard: Lookback period days. 
        if (highBuffer.length > period + 1) {
            highBuffer.shift();
            lowBuffer.shift();
        }

        if (highBuffer.length < period + 1) {
            results.push([date, "", ""]);
            continue;
        }

        // Find index of Max High and Min Low in the buffer
        // The buffer represents a window. Last element is "0 days ago".
        // We need "Days Since".
        // If max is at index `len - 1` (current), days since = 0.
        // If max is at index `0` (oldest), days since = period.

        let maxHigh = -Infinity;
        let minLow = Infinity;
        let maxIndex = -1;
        let minIndex = -1;

        // Iterate buffer
        for (let j = 0; j < highBuffer.length; j++) {
            if (highBuffer[j] >= maxHigh) {
                // Note regarding ties: usually most recent? Or oldest?
                // Standard: "Number of periods since the highest high".
                // Usually means most recent high if equal? 
                // TradingView: `highestbars(high, length)` returns offset.
                // Let's assume most recent to restart the count.
                maxHigh = highBuffer[j];
                maxIndex = j;
            }
            if (lowBuffer[j] <= minLow) {
                minLow = lowBuffer[j];
                minIndex = j;
            }
        }

        // Calculate Days Since
        // buffer length is period + 1.
        // Index 0 is 'period' ago. Index 'period' is 0 ago.
        // Days Since = (buffer.length - 1) - Index.

        const daysSinceHigh = (highBuffer.length - 1) - maxIndex;
        const daysSinceLow = (lowBuffer.length - 1) - minIndex;

        const aroonUp = ((period - daysSinceHigh) / period) * 100;
        const aroonDown = ((period - daysSinceLow) / period) * 100;

        results.push([date, aroonUp, aroonDown]);
    }

    // Trim Output
    let firstValidIndex = -1;
    for (let i = 1; i < results.length; i++) {
        // Check both columns? Usually they come valid together.
        if (results[i][1] !== "" && results[i][1] !== null) {
            firstValidIndex = i;
            break;
        }
    }

    if (firstValidIndex !== -1) {
        return [results[0], ...results.slice(firstValidIndex)];
    } else {
        return [results[0]];
    }
}