The cryptocurrency market is very volatile. How do you take advantage and earn extra income from it? You buy low and sell high. Easy to say, but what would be your first step? Which trading pairs would you trade?
A strategy that comes to mind is range trading. When a cryptocurrency trading pair has been moving sideways for a couple of days, you identify a range at which you will buy and sell at over a short period of time. Let’s say you buy an asset at $1 and sell it at $1.10. You believe it is going to trade again at $1 then rise again to $1.10. You repeat this process until you think the price will no longer trade in this range.
There is a volatility indicator designed to determine if the market is choppy (trading sideways) or not choppy (trading within a trend in either direction). It is called the Choppiness Index (CHOP). CHOP is not meant to predict future market direction, it is a metric to be used for defining the market’s trendiness only.
The focus of this tutorial is to automate the computation for the Choppiness Index of all the cryptocurrency trading pairs of the exchange of your choice. Values range between 0 and 100, with low values indicating a strong trend and high values signaling consolidation. If the Choppiness Index for the pair is equal to 61.8 or higher, they will be included in the list of pairs that you can potentially place trades on. This does NOT include identifying significant price levels that you can use to enter and exit trades.
To calculate Choppinness Index:
100 * LOG10( SUM(ATR(1), n) / ( MaxHi(n) - MinLo(n) ) ) / LOG10(n) n = User defined period length. LOG10(n) = base-10 LOG of n ATR(1) = Average True Range (Period of 1) SUM(ATR(1), n) = Sum of the Average True Range over past n bars MaxHi(n) = The highest high over past n bars
Here is a sample web application:
The Open Chart link opens a new web page displaying a simple chart of Choppiness Index along with a line chart of the cryptocurrency trading pair’s closing prices.
You may compare the application’s charts side by side with TradingView chart, adding the indicator called “Choppiness Index (CHOP)”
Below are the sections of this tutorial:
- Part 1 Setup API Keys in your Cryptocurrency Exchange (Binance US and Kraken)
- Part 2 Setup Environment Variables
- Part 3 Setup Python project workspace
- Part 4 Writing the Python programs
- Part 5 Running the application
Part 1 – Setup API Keys in your Cryptocurrency Exchange
Part 1A – Setting up API Keys in Binance US
Step 1. Login to your Binance US account.
Step 2. Click “API Management” under the email address drop-down.
Step 3. Enter the API key label. Click “Create” when done.
Step 4. An email is sent to confirm that you created the API key.
Step 5. Open your email. Click “Confirm API Key Creation” button.
Step 6. The newly-created API key will be displayed. Make sure to save the Secret Key somewhere safe. It will disappear after refreshing the page.
Part 1B – Setting up API Keys in Kraken
Step 1. Login to your Kraken account in https://www.kraken.com
Step 2. Click on your name on the top right panel of the website. Select “API” from the list.
Step 3. Click the “Add key” link.
Step 4. Select at least one key permission. Then click the “Generate key” button.
Step 5. The API key and Private key will be displayed. Save these information in a safe place.
PART 2 – Setup Environment Variables
Step 1. If you are using Windows, select Control Panel -> System and Security -> System -> Advanced system settings -> Environment Variables..
Step 2. Click “New…” In the Variable Name and Variable Value fields, enter these data:
Variable name | Notes |
BINANCE_US_API_KEY | Copy the API Key from Part 1A Step 6 |
BINANCE_US_SECRET_KEY | Copy the Secret Key from Part 1A Step 6 |
KRAKEN_API | Copy the API key from Part 1B Step 5 |
KRAKEN_PRIVATE | Copy the Private key from Part 1B Step 5 |
PART 3 – Setup Python project workspace
Step 1. Install Pycharm Community Edition in https://www.jetbrains.com/pycharm/download/
This tutorial uses the latest stable release pycharm-community-2021.1.2.exe
Step 2. Open Pycharm. Create New Project with a Virtual environment. The latest Pycharm installer will always have new virtual environment installed.
Step 3. Create new file with filename requirements.txt. Enter the following library names
ccxt
pandas
datetime
requests
flask
numpy
matplotlib
Step 4. Select “Terminal” on the bottom panel. This will launch a command line. Execute the command to install the libraries listed in requirements.txt
pip3 install -r requirements.txt
PART 4 – Writing the Python programs
Step 1. Create new Python files called config.py. This will contain most of the settings for the application.
config.py
import os EXCHANGE_USED = "BINANCEUS" BINANCE_US_API_KEY = os.environ.get('BINANCE_US_API_KEY') BINANCE_US_SECRET_KEY = os.environ.get('BINANCE_US_SECRET_KEY') KRAKEN_API = os.environ.get('KRAKEN_API') KRAKEN_PRIVATE = os.environ.get('KRAKEN_PRIVATE') CHART_TIMEFRAME = '1d' CHOPPINESS_DAYS = 14 LIMIT = CHOPPINESS_DAYS * 24 START_DATE = '20220101' RANGING_INDEX = 61.8 TRENDING_INDEX = 38.2 EXCLUDE_CURRENCY = ['GBP', 'JPY', 'EUR', 'CAD', 'AUD', 'ZUSD', 'CHF'] PRICE_PRECISION = ".8f" CHOP_INDEX_PRECISION = 2
Step 2. Create a new file called app.py. It will have all logic from connecting to the cryptocurrency exchange, to loading the exchange’s trading pairs, calculating CHOP indices and categorizing the trading pair to Ranging, Trending or neither of the two. This file also handles the plotting of chart into HTML file.
app.py
from flask import Flask, render_template
import ccxt
import config
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from io import BytesIO
import base64
import datetime, time
from pytz import timezone
app = Flask(__name__)
class Asset:
def __init__(self, symbol, chop_indicator, min_range_price, max_range_price, chop_chart_url):
self.symbol = symbol
self.chop_indicator = chop_indicator
self.min_range_price = min_range_price
self.max_range_price = max_range_price
self.chop_chart_url = chop_chart_url
@app.route('/', methods=['GET'])
def get_sideways_crypto_pairs():
print(f'default exchange {config.EXCHANGE_USED}')
return get_sideways_crypto_pairs_exchange(config.EXCHANGE_USED)
# If you want to override the default exchange defined in config.py, pass the exchange name in the URL
@app.route('/cryptoPairsSideways/<param_exchange>', methods=['GET'])
def get_sideways_crypto_pairs_exchange(param_exchange):
start_time = time.time()
if param_exchange is not None:
config.EXCHANGE_USED = param_exchange
print(config.EXCHANGE_USED)
exchange = init_exchange(param_exchange)
if exchange is None:
return f'Unable to connect to the Cryptocurrency Exchange settings for {param_exchange}.'
markets = exchange.load_markets()
symbols = [markets[market]['id'] for market in markets]
exclude = config.EXCLUDE_CURRENCY
symbols = [symbol for symbol in symbols if all(excludes not in symbol for excludes in exclude)]
ranging_list = []
not_ranging_list = []
neither_list = []
ctr = 0
total = len(symbols)
for symbol in symbols:
print(f"{ctr} of {total} {symbol}")
ctr += 1
data = get_exchange_symbol_data(exchange, symbol)
if data is not None and data.size > 0:
if data[-1:]['CHOP'].values[0] >= config.RANGING_INDEX:
asset = Asset(symbol, round(data[-1:]['CHOP'].values[0], config.CHOP_INDEX_PRECISION),
format(data[-config.LIMIT:]['Close'].min(), config.PRICE_PRECISION),
format(data[-config.LIMIT:]['Close'].max(), config.PRICE_PRECISION),
f"/choppinessChart/{param_exchange}/{symbol}")
ranging_list.append(asset)
elif data[-1:]['CHOP'].values[0] >= config.TRENDING_INDEX:
asset = Asset(symbol, round(data[-1:]['CHOP'].values[0], config.CHOP_INDEX_PRECISION),
format(data[-config.LIMIT:]['Close'].min(), config.PRICE_PRECISION),
format(data[-config.LIMIT:]['Close'].max(), config.PRICE_PRECISION),
f"/choppinessChart/{param_exchange}/{symbol}")
not_ranging_list.append(asset)
else:
asset = Asset(symbol, round(data[-1:]['CHOP'].values[0], config.CHOP_INDEX_PRECISION),
format(data[-config.LIMIT:]['Close'].min(), config.PRICE_PRECISION),
format(data[-config.LIMIT:]['Close'].max(), config.PRICE_PRECISION),
f"/choppinessChart/{param_exchange}/{symbol}")
neither_list.append(asset)
html = "<html><head><title>Crypto Trading Pairs on Sideways</title>"
html += "<link rel='stylesheet' href='/static/sorta.css'>"
html += "<script src='/static/sort-table.js'></script></head><body>"
time_now = str(datetime.datetime.now(timezone('EST')))[:-7]
html += time_now
html += print_criteria()
html += print_results(ranging_list, not_ranging_list, neither_list)
html += "</body></html>"
end_time = time.time()
print(f'time total:{end_time - start_time}')
return html
def init_exchange(param_exchange):
exchange = None
if param_exchange == "BINANCEUS":
exchange = ccxt.binanceus({
'apiKey': config.BINANCE_US_API_KEY,
'secret': config.BINANCE_US_SECRET_KEY
})
elif param_exchange == "KRAKEN":
exchange = ccxt.kraken({
'apiKey': config.KRAKEN_API,
'secret': config.KRAKEN_PRIVATE,
})
return exchange
def get_exchange_symbol_data(exchange, symbol):
from datetime import datetime
since = int(datetime(int(config.START_DATE[:4]), int(config.START_DATE[4:6]),
int(config.START_DATE[6:])).timestamp() * 1000)
data = None
try:
klines = exchange.fetch_ohlcv(symbol, config.CHART_TIMEFRAME, since, config.LIMIT)
if len(klines) > config.CHOPPINESS_DAYS:
data = pd.DataFrame(klines, columns=['timestamp', 'Open', 'High', 'Low', 'Close', 'Volume'])
data['timestamp'] = pd.to_datetime(data['timestamp'], unit='ms')
data = data.dropna()
data['CHOP'] = get_choppiness_index(data['High'], data['Low'], data['Close'], config.CHOPPINESS_DAYS)
except Exception as e:
pass
return data
def get_choppiness_index(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()
highest = high.rolling(lookback).max()
lowest = low.rolling(lookback).min()
choppiness_index = 100 * np.log10((atr.rolling(lookback).sum()) / (highest - lowest)) / np.log10(lookback)
return choppiness_index
def print_criteria():
html = "<br/><br/><b>SETTINGS</b>"
html += f"<table border='1'><tr><td>RANGING INDEX</td><td>{config.RANGING_INDEX}</td></tr>" \
f"<tr><td>TRENDING INDEX</td><td>{config.TRENDING_INDEX}</td></tr>" \
f"<tr><td>CHART TIMEFRAME</td><td>{config.CHART_TIMEFRAME}</td></tr>" \
f"<tr><td>CRYPTO EXCHANGE USED</td><td>{config.EXCHANGE_USED}</td></tr>" \
f"<tr><td>IGNORED QUOTE CURRENCY</td><td>{config.EXCLUDE_CURRENCY}</td></tr>" \
"</table><br/>"
return html
def print_results(ranging_list, not_ranging_list, neither_list):
html = print_table(ranging_list, "Ranging (Sideways Crypto Pairs)")
html += print_table(not_ranging_list, "Trending Pairs")
html += print_table(neither_list, "Neither Ranging nor Trending Pairs")
return html
def print_table(list, label):
ctr = 0
html = f"<br><b>{label}</b><br>"
html += "<table border='1' class='js-sort-table'><thead><tr><th class='js-sort-number'>#</th>" \
"<th class='js-sort-string'><b>Pair</b></th>" \
"<th class='js-sort-number'><b>Choppiness Index</b></th>" \
"<th class='js-sort-number'><b>Min. Range Price</b></th>" \
"<th class='js-sort-number'><b>Max. Range Price</b></th>" \
"<th>Choppiness Indicator Chart</th></tr></thead>"
for item in list:
ctr += 1
html += f"<tr><td>{ctr}</td> " \
f"<td>{item.symbol}</td>" \
f"<td align='right'>{item.chop_indicator}</td>" \
f"<td align='right'>{item.min_range_price}</td>" \
f"<td align='right'>{item.max_range_price}</td>" \
f"<td><a href='{item.chop_chart_url}' target='_blank'>Open Chart</a></td></tr>"
html += "</table>"
return html
@app.route('/choppinessChart/<param_exchange>/<symbol>', methods=['GET'])
def display_choppiness_chart(param_exchange, symbol):
exchange = init_exchange(param_exchange)
data = get_exchange_symbol_data(exchange, symbol)
ax1 = plt.subplot2grid((11, 1,), (0, 0), rowspan=5, colspan=1)
ax2 = plt.subplot2grid((11, 1,), (6, 0), rowspan=4, colspan=1)
ax1.plot(data['Close'], linewidth=1.5, color='#2196f3')
ax1.set_title(f'{symbol} ({exchange}) CLOSING PRICES')
ax2.plot(data['CHOP'], linewidth=1.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(f'{symbol} CHOPPINESS INDEX')
img_bytes = BytesIO()
plt.savefig(img_bytes, format='png')
plt.close()
plot_url = base64.b64encode(img_bytes.getvalue()).decode("utf-8").replace("\n", "")
return render_template('plot.html', plot_url=plot_url)
Step 3. Create a directory called templates, then add a new HTML file called plot.html. This will contain the template for displaying the popup window that contains the line charts for the stock’s prices and Choppines Index
templates/plot.html
<!doctype html> <head meta charset="utf-8"> <title>Choppiness Index</title></head> <section> <img src="data:image/png;base64, {{ plot_url }}"> </section>
PART 5. Running the application
Step 1. Select “Terminal” on the bottom panel.
flask run
Step 2. Open a browser and enter http://127.0.0.1/ This is load the default cryptocurrency exchange defined in config.py, which is BINANCEUS.
You may switch to another cryptocurrency exchange without restarting your server, by adding /cryptoPairsSideways/<exchangeName>
Example: hhttp://127.0.0.1:5000/cryptoPairsSideways/KRAKEN
References:
Detecting Ranging and Trending Markets with Choppiness Index in Python
Range Trading Crypto Strategy: How to Use and Make Profit