While working on a new dashboard for QTPyLib I needed to get a Matplotlib plot to refresh and update based on data coming from a ZeroMQ stream.

After going over the documentation and Stack Overflow I was finally able to get this working. To save others from the headache, I decided to post a working demo here as a public service.

First, here's a quick example:

Peaked your interest? Good! Let's get started...

We'll start with a simpler example than shown above.

In our example, we'll use ZeroMQ's pub-sub architecture using 2 files:

  1. server.py will download SPY daily market data from Yahoo! finance and then will "publish" using a socket
  2. client.py will listen on our server's port and will plot every new data point

Here's the code:

The server (streamer)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
server.py
download SPY data from Yahoo finance and stream it using ZeroMQ
"""

# import libraries
import zmq
import time
from pandas_datareader import data

# create the zmq context and socket and bind the socket to port 1234
socket = zmq.Context(zmq.REP).socket(zmq.PUB)
socket.bind("tcp://*:1234")

# get market data from yahoo
data = data.get_data_yahoo("SPY", start="2017-01-01")

# iterate over rows
for ix, _ in data.iterrows():
    # get row as dataframe
    row = data[data.index==ix]['Adj Close']

    # stream row as python object
    socket.send_pyobj(row)

    # wait 1 second
    time.sleep(1)

The Client (plotter)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
client.py
listens on ZeroMQ's port and plot every new data point
"""

# import libraries
import zmq
import time
import matplotlib.pyplot as plt
import pandas as pd

# create the zmq client and listen on port 1234
socket = zmq.Context(zmq.REP).socket(zmq.SUB)
socket.setsockopt_string(zmq.SUBSCRIBE, '')
socket.connect('tcp://127.0.0.1:1234')

# create an empty dataframe that will store streaming data
df = pd.DataFrame()

# create plot
plt.ion() # <-- work in "interactive mode"
fig, ax = plt.subplots()
fig.canvas.set_window_title('Live Chart')
ax.set_title("SPY, 2017")

# act on new data coming from streamer
while True:
    # receive python object
    row = socket.recv_pyobj()

    # append new row to dataframe
    df = pd.concat([df, row])

    # plot all data
    ax.plot(df, color='r')

    # show the plot
    plt.show()
    plt.pause(0.0001) # <-- sets the current plot until refreshed

    # be nice to the cpu :)
    time.sleep(.1)

Once you run both scripts you should see somehting like this:

* I recommend you run client.py first, so you won't miss plotting any data points :)

You can, of course, use any of the formatting options available in Matplotlib, so let your imagination run wild.