Sometimes, it’s just helpful to have something front and center vs. having to navigate to a web page to find it. This tutorial uses a Raspberry Pi Zero and an OLED display to show a near real-time stock price and gain/loss information that can be used anywhere that the Raspberry Pi can connect to a network with internet connectivity.

Limitations

Obtaining pre and post-market data can be a bit more complicated than simply obtaining during-hours data. The Yahoo finances library used here does not yet support off-hours quotes, so the data returned off-hours is the last close price of the stock. This is going to be left as an enhancement to the reader’s efforts.

Parts Needed

This tutorial utilizes the following parts and software:

  • 1x Raspberry Pi Zero WH (almost any Raspberry Pi version should work)

WARNING: It is recommended that you utilize an external +3.3V power supply and not the VCC from the Raspberry Pi instance to power the OLED display. The Raspberry Pi can consume quite a bit of power and this may interrupt proper operation of the OLED display under certain circumstances.

Prerequisites

It is assumed the reader understands how to install/configure an adequate operating system for the Raspberry Pi such as Raspberry Pi OS, etc. This tutorial assumes the Raspberry Pi OS is used - while other operating systems may in fact work with the code provided (Python), if things don’t work as expected, attempt to install/configure Raspberry Pi OS with the provided code to verify it’s not a difference in the operating system that is causing the issue..

Wiring/Connectivity

First, the wiring. Connect the OLED display to the Raspberry Pi according to the following diagram - in summary, 3.3V to RasPi VCC, GND to RasPi GND, SDA to RasPi SDA, and SCL to RasPi SDL:

Wiring Circuit

Enabling I2C

This code/example uses I2C to communicate with the OLED display. To enable I2C, SSH to the Raspberry Pi device and run sudo raspi-config, then navigate to Interfacing Options. Select I2C and select <Yes> when prompted to enable I2C. This may require a reboot to enable - go ahead and reboot the Raspberry Pi device.

Programming

Now that I2C is enabled, SSH to the Raspberry Pi again and create a directory for the source code and install some required depencies/create a Python virtual environment:

# install os dependencies
$ sudo apt-get -y install python3-pandas

# create a project directory
$ mkdir stock-watcher
$ cd stock-watcher/

# create a python3 virtual environment and activate it
$ pip3 install virtualenv
$ python3 -m virtualenv .env
$ . .env/bin/activate

# install python dependencies
$ pip install adafruit-blinka \
              adafruit-circuitpython-ssd1306 \
              html5lib \
              Pillow \
              requests \
              requests_html \
              yahoo_fin
$ pip --no-cache-dir install pandas

Now that the dependencies are installed, go ahead and create a file named display_stock.py with the following contents:

#!/usr/bin/env python3

import adafruit_ssd1306
import board
import busio
import digitalio
import socket
import subprocess
import time
from datetime import datetime
from PIL import Image, ImageDraw, ImageFont
from yahoo_fin import stock_info

# which stock to track
TICKER_SYMBOL = 'AAPL'

# configs
OLED_ADDR = 0x3C
OLED_WIDTH = 128
OLED_HEIGHT = 64

# initialize i2c and bus
i2c = busio.I2C(board.SCL, board.SDA)
oled = adafruit_ssd1306.SSD1306_I2C(OLED_WIDTH, OLED_HEIGHT, i2c, addr=OLED_ADDR)

# clear display
oled.fill(0)
oled.show()

# create blank image for drawing, and initialiize the draw/fonts
image = Image.new("1", (OLED_WIDTH, OLED_HEIGHT))
draw = ImageDraw.Draw(image)
ticker_font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeSans.ttf", 16)
price_font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeSans.ttf", 26)
diff_font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeSans.ttf", 12)

