How to Create Bitcoin Logarithmic Price Scale Using Matplotlib

4 minute read

Get Data

Before we can plot the figure, we need to get the data. Again, I am going to use Catalyst to collect Bitcoin price and volume data from Bitfinex.

Start your Jupyter Notebook or JupyterLab, and create a new file.

First, let us import some modules.

# Make interactive plots in JupyterLab
# If you would like to know more, check this post of mine
# https://0xboz.github.io/blog/how-to-create-interactive-plots-in-jupyterlab/
%matplotlib widget
from catalyst.api import symbol, record
from catalyst import run_algorithm
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib import style
from matplotlib import ticker
import numpy as np
import pandas as pd

Next, we need to define a few variables, which will make our life easier when switching to another trading pair or changing some params.

trading_pair = 'btc_usd'
# Catalyst has collected more than 4 years BTC/USD daily and minute data from Bitfinex
exchange = 'bitfinex'
start = '2015-3-2'
end = '2019-6-25'
# We are using the daily data in this tutorial
frequency = 'daily'
capital_base = 1000
quote_currency = trading_pair.split('_')[1]

As usual, we start with initializing the function and tell Catalyst which trading pair we are going to use.

def initialize(context):
    context.asset = symbol(trading_pair)

Then we deploy handle_data() function and record price, volume and other data on the fly. Please note handle_data() runs every minute, it is recommended to use schedule function instead if your algo doesn’t need that luxury computation. Later you can shorten the time window (say a few weeks) and try it out with frequency = 'minute' and tell me what you have found in case your computer doesn’t crash.

def handle_data(context, data):
    # The last known price and volume of current date/minute and the day/minute before
    if frequency == 'daily':
        previous_price, current_price = data.history(
            context.asset, 'price', 2, '1d')
        volume = data.current(context.asset, 'volume')
    elif frequency == 'minute':
        previous_price, current_price = data.history(
            context.asset, 'price', 2, '1T')
        volume = data.current(context.asset, 'volume')
    # Calculate rate of return
    simple_return = current_price / previous_price - 1
    # Calculate log return
    log_return = np.log(current_price) - np.log(previous_price)
    record(price=current_price, simple_return=simple_return, log_return=log_return, volume=volume)

One more thing, let us set up __name__ == '__main__'.

if __name__ == '__main__':   
    perf = run_algorithm(capital_base=capital_base,
                         data_frequency=frequency,
                         initialize=initialize,
                         handle_data=handle_data,                      
                         exchange_name=exchange,
                         quote_currency=quote_currency,
                         start=pd.to_datetime(start, utc=True),
                         end=pd.to_datetime(end, utc=True))

Awesome! But before we run our algo, we need to ingest the data first.

(venv) catalyst ingest-exchange -x bitfinex -f daily -i btc_usd

Just in case you would like to run it on minute data, don’t forget to run this command.

(venv) catalyst ingest-exchange -x bitfinex -f minute -i btc_usd

Okay, now let us go back to our coding cell and run the script by Shift + Enter or Ctrl + Enter. If you happen to see the following, meaning we can move onto the next step - plotting!

Run catalyst algo

Matplotlib

Since we have already imported matplotlib, let us jump right into plotting this time.

# Plot style and figure size
style.use('fivethirtyeight')
fig = plt.figure(figsize=(12,18))

Okay, now the fun part begins. First, we are going have plot a regular price chart for Bitcoin.

