Bring Drag & Drop to Dash with Dashboard Engine. 💫 Learn how at our next webinar!

Adding Websocket call breaks application: InvalidThreadUseError

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>)