Detecting Ranging and Trending Markets with Choppiness Index in Python

An extremely useful indicator to keep yourself away from a pitfall

Nikhil Adithyan
May 16 · 11 min read
Source

Introduction

Every technical indicator out there is simply great and unique in its own perspective and usages. But, most of’em never fails to fall in one pitfall which is nothing but the ranging markets. What is a ranging market? Ranging markets are markets that show no trend or momentum but move back and forth between specific high and low price ranges (these markets are also called choppy, sideways, flat markets). Technical indicators are prone to revealing false entry and exit points while markets are ranging. Fortunately, we have a set of indicators that are designed particularly to observe whether a market is ranging or not. Such indicators are called volatility indicators.

Volatility and ATR

Before jumping on to exploring Choppiness Index, it is essential to have some basic idea on two important concepts which is nothing but Volatility and Average True Range (ATR). Volatility is the measure of the magnitude of price variation or dispersion. Higher the volatility, higher the risk, and vice-versa. People having expertise in this concept will have the possibility to hold a tremendous edge in the market. We have tons of tools to calculate the Volatility of a market but none can achieve a hundred percent accuracy in measuring it but, there are some that have the potential to calculate with more accuracy. One such tool is the Average True Range, shortly known as ATR.

MAX [ {HIGH - LOW}, {HIGH - P.CLOSE}, {P.CLOSE - LOW} ]where,
MAX = Maximum values
HIGH = Market High
LOW = Market Low
P.CLOSE = Previous market close
ATR 14 = SMA 14 [ TR ]where,
ATR 14 = 14 Period Average True Range
SMA 14 = 14 Period Simple Moving Average
TR = True Range

Choppiness Index

Choppiness Index is a volatility indicator that is used to identify whether a market is ranging or trending. The characteristics of the Choppiness Index are almost similar to ATR. It is a lagging and non-directional indicator whose values rise when the market is bound to consolidate and decreases in value when the market shows high momentum or price actions. The calculation of the Choppiness Index involves two steps:

  • Choppiness Index calculation: To calculate the Choppiness Index with a traditional setting of 14 as the lookback period is calculated by first taking log 10 of the value received by dividing the 14-day total of the previously calculated ATR 1 by the difference between the 14-day highest high and 14-day lowest low. This value is then divided by log 10 of the lookback period and finally multiplied by 100. This might sound confusing but will be easy to understand once you see the representation of the calculation:
CI14 = 100 * LOG10 [14D ATR1 SUM/(14D HIGHH - 14D LOWL)] / LOG10(14)where,
CI14 = 14-day Choppiness Index
14D ATR1 SUM = 14-day sum of ATR with 1 as lookback period
14D HIGHH = 14-day highest high
14D LOWL - 14-day lowest low

Implementation in Python

Our process starts with importing the essential packages into our python environment. Then, we will be pulling the historical stock data of Tesla using an API provided by twelvedata.com (not an affiliate link). After that, we will build the Choppiness Index from scratch.

Step-1: Importing Packages

Importing the required packages into the python environment is a non-skippable step. The essential packages are going to be Pandas to work with data, NumPy to work with arrays and for complex functions, Matplotlib for plotting purposes, and Requests to make API calls.

import pandas as pd
import requests
import matplotlib.pyplot as plt
import numpy as np

plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (20, 10)

Step-2: Extracting data from twelvedata.com

In this step, we are going to pull the historical stock data of Tesla using an API endpoint provided by twelvedata.com. Before that, a note on twelvedata.com: Twelve Data is one of the leading market data providers having an enormous amount of API endpoints for all types of market data. It is very easy to interact with the APIs provided by Twelve Data and has one of the best documentation ever. Also, ensure that you have an account on twelvedata.com, only then, you will be able to access your API key (vital element to extract data with an API).

def get_historical_data(symbol, start_date):
api_key = 'YOUR API KEY'
api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
raw_df = requests.get(api_url).json()
df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
df = df[df.index >= start_date]
df.index = pd.to_datetime(df.index)
return df

tsla = get_historical_data('TSLA', '2020-01-01')
tsla
Image by Author

Step-3: Choppiness Index Calculation

In this step, we are going to calculate the values of the Choppiness Index with 14 as the lookback period using the Choppiness Index formula we discussed before.

def get_ci(high, low, close, lookback):
tr1 = pd.DataFrame(high - low).rename(columns = {0:'tr1'})
tr2 = pd.DataFrame(abs(high - close.shift(1))).rename(columns = {0:'tr2'})
tr3 = pd.DataFrame(abs(low - close.shift(1))).rename(columns = {0:'tr3'})
frames = [tr1, tr2, tr3]
tr = pd.concat(frames, axis = 1, join = 'inner').dropna().max(axis = 1)
atr = tr.rolling(1).mean()
highh = high.rolling(lookback).max()
lowl = low.rolling(lookback).min()
ci = 100 * np.log10((atr.rolling(lookback).sum()) / (highh - lowl)) / np.log10(lookback)
return ci

