Calculating Average True Range (ATR) on OHLC data with Python

Question:

The ATR is the average of the True Range for a given period. True Range is (High-Low) meaning I have computed this with the following:

df['High'].subtract(df['Low']).rolling(distance).mean()

However if a short period (or ‘distance’ in the example above) is required the ATR can be very jumpy, i.e. with large sporadic gaps appearing between some numbers.

The real ATR equation recognises this and smooths it out by doing the following:

Current ATR = [(Prior ATR x 13) + Current TR] / 14

However I am unsure how to do this in the same manner as I did above, i.e. a column wide operation.

Sample data including the TR and ATR(10) from my original method:

Date        Time            Open    High    Low     Close   TR      ATR
30/09/16    14:45:00+00:00  1.1216  1.1221  1.1208  1.1209  0.0013  0.0013
30/09/16    15:00:00+00:00  1.1209  1.1211  1.1203  1.1205  0.0008  0.0013
30/09/16    15:15:00+00:00  1.1205  1.1216  1.1204  1.1216  0.0012  0.0013
30/09/16    15:30:00+00:00  1.1217  1.1222  1.1213  1.1216  0.0008  0.0013
30/09/16    15:45:00+00:00  1.1216  1.1240  1.1216  1.1240  0.0025  0.0015
30/09/16    16:00:00+00:00  1.1239  1.1246  1.1228  1.1242  0.0019  0.0015
30/09/16    16:15:00+00:00  1.1242  1.1251  1.1235  1.1240  0.0016  0.0016
30/09/16    16:30:00+00:00  1.1240  1.1240  1.1234  1.1236  0.0007  0.0014
30/09/16    16:45:00+00:00  1.1237  1.1245  1.1235  1.1238  0.0009  0.0012
30/09/16    17:00:00+00:00  1.1238  1.1239  1.1231  1.1233  0.0008  0.0012
30/09/16    17:15:00+00:00  1.1233  1.1245  1.1232  1.1240  0.0013  0.0012
30/09/16    17:30:00+00:00  1.1240  1.1242  1.1228  1.1230  0.0013  0.0013
30/09/16    17:45:00+00:00  1.1230  1.1230  1.1221  1.1227  0.0009  0.0013
30/09/16    18:00:00+00:00  1.1227  1.1232  1.1227  1.1232  0.0005  0.0012
30/09/16    18:15:00+00:00  1.1232  1.1232  1.1227  1.1227  0.0005  0.0010
30/09/16    18:30:00+00:00  1.1227  1.1231  1.1225  1.1231  0.0006  0.0009
30/09/16    18:45:00+00:00  1.1231  1.1237  1.1230  1.1232  0.0007  0.0008
30/09/16    19:00:00+00:00  1.1232  1.1233  1.1229  1.1231  0.0004  0.0008
30/09/16    19:15:00+00:00  1.1231  1.1234  1.1230  1.1230  0.0004  0.0007
30/09/16    19:30:00+00:00  1.1231  1.1234  1.1230  1.1234  0.0004  0.0007
30/09/16    19:45:00+00:00  1.1233  1.1240  1.1230  1.1239  0.0010  0.0007
30/09/16    20:00:00+00:00  1.1239  1.1242  1.1237  1.1238  0.0005  0.0006
30/09/16    20:15:00+00:00  1.1238  1.1240  1.1235  1.1237  0.0005  0.0006
30/09/16    20:30:00+00:00  1.1237  1.1238  1.1235  1.1235  0.0003  0.0005
30/09/16    20:45:00+00:00  1.1235  1.1236  1.1233  1.1233  0.0003  0.0005
30/09/16    21:00:00+00:00  1.1233  1.1238  1.1233  1.1237  0.0006  0.0005
30/09/16    21:15:00+00:00  1.1237  1.1244  1.1237  1.1242  0.0008  0.0005
30/09/16    21:30:00+00:00  1.1242  1.1243  1.1239  1.1239  0.0004  0.0005
30/09/16    21:45:00+00:00  1.1239  1.1244  1.1236  1.1241  0.0008  0.0006
Asked By: cardycakes

