Hacking 1-Minute Cryptocurrency Candlesticks: (2) Custom Candlestick Charts in Plotly

In today’s Part 2, as the first step towards crypto-series analysis, we will present the Python code allowing for price-series visualisation by developing a custom candlestick chart function. As the final product, we will be able to present our data as follows:

candlestick chart plotly python quantatrisk

A clear and informative time-series visualisation is often a challenge. Especially this is true when it comes to candlestick charts in Python. Searching the Web for a perfect solution may bring you to the Plotly package which stores in its arsenal the corresponding function allowing for easy charting (see some examples here).

The final shape (more precisely: its vertical span) depends on the recorded values of highest and lowest prices within the selected time-interval of your interest (e.g. 1 minute, 1 day, etc.). In particular, if the highest price has been greater than the open or close price, the top whisker is displayed, denoted by a thin vertical line. This was the main reason why these charts started to resemble a collection of “candles” giving a rise to the naming as we know today.

For the traders and algo-traders, the OHLC price patterns are the source of valuable information. They allow for a quick, visual inspection of local or global trends, swings in traded directions, inner volatility within a selected time-frame. When observed “live” on the computer screen, the current candle (under construction) continuously changes its shape while all the past candles are “closed”. For the former, in other words, we can track the price movements but the history of them is gone once the candle is formed and closed. One solution to that problem is to increase the time resolution of your candlestick chart, e.g. from 1 hour to 1 minute candles.

However, what if 1 minute time resolution is not enough? How can we visualise sub-minute price variations?

In Part 1, we discussed how one may utilise Binance’s live data streams and start recording them for any cryptocurrency. We used Bitcoin as an example. In Section 2 we presented a code for stream recording and saving the OHLC prices and candlestick data in a .json file while in Section 3 we employed a default Plotly’s function of plotly.graph_objects.Candlestick to visualise the chart.

Luckily, the captured data stream is continuous allowing for the analysis of “sub-minute” price dynamics and its behaviour:

i.e. to provide a user with an ability to re-draw candlesticks in the way that controls the time-width of the candle’s main body.

1. Problem of Default Width of a Candlestick in Plotly

A counterintuitive aspect of using the default functionality like the one of delivered by class plotly.graph_objects.Candlestick(.) is, in fact, the lack of candlestick’s width adjustments. A closer inspect of the documentation reveals that only one feature available, i.e.


What it does is it increases or decreases the body’s and whiskers’ width. Unfortunately, there is no direct way to sync that width to account for the actual time-dependent boundaries of the candles themselves, i.e. where the left wall begins and where the right wall ends. To overcome this drawback, we need a custom function.

2. Designing a Custom Candlestick Chart

As an entry point, we might have a set of information as presented in the table above. The basic requirement would be what is the candlestick start and end time followed by OHLC price points. This will allow us to draw each new candle. Let’s recall the code from Part 1, beginning from reading the streamed cryptocurrency data:

# Hacking 1-Minute Cryptocurrency Candlesticks: (2) Custom Candlesticks Charts in Plotly
# (c) 2022 by QuantAtRisk

import numpy as np
import pandas as pd
import json
import datetime
import plotly.graph_objects as go  # for candlestick charts
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import plotly.express as px

# constants
PROJECT_PATH = '/Users/pawel/Projects/vs'    # use your project path here
DATA_PATH = '/data/'
DATA_FILENAME = 'btc_usdt_1m_20220401_2250'  # .json

# color codes for plotly
whiteP, blackP, redP, greyP = '#FFFFFF', '#000000', '#B54D47', 'rgb(150,150,150)'

# supporting function
def convtime(t, corr=True):
    if corr:
        t = t / 1000
    return datetime.datetime.fromtimestamp(t).strftime("%Y-%m-%d %H:%M:%S")


# reading in the database
data = json.load( open(PROJECT_PATH + DATA_PATH + DATA_FILENAME + '.json') )


# data processing
# converting data from json to pandas' DataFrame
#
df = pd.DataFrame(columns=['Timestamp', 'Event_Time', 'Candle_Start', 'Candle_End', 'Freq', 'Open', 'High', 'Low', 'Close', 'Final'])

for k in data.keys():
    ts = data[k]['E']  # timestamp
    o, h, l, c = float(data[k]['k']['o']), float(data[k]['k']['h']),  float(data[k]['k']['l']), float(data[k]['k']['c'])
    freq, final = data[k]['k']['i'], data[k]['k']['x']
    event_time = convtime(ts)
    c_start, c_end = convtime(data[k]['k']['t']), convtime(data[k]['k']['T'])
    df.loc[len(df)] = [ts, event_time, c_start, c_end, freq, o, h, l, c, final]

df.set_index(['Timestamp'], drop=True, inplace=True)

where btc_usdt_1m_20220401_2250.json file is available for download at the end of this article. By displaying the first 30 lines of df DataFrame, we will end up with table already presented and referred to above.