tsla['ci_14'] = get_ci(tsla['high'], tsla['low'], tsla['close'], 14)
tsla = tsla.dropna()
tsla
Image by Author

Using Choppiness Index

The values of the Choppiness Index bound between 0 to 100, hence acting as a range-bound oscillator. The closer the values to 100, the higher the choppiness and vice-versa. Usually, two levels are constructed above and below the Choppiness Index plot which is used to identify whether a market is ranging or trending. The above level is usually plotted at a higher threshold of 61.8 and if the values of the Choppiness Index are equal to or above this threshold, then the market is considered to be ranging or consolidating. Likewise, the below level is plotted at a lower threshold of 38.2 and if the Choppiness Index has a reading of or below this threshold, then the market is considered to be trending. The usage of the Choppiness Index can be represented as follows:

IF CHOPPINESS INDEX >= 61.8 --> MARKET IS CONSOLIDATING
IF CHOPPINESS INDEX <= 38.2 --> MARKET IS TRENDING
ax1 = plt.subplot2grid((11,1,), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1,), (6,0), rowspan = 4, colspan = 1)
ax1.plot(tsla['close'], linewidth = 2.5, color = '#2196f3')
ax1.set_title('TSLA CLOSING PRICES')
ax2.plot(tsla['ci_14'], linewidth = 2.5, color = '#fb8c00')
ax2.axhline(38.2, linestyle = '--', linewidth = 1.5, color = 'grey')
ax2.axhline(61.8, linestyle = '--', linewidth = 1.5, color = 'grey')
ax2.set_title('TSLA CHOPPINESS INDEX 14')
plt.show()
Image by Author

Final Thoughts!

After a long process of theory and coding, we have successfully gained some understanding of what the Choppiness Index is all about and its calculation and usage. It might seem to be a simple indicator but it’s very useful while trading and saves you from depleting your capital. Since the Choppiness Index is very lagging, ensure that you’re cautious than ever while using it for trading purposes. Also, you should not completely rely just on the Choppiness Index for generating entry and exit points but can be used as a filter to your actual trading strategy. There are two things to remember while using the Choppiness Index in the real-world market:

  • Backtesting: Drawing conclusions by just backtesting the algorithm in one asset or so won’t be effective and sometimes can even lead to unexpected turns. The real-world market doesn't work the same every time. In order to make better trades, try backtesting the trading strategy with different assets and modify the strategy if needed.

Full code:

import pandas as pd
import requests
import matplotlib.pyplot as plt
import numpy as np

plt.style.use('fivethirtyeight')
plt.rcParams['figure.figsize'] = (20, 10)

def get_historical_data(symbol, start_date):
api_key = 'YOUR API KEY'
api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
raw_df = requests.get(api_url).json()
df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
df = df[df.index >= start_date]
df.index = pd.to_datetime(df.index)
return df

tsla = get_historical_data('TSLA', '2020-01-01')
print(tsla)

def get_ci(high, low, close, lookback):
tr1 = pd.DataFrame(high - low).rename(columns = {0:'tr1'})
tr2 = pd.DataFrame(abs(high - close.shift(1))).rename(columns = {0:'tr2'})
tr3 = pd.DataFrame(abs(low - close.shift(1))).rename(columns = {0:'tr3'})
frames = [tr1, tr2, tr3]
tr = pd.concat(frames, axis = 1, join = 'inner').dropna().max(axis = 1)
atr = tr.rolling(1).mean()
highh = high.rolling(lookback).max()
lowl = low.rolling(lookback).min()
ci = 100 * np.log10((atr.rolling(lookback).sum()) / (highh - lowl)) / np.log10(lookback)
return ci

tsla['ci_14'] = get_ci(tsla['high'], tsla['low'], tsla['close'], 14)
tsla = tsla.dropna()
print(tsla)

ax1 = plt.subplot2grid((11,1,), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1,), (6,0), rowspan = 4, colspan = 1)
ax1.plot(tsla['close'], linewidth = 2.5, color = '#2196f3')
ax1.set_title('TSLA CLOSING PRICES')
ax2.plot(tsla['ci_14'], linewidth = 2.5, color = '#fb8c00')
ax2.axhline(38.2, linestyle = '--', linewidth = 1.5, color = 'grey')
ax2.axhline(61.8, linestyle = '--', linewidth = 1.5, color = 'grey')
ax2.set_title('TSLA CHOPPINESS INDEX 14')
plt.show()

CodeX

Everything connected with Tech & Code