Using a charting library to overlay Volume Profile on a Candlestick chart in Python

Viewed 4558

I would like to plot Volume Profile on a candlestick chart in python, that would result in something like this.

CandleStickwithVolumeProfile

My main ohlc data would be in a pandas dataframe.

Date,       Open,  High,  Low,   Close
2019-10-18, 54.09, 54.62, 53.35, 53.78
2019-10-17, 52.99, 54.16, 52.62, 53.93
2019-10-16, 52.92, 53.74, 52.51, 53.36

Then my Volume information would be in another dataframe like this.

Price, Volume
54.75, 150
54.50, 135
54.25, 140
54.00, 140
53.75, 125
53.50, 145
53.25, 130
53.00, 135
52.75, 155
52.50, 150

Iv'e tried in about every library I know, Matplotlib, Plotly, Bokeh. Iv'e tried simply plotting a bar chart beside a candlestick but the scaling usually is off. I would be interested in used any of the standard charting libraries in python that would produce this result in a fairly simple way. Hopefully someone here knows of some way to do this.

2 Answers

Well, I decided to dig deep in the plotly documentation and see if I could find a way to do this. Turns out it's not that big of a deal. I'm beginning to like Plotly more and more.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Oct 23 22:17:44 2019

@author: TysonU
"""
from plotly.offline import plot
import plotly.graph_objects as go
import random
import pandas as pd

#Create random OHLC and Volume
high = 40
low = 20
dev = 1
days = 100

fake_market = []
for each in range(days):
    ohlc = []
    ohlc.append(each)
    if each == 0:
        o = random.randrange(low, high)
        ohlc.append(o)
    else:
        ohlc.append(c) #I know
    h = random.randrange(o, high)
    ohlc.append(h)
    l = random.randrange(low, o)
    ohlc.append(l)
    c = random.randrange(l, h)
    ohlc.append(c)
    fake_market.append(ohlc)

fake_volume = [[x, random.randrange(10, 30)] for x in range(low, (high+1))]
df = pd.DataFrame(fake_market, columns=["Date", "Open", "High", "Low", "Close"])
df2 = pd.DataFrame(fake_volume, columns=["Volume", "Price"])

#Do all the plotly stuff
fig = go.Figure(
    data=[
        go.Bar(
            x=[str(x) for x in df2.Price.to_list()],
            y=[str(x) for x in df2.Volume.to_list()],
            orientation="h",
            xaxis="x",
            yaxis="y",
            visible=True,
            showlegend=False
        ),
        go.Candlestick(
            x=[str(x) for x in df.Date.to_list()],
            open=[str(x) for x in df.Open.to_list()],
            high=[str(x) for x in df.High.to_list()],
            low=[str(x) for x in df.Low.to_list()],
            close=[str(x) for x in df.Close.to_list()],
            xaxis="x2",
            yaxis="y2",
            visible=True,
            showlegend=False
        )
    ],
    layout=go.Layout(
        title=go.layout.Title(text="Candlestick With Volume Profile"),
        xaxis=go.layout.XAxis(
            side="top",
            range=[0, 300],
            rangeslider=go.layout.xaxis.Rangeslider(visible=False),
            showticklabels=False
        ),
        yaxis=go.layout.YAxis(
            side="left",
            range=[low, high],
            showticklabels=False
        ),
        xaxis2=go.layout.XAxis(
            side="bottom",
            title="Date",
            rangeslider=go.layout.xaxis.Rangeslider(visible=False),
            overlaying="x"
        ),
        yaxis2=go.layout.YAxis(
            side="right",
            title="Price",
            range=[low, high],
            overlaying="y"
        )
    )
)
template = ["plotly", "plotly_white", "plotly_dark", "ggplot2", "seaborn", "none"]
fig.update_layout(template=template[2])

plot(fig)

Not sure what the law is for publicly posting stock data so I built a simple generator for generating OHLC data and Volume. Real stock data would make the chart seem less of a mess.

VolumeProfileOnCandlestick

Something I have not got right yet is the side that the bars are on. Currently they are on the Left side but it would be nice to have them on the Right side. Should be something easy though.

Well, hope this helps someone sometime. Have a great day!

This works for me:

def custom_round(x, base=5):
    return int(base * round(float(x)/base))

def round_and_group(data,base=5):
    df = data[['Last', 'Volume']].copy()
    #Round to nearest X
    df['Last'] = df['Last'].apply(lambda x: custom_round(x, base=base))
    # Remove the date index
    df = df.set_index('Last')
    df = df.groupby(['Last']).sum()
    return df

df=round_and_group(TTF,base=1)
df.reset_index(inplace=True)
plt.figure(figsize=(10,4), dpi=120) # 10 is width, 4 is height
ax1=plt.subplot(1,2,1)
plt.barh(df['Last'],df['Volume'], 2)
ax2= ax1.twiny()
candlestick2_ohlc(ax2,TTF['Open'],
                      TTF['High'],
                      TTF['Low'],
                      TTF['Last'],width = 0.6,colorup='g')

Fig:

Related