The standard use of Plotly’s Candlestick function which we have coded last time was:

# standard Plotly's Candlestick function calling
#

# plot only final candlestick values of OHLC
dff = df[df['Final'] == True]

# chart
fig = go.Figure(data=go.Candlestick(x     = dff.iloc[:,0], 
                                    open  = dff.iloc[:,4], 
                                    high  = dff.iloc[:,5],
                                    low   = dff.iloc[:,6],
                                    close = dff.iloc[:,7],)
               )
fig.update(layout_xaxis_rangeslider_visible=False)
fig.update_layout(plot_bgcolor=whiteP, width=900)
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor=greyP)
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor=greyP)
fig.update_yaxes(title_text='BTC/USDT')
fig.update_xaxes(title_text='Date and Time (UTC+2h)')
#fig.update_xaxes(range=['2022-04-02 20:44:00', '2022-04-02 21:14:00'])
#fig.update_yaxes(range=[45.6e3, 46e3])
    
# update line and fill colors
cs = fig.data[0]
cs.increasing.fillcolor, cs.increasing.line.color = blackP, blackP
cs.decreasing.fillcolor, cs.decreasing.line.color = redP, redP

To address the time-width dependent shape of the candlesticks, the solution comes with a help of designing two separates traces and next merging them together. Trace #1 will be, in fact, a multi-trace for each candlestick separately. The solution we apply here is fairly simple. We are drawing a rectangle to define the candle’s body both in height (Low to High) and in width (start and end time). Each candle’s traces is added in a for loop where we select the indexes of these candlesticks we are interested to display. In the code below we use Python’s range(.) function but it can be replaced by the custom-made list.

# Custom Candlestick Charts
#

from plotly.subplots import make_subplots

# data selection for the chart
dff = df[df['Final'] == True]  # dataset only for "closed" candlesticks
dff2= df[df['Final'] != True]  # intermittent data


fig = make_subplots()

# Trace 1 (Multi-trace)
#
for i in range(2, 20):  # select a few first candlesticks to be drawn (indexes)
    
    # draw High-Low vertical line (whisker)
    tmp_x = [pd.to_datetime(dff.iloc[i,1])+pd.DateOffset(seconds=30), 
             pd.to_datetime(dff.iloc[i,1])+pd.DateOffset(seconds=30)]
    tmp_y = [dff.iloc[i,5], dff.iloc[i,6]]
    if dff.iloc[i,4] >= dff.iloc[i,7]:
        fcolor = redP  # red candle if open >= close price
    else:
        fcolor = blackP # black cangle if close > open price
    tmp_trace = go.Scatter(x = tmp_x,
                           y = tmp_y, line=dict(color=fcolor, width=2), 
                           mode ='lines', 
                           name = str(i),
                           showlegend=False,
                           fill='toself', fillcolor=fcolor, opacity=0.7,
                          )
    fig.add_trace(tmp_trace)
    
    # draw the candlestick body
    tmp_x = [dff.iloc[i,1], dff.iloc[i,1], dff.iloc[i,2], dff.iloc[i,2], dff.iloc[i,1]]
    tmp_y = [dff.iloc[i,4], dff.iloc[i,7], dff.iloc[i,7], dff.iloc[i,4], dff.iloc[i,4]]
    if dff.iloc[i,4] >= dff.iloc[i,7]:
        fcolor = redP
        opa = 0.6
    else:
        fcolor = blackP
        opa = 0.3
    tmp_trace = go.Scatter(x = tmp_x,
                           y = tmp_y, line=dict(color=fcolor), 
                           mode ='lines', 
                           name = str(i),
                           showlegend=False,
                           fill='toself', fillcolor=fcolor, opacity=opa
                          )
    fig.add_trace(tmp_trace)
    
# Trace 2
#
trace2 = go.Scatter(x = dff2.iloc[:,0], 
                    y = dff2.iloc[:,7], line=dict(color='#000000', width=2),
                    opacity=1, showlegend=False
                   )
fig.add_trace(trace2) # ,secondary_y=False)


fig.update_layout(plot_bgcolor=whiteP, width=900)
fig.update(layout_xaxis_rangeslider_visible=False)
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor=greyP)
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor=greyP)
fig.update_yaxes(title_text='BTC/USDT')
fig.update_xaxes(title_text='Date and Time (UTC+2h)')
fig.update_xaxes(range=['2022-04-02 20:44:00', '2022-04-02 21:04:00'])
fig.update_yaxes(range=[45.7e3, 45.95e3])

fig.show()

Trace 2 is a simple addition of close-price (last price) streamed in the 1-min interval, i.e. between candlestick’s official start and end times, inclusive. As promised, the final outcome is generated in that process:

Feel free to adopt this code for the needs of your own research!

 

DOWNLOADABLES
     btc_usdt_1m_20220401_2250.json

2 comments
Leave a Reply

Your email address will not be published. Required fields are marked *