try:
    while True:
        print("Querying stock...")

        # get the date/time for update
        update_datetime = datetime.now()
        update_time = update_datetime.strftime('%I:%M:%S')

        # get stock information
        try:
            quote = stock_info.get_quote_table(TICKER_SYMBOL)
            price = round(quote['Quote Price'], 2)
            prev_close = round(quote['Previous Close'], 2)
            delta = round(price - prev_close, 2)
            symbol = '\u25b2' if delta >= 0 else '\u25bc'   # display up/down arrow based on movement

            # convert to strings to display/handle display
            price_str = str(price)
            delta_str = str(delta)

            # calculate positioning
            (price_font_width, price_font_height) = price_font.getsize(price_str)
            (diff_font_width, diff_font_height) = diff_font.getsize(delta_str)

            # clear and draw stock info to screen
            draw.rectangle((0, 0, OLED_WIDTH, OLED_HEIGHT), outline=0, fill=0)
            draw.text((0, 0), TICKER_SYMBOL, font=ticker_font, fill=255)
            draw.text((oled.width - diff_font.getsize(update_time)[0], 0), update_time, font=diff_font, fill=255)
            draw.text((oled.width // 2 - price_font_width // 2, oled.height // 2 - price_font_height // 2), "${}".format(price_str), font=price_font, fill=255)
            draw.text((oled.width // 2 - diff_font_width // 2, OLED_HEIGHT - diff_font_height), "{} ${}".format(symbol, delta_str), font=diff_font, fill=255)
            oled.image(image)
            oled.show()

            print("Done!")
        except socket.gaierror as e:
            print("Failure to get stock quote - DNS error: {}".format(str(e)))
        except Exception as e:
            print("Failure to get/process stock quote - unknown error: {}".format(str(e)))

        # sleep between queries
        time.sleep(5)
except KeyboardInterrupt:
    print("SIGINT detected - releasing screen")
    oled.fill(0)
    oled.show()
    exit(0)

Once the above is created, update the TICKER_SYMBOL value and run the script. If all goes well, you should see the stock symbol, current price, and change since last close price display on the OLED!

Enhancing

Now that you have your script created and working, there are many things you could do to enhance it (add buttons for different metrics, add lights when you hit an all-time high, etc.). In addition, MUCH more error checking should be added to the script above.

One useful thing is to enable the Raspberry Pi to launch the stock symbol display on boot so you don’t need to run the script by hand each time the Raspberry Pi is rebooted. To do this, edit the file /etc/rc.local and add the following lines prior to the exit 0 directive:

$ sudo vim /etc/rc.local
# add the following before `exit 0`:
#   ...
#   . /home/pi/stock-watcher/.env/bin/activate
#   python3 /home/pi/stock-watcher/display_stock.py
#   exit 0

Now reboot the Pi device. If all goes well, the OLED should automatically be populated with the stock information once the Pi device boots, gets an IP address, and is able to connect to the internet. If something appears to have gone wrong, investigate the logs in /var/log for clues.

Troubleshooting

There is a possibility when running the display_stock.py script you will get an error related to the numpy package (especially if you’re running on a Pi Zero or similar) that looks like the following:

Traceback (most recent call last):
  File "display_stock.py", line 10, in <module>
    from yahoo_fin import stock_info
  File "/home/pi/stock-watcher/.env/lib/python3.7/site-packages/yahoo_fin/__init__.py", line 3, in <module>
    import pandas as pd
  File "/home/pi/stock-watcher/.env/lib/python3.7/site-packages/pandas/__init__.py", line 17, in <module>
    "Unable to import required dependencies:\n" + "\n".join(missing_dependencies)
ImportError: Unable to import required dependencies:
numpy:

IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!

Importing the numpy C-extensions failed. This error can happen for
many reasons, often due to issues with your setup or how NumPy was
installed.

We have compiled some common reasons and troubleshooting tips at:

    https://numpy.org/devdocs/user/troubleshooting-importerror.html

Please note and check the following:

  * The Python version is: Python3.7 from "/home/pi/stock-watcher/.env/bin/python"
  * The NumPy version is: "1.19.4"

and make sure that they are the versions you expect.
Please carefully study the documentation linked above for further help.

Original error was: libf77blas.so.3: cannot open shared object file: No such file or directory

A potential fix is documented in the link detailed in the error - what worked well on the Pi Zero that was being used when this was written was to perform the following, which resulted in being able to then successfully launch the display_stock.py script:

$ sudo apt-get install libatlas-base-dev

Credit

The above tutorial was pieced together with some information from the following sites/resources, among others that were likely missed in this list: