Sure:
from bokeh.plotting import figure
from bokeh.models import CDSView, ColumnDataSource, IndexFilter, Button, CustomJS, Div, ColorBar, LogColorMapper, HoverTool, FuncTickFormatter
from bokeh.plotting import figure, output_notebook, save, show, output_file
from bokeh.io import curdoc
from bokeh.layouts import gridplot, column, row
from bokeh import events
from bokeh.models.ranges import DataRange1d
from pathlib import Path
import os, glob, warnings, param, tiledb, random, dask, logging, time, json, copy, requests
import panel as pn
import pandas as pd
import numpy as np
from engineering_notation import EngNumber
pn.extension()
warnings.filterwarnings('ignore')
# DB query timestamps
start = pd.datetime.utcnow()-pd.Timedelta('1D')#'2020-11-01'
end = pd.datetime.utcnow()#'2021-01-03'
# Improve DB read performance
config = tiledb.Config({
"sm.tile_cache_size":str(200_000_000),
"py.init_buffer_bytes": str(1024**2 * 400)
})
ctx = tiledb.Ctx(config)
# Resolution to read from DB
res = '10min'
# Symbol to load
ticker = 'BTC_USDT'
# Create multi-range subqueries
a = pd.date_range(start=start, end=end, periods=2).to_frame().resample(res).last().index-pd.Timedelta('5min')
b = a+pd.Timedelta('7min') if ticker!= 'BTC_USDT' else a+pd.Timedelta('4min')
c = [slice(*pd.DataFrame({'a':a,'b':b}).iloc[i].values.astype(np.datetime64)) for i in range(len(a))]
# Path to DB
_dir = Path(r'../Material Indicators/mnt/volume-nbg1-1/orderbook_data3/Binance') if ticker!='BTC_USDT' else Path(r'../Material Indicators/mnt/volume-nbg1-1/orderbook_data_BTC_only')
_dir = os.path.join(_dir,ticker)
# Load data from DB and return as multi-index df
with tiledb.open(_dir,ctx=ctx) as A:
df = A.query(dims=['price','date'],index_col=['price','date']).df[:,c]#.unstack().ffill(axis=1)#.drop_duplicates(subset=['price','date'],keep='last')
def custom_round(x, base=10):
return (base * round(float(x)/base))
def bin_rows(df,price,freq=50,decimal=0.005):
k = np.sort(np.concatenate([np.logspace(-8,8,17),np.logspace(-8,8,17)*5]))
base = k[np.abs(k-np.mean([price.max(),price.min()])*decimal).argmin()]
bins = pd.interval_range(start=custom_round(price.min()*0.3,base),
end=custom_round(price.max()*2,base),
freq=base,closed='right')
return df.groupby(pd.cut(df.index,bins=bins,right=True,include_lowest=True)).sum()
def callback(event):
global i
i+=1
patch = dict(
image=[(0,unstacked.iloc[:,:i].values)],
x=[(0,unstacked.iloc[:,:i].columns.min())],
y=[(0,unstacked.iloc[:,:i].index.min())],
dh=[(0,unstacked.iloc[:,:i].index.max()-unstacked.iloc[:,:i].index.min())],
dw=[(0,unstacked.iloc[:,:i].columns.max()-unstacked.iloc[:,:i].columns.min())]
)
source.patch(patch)
# Downsample data in x-axis & y-axis
unstacked = bin_rows(df.unstack().quantity.resample('10min',axis=1).last(),price=pd.Series([45000,43000]))
unstacked.index = [i.right for i in unstacked.index]
unstacked.index.name = 'price'
# Create CDS for bokeh stream, start with first column
i = 1
source = ColumnDataSource(
dict(
image=[unstacked.iloc[:,:i].values],
x=[unstacked.iloc[:,:i].columns.min()],
y=[unstacked.iloc[:,:i].index.min()],
dh=[unstacked.iloc[:,:i].index.max()-unstacked.iloc[:,:i].index.min()],
dw=[unstacked.iloc[:,:i].columns.max()-unstacked.iloc[:,:i].columns.min()]
)
)
# Load JS code for formatting axes & cmap
with open('JS_code','r') as f:
JS_Eng_units,JS_decimal = json.load(f).values()
formatter = FuncTickFormatter(code=JS_Eng_units)
x_range = DataRange1d(range_padding=0.0)
y_range = DataRange1d(range_padding=0.0)
p = figure(x_range=x_range,y_range=y_range,
sizing_mode='stretch_width',height=500,
x_axis_type='datetime',y_axis_location="right")
# Figure out how to change cmap to Fire, as well as how to have a dynamic range
color_mapper = LogColorMapper(palette="Viridis256", low=2e6, high=1e7)
# Image with stream
p.image(
image='image',x='x',y='y',
dh='dh',dw='dw',
source=source,color_mapper=color_mapper
)
# Custom hovertools
p.add_tools(HoverTool(
tooltips=[
( "date", "$x{%F %T}" ),
( "price", "$y{"+f"0.00"+" a}" ),
( "value", "@image{0,0.00}" ),
],
formatters={
'$x' : 'datetime', # use 'datetime' formatter for 'date' field
},
# display a tooltip whenever the cursor is vertically in line with a glyph
# mode='vline'
))
# Formatting stuff
p.yaxis.formatter = formatter
p.yaxis.axis_label_text_font_style = 'normal'
p.yaxis.axis_label = 'Price [USDT]'
# p.yaxis.axis_label_text_font = 'roboto'
p.yaxis.axis_label_text_font_size = '20px'
color_bar = ColorBar(title='Vol.',title_standoff=10,width=25,color_mapper=color_mapper, label_standoff=7,formatter=formatter,location=(0,0))
p.add_layout(color_bar, 'left')
# Add button with callback
button = Button(label="Update OB")
button.on_click(callback)
layout = column(button,p)
# curdoc().add_root(layout)
# Use panel to display
pn.Column(layout)
Sample data (csv):
https://1drv.ms/u/s!ArP7_EkyioIBxuAp7LiIv9Evg9EsOA?e=PdS9KM