Visualizing Live Price Updates with Bokeh (Python Tutorial)
Data sourced with Tiingo API
One impressive feature that commercial trading and brokerage websites seem to pull of with ease is the visualization of live price data. It seems tricky but in fact it’s something that you can easily do yourself if you know how.
In this tutorial to I’d like to show you an approach how to visualize live price data using WebSockets to receive like price data and plotting them with the Bokeh library.
This story is solely for general information purposes, and should not be relied upon for trading recommendations or financial advice. Source code and information is provided for educational purposes only, and should not be relied upon to make an investment decision. Please review my full cautionary guidance before continuing.
What are WebSockets?
To receive live price data for assets like stocks, crypto or FOREX, data providers often use Websockets.
WebSockets is a protocol that enables two-way communication between a client and a server over a single, long-lived connection.
WebSockets work by first setting up a standard HTTP connection and then upgrading the connection to a persistent WebSocket connection via a protocol switch. This is also known as a WebSocket handshake. Once the handshake is successful, the initial HTTP connection is replaced by a WebSocket connection that uses the same underlying TCP/IP connection.
The advantages of using WebSockets are:
Real-time communication: WebSockets allow for real-time data transfer. Once the connection is open, you can read from and write to the server in real-time.
Reduced overhead and latency: With HTTP, each new request comes with a range of additional information in headers, which can increase overhead.
Full-duplex communication: WebSockets provide a full-duplex communication channel. That is, data can be sent and received simultaneously without interrupting the transmission line.
Using Tiingo WebSockets
Let’s take a look how we would use WebSocket messages from Tiingo API. Here the link to the documentation. Setting up WebSockets is very similar for the different data providers so you can reuse this code for other providers.
Connection Setup
The first thing we need to find out is the WebSocket URL, which will be ‘wss://api.tiingo.com/iex’ for stocks.
Next, you need to create a subscription to listen to certain types of messages. The subscription consists of the eventName (e.g. subscribe), your Tiingo API key and an eventData object, which contains the threshold level and optionally, an array of tickers.
The threshold level specifies the threshold of messages you will receive from the socket.
A level of 0 means you will get ALL Top-of-Book and Last Trade updates.
A level of 5 means you will get all Last Trade updates and only Quote updates that are deemed major updates by our system.
subscribe = {
'eventName':'subscribe',
'authorization':'<Your Tiingo API key>',
'eventData': {
'thresholdLevel': 5,
'ticker': ['tsla', 'aapl', 'msft']
}
}
Setting up the WebSocket connection involves creating a connection and passing in the WebSocket URL:
ws = create_connection(ws_url)
Then you can send the subscription message using the connection:
ws.send(json.dumps(subscribe))
Listener Setup
Setting up a WebSocket is ridiculously easy. All you need to do is run an endless loop and call the receive() method on the WebSocket connection. When we receive a message, we can call a handler function to process it.
One important concept to understand is that the listener needs to run on a separate thread. Since it is running a continuous ‘while’ loop it can not run on the main thread which updates the UI because all the following code for UI updates would not run due to the loop. Message updates need to be passed from the WebSocket thread to the main UI thread. To achieve this, we can use the doc.add_next_tick_callback() Bokeh provides to update the data source from the listener thread with the newest data.
def websocket_listener(ws, doc, data_source):
while True:
message = ws.recv()
new_row = process_message(message)
if new_row:
doc.add_next_tick_callback(lambda: data_source.stream(new_row))
In order to start the listener on a separate thread, we will use the Python threading module.
listener_thread = threading.Thread(target=websocket_listener, args=(ws, doc, data_source))
listener_thread.start()
Message Processing
The WebSocket interface provides messages from the Tiingo system. This is what a sample message looks like:
{
"messageType":"A",
"service":"iex",
"data":[
"T",
"2019-01-30T13:33:45.594808294-05:00",
1548873225594808294,
"wes",
null,
null,
null,
null,
null,
50.285,
200,
null,
0,
0,
0,
0
]
}
The messageType will always be ‘A’ for new price quotes. In our code we are checking the messageType anyway in case other types will be added in the future.
The data array contains a list of values, each of which has a separate meaning. These array elements are described in the Tiingo documentation for the message response.
Here the array indexes we are interested in:
0: Message Type: Here we need to check for ‘Q’, which is a top-of-book quote
1: The datetime stamp
3: Ticker
7: Ask Price
In case you don’t pass any tickers in the subscription object, you will receive quotes for all symbols, which I don’t recommend. In this case, you will need to filter based on the ticker provided in the message.
What is Bokeh?
Bokeh is a powerful Python library used for creating interactive and versatile visualizations in a web browser. It helps you create beautiful data visualizations that support the presentation of data.
Bokeh offers both high-level and low-level interfaces for creating plots. High-level interfaces, such as bokeh.plotting
and bokeh.charts
, are used for generating general purpose plots and statistical charts. Low-level interface, bokeh.models
, gives more flexibility and control in creating complex statistical plots.
Some key features:
Bokeh can create a wide range of visualization types, such as scatter plots, line plots, bar plots, histograms, heatmaps, etc. It's also great for creating complex layouts for dashboards and data applications.
Bokeh creates interactive plots and dashboards. Interactivity includes panning, zooming, selecting, linked panning, and linked brushing.
Bokeh can create server-backed apps enabling complex querying, client-side computations, and large dataset visualizations.
Bokeh is capable of transforming visualization written in other libraries like matplotlib, seaborn, ggplot, etc., into Bokeh plots more conveniently.
Bokeh also supports streaming and real-time data, making it an excellent choice for plots that require updating the source data regularly. That’s what we will use in this tutorial
How to stream data to Bokeh
So how do we stream price data from the WebSocket messages to the Bokeh chart?
The bokeh.models module provides a handy ColumnDataSource, which is basically just a dictionary of key/value pairs.
We first create the ColumnDataSource by defining the key/value pairs. When we receive a message, we can use the data source stream() method to update the data source with new values.
Then, when add a line trace to the Bokeh chart, we pass the data source in as a initialization parameter.
Here the high-level code:
# Create the datasource
data_source = ColumnDataSource(data={"ask": [], "datetime": []})
# Update the data source
data_source.stream({"ask": [data['data'][7]], "datetime": [datetime.now()]})
# Create the Bokeh chart
fig = figure(x_axis_type="datetime", plot_width=1024, plot_height=480,
tooltips=[("ask", "@ask")], title=f"{symbol.upper()} ask Price Live")
# Add the price line to the chart
fig.line(x="DateTime", y="Ask", line_color="blue", line_width=3.0, source=data_source)
The Game Plan
So here’s what we’ll do in this tutorial:
Define a data source
Create a WebSocket connection to the Tiingo IEX WebSocket
Listen for top-of-book quote messages
Create and display a Bokeh chart
When we receive a message, update the data source with the new Ask price.
StockDads.com is a thriving trading community with AI trading stock/crypto alerts, expert advice and a ton of educational materials. Get a 30% forever discount with code ‘BOTRADING’