||

Answers:

That’s not the right calculation for TR see – ATR, but here is how I would do it:

Where alpha = 2 / (span+1)

df['ATR'] = df['TR'].ewm(span = 10).mean()

Otherwise you should be able to easily do you’re own smoothing like this:

df['ATR'] = ( df['ATR'].shift(1)*13 + df['TR'] ) / 14

Pandas ewm

Answered By: G.Dep

For anyone else looking on how to do this, here is my answer.

def wwma(values, n):
    """
     J. Welles Wilder's EMA 
    """
    return values.ewm(alpha=1/n, adjust=False).mean()

def atr(df, n=14):
    data = df.copy()
    high = data[HIGH]
    low = data[LOW]
    close = data[CLOSE]
    data['tr0'] = abs(high - low)
    data['tr1'] = abs(high - close.shift())
    data['tr2'] = abs(low - close.shift())
    tr = data[['tr0', 'tr1', 'tr2']].max(axis=1)
    atr = wwma(tr, n)
    return atr
Answered By: Andrew Olson

Just a disclaimer about TR calculation: it actualy is the biggest value of those 3 options:

  1. Present High – Present Low;
  2. abs(Present High – Last Close);
  3. abs(Present Low – Last Close);

You can get it several ways in python, here I present the way I coded:

df['TR'] = [max(tup) for tup in list(zip(df['High'] - df['Low'],
                                        (df['High'] - df['Close'].shift(1)).abs(),
                                        (df['Low']  - df['Close'].shift(1)).abs()))]

I did this in order to avoid creating other columns on my dataframe, so I basicaly created a list of tupples, each tupple containing the 3 possible values listed above, and then created a list of the biggest value of each tupple.

Answered By: P. Pinho

A fully working function:

def ATR(data: pd.DataFrame, window=14, use_nan=True) -> pd.Series:
   
    df_ = data.copy(deep=True)

    df_.loc[:, 'H_L'] = df_['High'] - df_['Low']
    df_.loc[:, 'H_Cp'] = abs(df_['High'] - df_['Close'].shift(1))
    df_.loc[:, 'L_Cp'] = abs(df_['Low'] - df_['Close'].shift(1))
    df_.loc[:, 'TR'] = df_[["H_L", "H_Cp", "L_Cp"]].max(axis=1)
    df_.loc[:, 'ATR'] = df_['TR'].rolling(window).mean()

    for i in range(window, len(df_)):
        df_.iloc[i, df_.columns.get_loc('ATR')] = (((df_.iloc[i - 1, df_.columns.get_loc('ATR')]) * (window - 1)) + df_.iloc[
            i, df_.columns.get_loc('TR')]) / window

    if use_nan:
        df_.iloc[:window, df_.columns.get_loc('ATR')] = np.nan

    return df_['ATR']
Answered By: Nand0san

This answer uses essentially the same logic as the prior correct answer by Andrew. It however avoids copying the dataframe, also avoids duplicate use of shift, and is a bit more DRY. As implemented, it matches the values of TradingView.

import pandas as pd

def rma(s: pd.Series, period: int) -> pd.Series:
    return s.ewm(alpha=1 / period).mean()

def atr(df: pd.DataFrame, length: int = 14) -> pd.Series:
    # Ref: https://stackoverflow.com/a/74282809/
    high, low, prev_close = df['high'], df['low'], df['close'].shift()
    tr_all = [high - low, high - prev_close, low - prev_close]
    tr_all = [tr.abs() for tr in tr_all]
    tr = pd.concat(tr_all, axis=1).max(axis=1)
    atr_ = rma(tr, length)
    return atr_
Answered By: Asclepius

The accepted answer is correct, but can be still reduced to a single line:

ATR = pd.concat([df.High.sub(df.Low), df.High.sub(df.Close.shift()), df.Low.sub(df.Close.shift())], axis=1).max(1).ewm(span=14).mean()

Answered By: mac13k
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.