I’m trying to create a Dash application that connect to a financial exchange FTX via this Websocket client. I’m aiming for 100ms refresh rate. The websocket connection in the client is managed by a background thread, so the actual call ws_perp.get_orderbook(market='KIN-PERP')
can be polled at high frequency with near-zero latency.
However, when I add the calls to my callback function I get a InvalidThreadUseError
error.
Can someone please help me understand how I can solve this?
Thanks!
Code
import dash
from dash.dependencies import Output, Input
from dash import dcc
from dash import html
import plotly
import random
import plotly.graph_objs as go
from collections import deque
import time
from ftx_client_ws import client as ws_client
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
SAMPLE_COUNT = 250
entry_list = deque(maxlen = SAMPLE_COUNT)
entry_list.append(100)
exit_list = deque(maxlen = SAMPLE_COUNT)
exit_list.append(100)
start_time = time.time()
previous_pass = 0
time_list = deque(maxlen = SAMPLE_COUNT)
time_list.append(0)
ws_perp = ws_client.FtxWebsocketClient()
ws_spot = ws_client.FtxWebsocketClient()
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div(id='dark-theme-container', children=[
dcc.Interval(
id='data-update',
interval=600,
n_intervals=0
),
dcc.Graph(id = 'live-graph',
animate = False,
style={"height":"80vh"}),
]
)
@app.callback(
Output('live-graph', 'figure'),
[ Input('data-update', 'n_intervals') ]
)
def update_graph_scatter(n):
global previous_pass
# These two client calls break the application
# perp_order_book = ws_perp.get_orderbook(market='KIN-PERP')
# spot_order_book = ws_spot.get_orderbook(market='KIN/USD')
# perp_leading_ask = perp_order_book['asks'][0][0]
# perp_leading_bid = perp_order_book['bids'][0][0]
#
# spot_leading_ask = spot_order_book['asks'][0][0]
# spot_leading_bid = spot_order_book['bids'][0][0]
#
# entry_spread = ( perp_leading_bid / spot_leading_ask ) * 100
# exit_spread = ( perp_leading_ask / spot_leading_bid ) * 100
#
# entry_list.append(entry_spread)
# exit_list.append(exit_spread)
entry_list.append(entry_list[-1] + entry_list[-1] * random.uniform(-0.1, 0.1))
exit_list.append(exit_list[-1] + exit_list[-1] * random.uniform(-0.1, 0.1))
if previous_pass == 0:
refresh_rate = time.time() - start_time
else:
refresh_rate = time.time() - previous_pass
time_list.append(time_list[-1] + refresh_rate)
previous_pass = time.time()
entry_data = plotly.graph_objs.Scatter(
x=list(time_list),
y=list(entry_list),
name='( perp_leading_bid / spot_leading_ask ) * 100',
mode='lines',
)
exit_data = plotly.graph_objs.Scatter(
x=list(time_list),
y=list(exit_list),
name='( perp_leading_ask / spot_leading_bid ) * 100',
mode= 'lines'
)
merged_list = entry_list + exit_list
previous_pass = time.time()
return {'data': [entry_data, exit_data],
'layout' : go.Layout(xaxis=dict(
range=[min(time_list),max(time_list)]),yaxis =
dict(range = [min(merged_list)*0.999,max(merged_list)*1.001]),
title=f"<b>KIN</b>, refresh rate: {refresh_rate:.2}s"
)}
if __name__ == '__main__':
app.run_server(debug=True)
Call Stack
Traceback (most recent call last):
File "D:\Users\Charles\Documents\dash_plotter.py", line 57, in update_graph_scatter
perp_order_book = ws_perp.get_orderbook(market='KIN-PERP')
File "D:\Users\Charles\Documents\ftx_client_ws\client.py", line 95, in get_orderbook
self.wait_for_orderbook_update(market, 5)
File "D:\Users\Charles\Documents\ftx_client_ws\client.py", line 112, in wait_for_orderbook_update
self._orderbook_update_events[market].wait(timeout)
File "src/gevent/event.py", line 163, in gevent._gevent_cevent.Event.wait
File "src/gevent/_abstract_linkable.py", line 509, in gevent._gevent_c_abstract_linkable.AbstractLinkable._wait
File "src/gevent/_abstract_linkable.py", line 206, in gevent._gevent_c_abstract_linkable.AbstractLinkable._capture_hub
gevent.exceptions.InvalidThreadUseError: (<Hub '' at 0x1f35dd95f40 backend=default ptr=<cdata 'struct uv_loop_s *' 0x000001F35DE60030> pending=0 ref=0 callbacks=0 thread_ident=0x740c>, None, <greenlet.greenlet object at 0x000001F35D1E70F0 (otid=0x000001F35FEA1780) current active started main>)