Skip to content

Commit 9b83da7

Browse files
committed
plotting updates, market profiler attributes update
1 parent a796a9d commit 9b83da7

File tree

8 files changed

+619
-32
lines changed

8 files changed

+619
-32
lines changed

config/influx_config_local.ini

Lines changed: 0 additions & 6 deletions
This file was deleted.

config/volume_data_recorder_config.json

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Config:
2+
# - inst: btcusdt
3+
# channel: aggTrade
4+
# token: BTC
5+
# resample_period: 1min
6+
# round_px_to_nearest: 10
7+
# - inst: ethusdt
8+
# channel: aggTrade
9+
# token: ETH
10+
# resample_period: 1min
11+
# round_px_to_nearest: 2
12+
- inst: nearusdt
13+
channel: aggTrade
14+
token: NEAR
15+
resample_period: 1min
16+
round_px_to_nearest: 0.01

data_pipeline/market_profile_reader.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def __init__(self, inst: str, timepoint: datetime, ohlcv: dict, orderflow: np.nd
5151
# read orderflow data
5252
self.n_levels = len(orderflow)
5353
self.price_levels = np.array([row[0] for row in orderflow])
54+
self.price_levels_range = (max(self.price_levels), min(self.price_levels))
5455
self.delta_qty = round(sum([row[2] for row in orderflow]), 8)
5556
self.total_bid_qty = round(sum([row[3] for row in orderflow]), 8)
5657
self.total_ask_qty = round(sum([row[4] for row in orderflow]), 8)

data_pipeline/volume_data_recorder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from unicorn_binance_websocket_api import BinanceWebSocketApiManager
66
from unicorn_fy import UnicornFy
77

8-
from research.influx_db_handler import InfluxDbHandler
8+
from influx_db_handler import InfluxDbHandler
99

1010
class VolumeDataRecorder:
1111

plotting_backend.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'''
2+
Backend for updating GUI data.
3+
Uses websocket server to receive message for real time updating the GUI.
4+
'''
5+
6+
import argparse
7+
import yaml
8+
import os
9+
from multiprocessing import Process
10+
11+
from data_pipeline.volume_data_recorder import VolumeDataRecorder
12+
13+
14+
def main_ws(env, config):
15+
ws = VolumeDataRecorder(env, config)
16+
ws.run()
17+
18+
19+
if __name__ == '__main__':
20+
'''
21+
Real time streaming of aggTrade data. Data are transformed and written into influx directly.
22+
'''
23+
# read argument
24+
parser = argparse.ArgumentParser()
25+
parser.add_argument("-e", "--env", dest="env", help="system environment", metavar="environment", default="local")
26+
args = parser.parse_args()
27+
if args.env == 'local':
28+
env = 'local'
29+
elif args.env == 'server':
30+
env = 'prod'
31+
32+
# read config
33+
with open(f'{os.path.dirname(__file__)}config/volume_data_recorder_config.yaml') as f:
34+
configs = yaml.full_load(f)['Config'] # list of configs for each thread
35+
36+
process_list = []
37+
for config in configs:
38+
volume_recorder_p = Process(target=main_ws, args=(env, config,), daemon=True)
39+
process_list.append(volume_recorder_p)
40+
volume_recorder_p.start()
41+
42+
for p in process_list:
43+
p.join()

plotting_classic.py

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
from datetime import datetime, timezone
2+
import pytz
3+
4+
import pandas as pd
5+
import numpy as np
6+
import matplotlib.pyplot as plt
7+
import pyqtgraph as pg
8+
import talib as ta
9+
10+
import finplot_lib as fplt
11+
12+
from data_pipeline.market_profile_reader import MarketProfileReader
13+
14+
'''
15+
Plotting of orderflow chart
16+
- Candlestick
17+
- Orderflow data by price level
18+
- Volume bar
19+
- Classic MACD
20+
- CVD
21+
- StochRSI
22+
23+
Pyqtgraph ref:
24+
https://pyqtgraph.readthedocs.io/en/latest/index.html
25+
https://doc.qt.io/qtforpython-5/contents.html
26+
'''
27+
28+
if __name__ == '__main__':
29+
inst = 'btcusdt'
30+
# input in HKT
31+
start = datetime(2021, 12, 10, 0, 0, 0)
32+
end = datetime(2021, 12, 10, 6, 0, 0)
33+
34+
profile = MarketProfileReader()
35+
profile.load_data_from_influx(inst=inst, start=start, end=end, env='local')
36+
37+
# slice_dt = pytz.timezone('Asia/Hong_Kong').localize(datetime(2022,3,17,17,23,0)) # input in HKT
38+
slice_start = pytz.timezone('Asia/Hong_Kong').localize(start) # input in HKT
39+
slice_end = pytz.timezone('Asia/Hong_Kong').localize(end) # input in HKT
40+
41+
# mp_slice = profile[slice_dt]
42+
mp_slice = profile[slice_start:slice_end]
43+
44+
ohlcv = pd.DataFrame(
45+
{
46+
'o': [mp.open for mp in mp_slice],
47+
'h': [mp.high for mp in mp_slice],
48+
'l': [mp.low for mp in mp_slice],
49+
'c': [mp.close for mp in mp_slice],
50+
'v': [mp.volume_qty for mp in mp_slice],
51+
'd': [mp.delta_qty for mp in mp_slice],
52+
},
53+
index=[mp.timepoint for mp in mp_slice]
54+
)
55+
56+
ema_n = [20, 50, 200]
57+
ema_colors = ['#33DCCD50', '#ADE03670', '#F4D03F80']
58+
for n in ema_n:
59+
ohlcv[f'ema{n}'] = ta.EMA(ohlcv['c'], timeperiod=n)
60+
61+
macd = [12, 26, 9]
62+
ohlcv['macd'], ohlcv['macd_signal'], ohlcv['macd_diff'] = ta.MACD(ohlcv['c'], fastperiod=macd[0], slowperiod=macd[1], signalperiod=macd[2])
63+
64+
stoch_rsi = [21, 14, 14]
65+
ohlcv['fastk'], ohlcv['fastd'] = ta.STOCHRSI(ohlcv['c'], timeperiod=stoch_rsi[0], fastk_period=stoch_rsi[1], fastd_period=stoch_rsi[2], fastd_matype=0)
66+
67+
print('Drawing plot...')
68+
69+
# plotting
70+
fplt.foreground = '#D6DBDF'
71+
fplt.background = '#151E26'
72+
fplt.legend_border_color = '#ffffff30' # make transparent
73+
fplt.legend_fill_color = '#ffffff10' # make transparent
74+
fplt.legend_text_color = fplt.foreground
75+
fplt.top_graph_scale = 2 # top graph and bottom graph has ratio of r:1
76+
fplt.winx, fplt.winy, fplt.winw, fplt.winh = 0,0,1600,1600
77+
ax, ax3, ax2, ax4 = fplt.create_plot(
78+
title='Chart',
79+
rows=4, # main candlestick = ax / MACD = ax3 / CVD = ax2 / StochRSI = ax4
80+
maximize=False,
81+
init_zoom_periods=18,
82+
row_stretch_factors=[3, 1, 1, 1]
83+
)
84+
85+
# placeholder for tick info; updated with fplt.set_time_inspector(func)
86+
hover_label = fplt.add_legend('', ax=ax)
87+
88+
# set max zoom: for orderflow data, allow more zoom (default was 20)
89+
fplt.max_zoom_points = 20
90+
91+
# add candlestick
92+
# this is the version of candlestick without orderflow data
93+
candlestick_plot = fplt.candlestick_ochl(datasrc=ohlcv[['o', 'c', 'h', 'l']], candle_width=0.7, ax=ax)
94+
95+
# add volume
96+
volume_plot = fplt.volume_ocv(ohlcv[['o', 'c', 'v']], candle_width=0.7, ax=ax.overlay())
97+
98+
# plot EMAs
99+
for n, color in zip(ema_n, ema_colors):
100+
fplt.plot(ohlcv[f'ema{n}'], ax=ax.overlay(), legend=f'EMA {n}', color=color)
101+
102+
# plot stoch RSI
103+
stoch_rsi_plot = fplt.plot(ohlcv['fastk'], ax=ax4, legend=f'StochRSI Fast k: {stoch_rsi[1]} Timeperiod: {stoch_rsi[0]}')
104+
fplt.plot(ohlcv['fastd'], ax=ax4, legend=f'StochRSI Fast d: {stoch_rsi[2]} Timeperiod: {stoch_rsi[0]}')
105+
106+
thresholds = [20, 80]
107+
for th in thresholds:
108+
rsi_threshold_line = pg.InfiniteLine(pos=th, angle=0, pen=fplt._makepen(color='#ffffff50', style='- - '))
109+
ax4.addItem(rsi_threshold_line, ignoreBounds=True)
110+
fplt.add_band(*thresholds, color='#2980B920', ax=ax4)
111+
112+
vb = stoch_rsi_plot.getViewBox()
113+
vb.setBackgroundColor('#00000000')
114+
115+
# plot MACD
116+
macd_plot = fplt.volume_ocv(ohlcv[['o', 'c', 'macd_diff']], ax=ax3, candle_width=0.7, colorfunc=fplt.strength_colorfilter)
117+
fplt.plot(ohlcv['macd'], ax=ax3, legend=f'MACD ({macd[0]}, {macd[1]}, {macd[2]})')
118+
fplt.plot(ohlcv['macd_signal'], ax=ax3, legend='Signal')
119+
120+
vb = macd_plot.getViewBox()
121+
vb.setBackgroundColor('#00000000')
122+
123+
'''
124+
Ref: examples/snp500.py
125+
'''
126+
# plot cvd
127+
line_color = '#F4D03F'
128+
cvd_plot = fplt.plot(np.cumsum(ohlcv['d']), ax=ax2, legend='CVD', color=line_color, fillLevel=0, brush=line_color+'10')
129+
# and set background
130+
vb = cvd_plot.getViewBox()
131+
vb.setBackgroundColor('#00000000')
132+
133+
'''
134+
Ref: examples/complicated.py
135+
'''
136+
# set bull body to same color as bull frame; otherwise it is default background color (transparent)
137+
bull = '#1ABC9C'
138+
bear = '#E74C3C'
139+
fplt.candle_bull_color = bull
140+
fplt.candle_bull_body_color = bull
141+
fplt.candle_bear_color = bear
142+
candlestick_plot.colors.update({
143+
'bull_body': fplt.candle_bull_color
144+
})
145+
146+
transparency = '45'
147+
volume_plot.colors.update({
148+
'bull_frame': fplt.candle_bull_color + transparency,
149+
'bull_body': fplt.candle_bull_body_color + transparency,
150+
'bear_frame': fplt.candle_bear_color + transparency,
151+
'bear_body': fplt.candle_bear_color + transparency,
152+
})
153+
154+
# set gridlines
155+
ax.showGrid(x=True, y=True, alpha=0.2)
156+
ax2.showGrid(x=True, y=True, alpha=0.2)
157+
ax3.showGrid(x=True, y=True, alpha=0.2)
158+
ax4.showGrid(x=True, y=True, alpha=0.2)
159+
160+
# add YAxis item at the right
161+
# ax.axes['right'] = {'item': fplt.YAxisItem(vb=ax.vb, orientation='right')}
162+
# ax2.axes['right'] = {'item': fplt.YAxisItem(vb=ax2.vb, orientation='right')}
163+
164+
# add legend of ohlcv data
165+
'''
166+
Ref: examples/snp500.py
167+
'''
168+
def update_legend_text(x, y):
169+
dt = datetime.fromtimestamp(x // 1000000000)
170+
utcdt = dt.astimezone(pytz.utc)
171+
# dt = dt.replace(tzinfo=timezone.utc)
172+
row = ohlcv.loc[utcdt]
173+
# format html with the candle and set legend
174+
fmt = '<span style="color:%s; margin: 16px;">%%s</span>' % (bull if (row['o'] < row['c']).all() else bear)
175+
rawtxt = '<span style="font-size:14px">%%s %%s</span> &nbsp; O: %s H: %s L: %s C: %s Delta: %s' % (fmt, fmt, fmt, fmt, fmt)
176+
hover_label.setText(rawtxt % ('TOKEN', 'INTERVAL', row['o'], row['h'], row['l'], row['c'], row['d']))
177+
fplt.set_time_inspector(update_legend_text, ax=ax, when='hover')
178+
179+
# additional crosshair info
180+
def enrich_info(x, y, xtext, ytext):
181+
o = ohlcv.iloc[x]['o']
182+
h = ohlcv.iloc[x]['h']
183+
l = ohlcv.iloc[x]['l']
184+
c = ohlcv.iloc[x]['c']
185+
add_xt = f'\t{xtext}'
186+
add_yt = f'\tLevel: {ytext}\n\n\tOpen: {o}\n\tHigh: {h}\n\tLow: {l}\n\tClose: {c}'
187+
return add_xt, add_yt
188+
189+
fplt.add_crosshair_info(enrich_info, ax=ax)
190+
191+
'''
192+
Ref: examples/complicated.py
193+
'''
194+
# set dark themes ====================
195+
pg.setConfigOptions(foreground=fplt.foreground, background=fplt.background)
196+
197+
# window background
198+
for win in fplt.windows:
199+
win.setBackground(fplt.background)
200+
201+
# axis, crosshair, candlesticks, volumes
202+
axs = [ax for win in fplt.windows for ax in win.axs]
203+
vbs = set([ax.vb for ax in axs])
204+
axs += fplt.overlay_axs
205+
axis_pen = fplt._makepen(color=fplt.foreground)
206+
for ax in axs:
207+
ax.axes['left']['item'].setPen(axis_pen)
208+
ax.axes['left']['item'].setTextPen(axis_pen)
209+
ax.axes['bottom']['item'].setPen(axis_pen)
210+
ax.axes['bottom']['item'].setTextPen(axis_pen)
211+
if ax.crosshair is not None:
212+
ax.crosshair.vline.pen.setColor(pg.mkColor(fplt.foreground))
213+
ax.crosshair.hline.pen.setColor(pg.mkColor(fplt.foreground))
214+
ax.crosshair.xtext.setColor(fplt.foreground)
215+
ax.crosshair.ytext.setColor(fplt.foreground)
216+
# ====================================
217+
218+
fplt.show()

0 commit comments

Comments
 (0)