# Price Chart
ax = plt.subplot2grid((20, 1), (0, 0), rowspan=7, colspan=1)
ax.plot(perf.index, perf.price, color='k', linewidth=0.75)
ax.yaxis.set_major_locator(plt.MaxNLocator(10))
ax.yaxis.set_major_formatter(ticker.FormatStrFormatter('$%.2f'))
ax.yaxis.set_minor_formatter(ticker.FormatStrFormatter('$%.2f'))
ax.set_ylim([0, np.max(perf.price) * 1.1])
plt.ylabel('Price', fontsize=10)
plt.yticks(fontsize=7)
title = (' / '.join(trading_pair.split('_'))).upper() + ' ' + frequency.title()
plt.title(title, fontsize=12)
plt.grid(which='minor')
plt.setp(ax.get_xticklabels(), visible=False)
bbox_props = dict(boxstyle='round',fc='w', ec='k',lw=0.25)
ax.annotate(str(perf.price[-1]), (perf.index[-1], perf.price[-1]),
            xytext = (perf.index[-1] + pd.Timedelta('14d'), perf.price[-1]), 
            bbox=bbox_props, fontsize=5)

And here is the volume part.

# Volume in Price Chart
axv = ax.twinx()
axv.set_ylim([0, np.max(perf.volume) * 2])
axv.bar(perf.index, perf.volume, color='#0079a3', alpha=0.4)
plt.grid(False)
plt.setp(axv.get_yticklabels(), visible=False)
Bitcoin regular price chart

Thanks to matplotlib, it is quite easy convert price to logarithmic scale from the code above.

# Log Price Chart
ax1 = plt.subplot2grid((20, 1), (7, 0), rowspan=7, colspan=1, sharex=ax)
ax1.semilogy(perf.index, perf.price, basey=10, color='k', linewidth=0.75)
ax1.yaxis.set_major_formatter(ticker.FormatStrFormatter('$%.2f'))
ax1.yaxis.set_minor_formatter(ticker.FormatStrFormatter('$%.2f'))
ax1.set_ylim([np.min(perf.price) * 0.9, np.max(perf.price) * 1.1])
plt.ylabel('Log Price', fontsize=10)
ax1.tick_params(axis='y', which='both', labelsize=6)
plt.grid(which='minor')
plt.setp(ax1.get_xticklabels(), visible=False)
bbox_props = dict(boxstyle='round',fc='w', ec='k',lw=0.25)
ax1.annotate(str(perf.price[-1]), (perf.index[-1], perf.price[-1]),
            xytext = (perf.index[-1] + pd.Timedelta('14d'), perf.price[-1]), 
            bbox=bbox_props, fontsize=5)
plt.title(title, fontsize=12)

# Volume in Log Price Chart
ax1v = ax1.twinx()
ax1v.set_ylim([0, np.max(perf.volume) * 2])
ax1v.bar(perf.index, perf.volume, color='#0079a3', alpha=0.4)
plt.grid(False)
plt.setp(ax1v.get_yticklabels(), visible=False)

Here is what it looks like.

Bitcoin log price chart

As a bonus, let us re-visit the discussion about Stationarity and Differencing on crypto trading data. The code below is going to generate daily log return time series chart.

# Log Return Chart
ax2 = plt.subplot2grid((20, 1), (14, 0), rowspan=6, colspan=1, sharex=ax)
ax2.plot(perf.index, perf.log_return, color='g', linewidth=0.25)
ax2.xaxis.set_major_locator(plt.MaxNLocator(7))
ax2.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.3f'))
ax2.set_ylim([-0.3, 0.3])
ax2.yaxis.set_minor_locator(ticker.AutoMinorLocator(2))
plt.ylabel('Log Return', fontsize=10)
plt.yticks(fontsize=7)
plt.xticks(fontsize=10, rotation=0)
plt.grid(which='minor')
xfmt = mdates.DateFormatter('%d-%m-%y')
ax2.xaxis.set_major_formatter(xfmt)
Bitcoin daily log return

From the chart above, we can observe the mean and variance are relatively constant over time. In the upcoming post, I will cover how to utilize this data with integration of order zero I(0) for our trading model.

We still need a few more lines of code to adjust those sub plots.

plt.subplots_adjust(left=0.1, bottom=0.05, right=0.94, top=0.96, wspace=0.2, hspace=1)
plt.show()

Send me a message if you have any questions/comments. Feel like stopping by and say hi? Let us talk more about crypto and quantitative trading over there. Here is the discord invite link.

Stay calm and happy trading!