Asset Ticker and Economic Indicator IoT Display

by rodrialk in Circuits > Microcontrollers

32 Views, 1 Favorites, 0 Comments

Asset Ticker and Economic Indicator IoT Display

demo_3.png
demo_3.png
demo_2.png
demo_1.png

Transform your financial monitoring with this real-time market display powered by the Matrix Portal S3 and a 64x32 LED matrix. Designed for retail traders, this project delivers instant access to customizable stock, ETF, and cryptocurrency data, along with key economic indicators. Through integration with AlphaVantage's API and Adafruit IO, users can easily track their chosen assets via a custom dashboard, eliminating the need for constant app refreshing. The display features color-coded performance indicators and scrolling updates for comprehensive market awareness at a glance.

Supplies

Required Hardware

  1. Adafruit Matrix Portal S3
  2. 64x32 RGB LED Matrix Panel
  3. Additional Components:
  4. USB-C cable for programming
  5. 5V 4A (minimum) power supply
  6. Optional: Case or mounting hardware

Required Accounts/API Keys

  1. Alpha Vantage API Key: Free tier available at www.alphavantage.co
  2. Adafruit IO Account: Free account at io.adafruit.com

Set Up S3 Board

Here is a video showing how to set up the Matrix portal.


You'll need to download this version of circuit python, and drop it into your BOOTLOADER mode of the Matrix Portal. To enter boot loader mode, double click the reset button and it will be available in your PC file explorer.


Finally you'll need to download the libraries we use in the program. In order to do this, use the bash command "circup install -a" in the console before the program begins running. In case this doesn't work with a particular library, here is a list of the ones you'll need.

  1. adafruit_matrixportal
  2. adafruit_displayio_layout
  3. adafruit_display_text
  4. adafruit_requests
  5. adafruit_io
  6. neopixel.mpy

Configuring for API Calls and Data Input

In order to (1) Make API calls and (2) Communicate with our board via AdafruitIO, we need to set up our settings.toml file. Before we get further into the build, this would be a good time to create and AdafruitIO account and Alphavantage account if you have not already. Once you have create your accounts, find your API key and adafruitIO username and password (specifically for the dashboard).


  1. For the adafruit IO dashboard, you simply need three feeds "stock-input", "etf-input", "crypto-input". These feed names must be exactly as written n the code and utilize the text feed. Here's some documentation and video to assist.
  2. For Alphavantage, as long as you have you're API key, you are all set.
  3. This sets us up for our settings.toml file. Here's how to configure it.

Create Adafruit Dashboard

To create a dashboard, log in to io.adafruit.com and click on "IO" at the top of the page. Once you are here, you can click "New Dashboard," call the dash board whatever you like, and open it when you are ready. Once in the dashboard click the golden key icon to retrieve your username and password, this will go into the settings.toml file as explained in the previous step. Now:

  1. Click the gear button
  2. Click "Create New Block"
  3. Choose "Text" option
  4. Name the feed "stock-input"
  5. Customize the appearance of the block
  6. Repeat steps 1-4 for two more feeds "etf-input" and "crypto-input"

Your dashboard should look similar to this:

Note: It is very important the feeds are named exactly as described, otherwise the code won't receive input from the dashboard, as it will not detect the feed.

Load the Code

Once you have set up the board, create your accounts, configured your settings.toml file, and set up your dashboard, the code will be plug and play. Simply load it onto your code.py and test it out. I'll try to explain some of the most important bits here.


Initializing Display:

self.matrix = MatrixPortal(
status_neopixel=board.NEOPIXEL,
debug=True,
width=64,
height=32
)

This code snippet will initialize the neopixel matrix display, allowing us to individually address each pixe.


WiFi and Service Connection:

def connect_services(self):
"""Connect to WiFi and initialize services"""
try:
wifi.radio.connect(os.getenv("WIFI_SSID"), os.getenv("WIFI_PASSWORD"))
pool = socketpool.SocketPool(wifi.radio)
self.requests = adafruit_requests.Session(pool, ssl.create_default_context())
self.io = IO_HTTP(self.adafruit_io_username, self.adafruit_io_key, self.requests)
return True
except Exception as e:
print(f"Connection failed: {e}")
return False

With this function we can connect to WiFi using our SSID and password we configured in the settings. We also create a socket pool for network communications. Also, we set up a connection to AdafruitIO to make requests with SSL support.


Retrieving Adafruit IO Feed Data:

def _get_feed_value(self, feed_name):
"""Get latest value from Adafruit IO feed"""
try:
feed = self.io.get_feed(feed_name)
data = self.io.receive_data(feed['key'])
return data['value'] if data else None
except Exception as e:
print(f"Error getting feed {feed_name}: {e}")
return None

This function takes a feed name, one of the names we have created, and retrieves the most recent text input from the feed.


Display Mode Selection and Button Handling:

def handle_buttons(self):
"""Enhanced button handling with API integration"""
change_detected = False
# Handle UP button (mode change)
if self.is_button_pressed(self.button_up, 'up'):
self.clear_display()
self.current_mode = (self.current_mode + 1) % len(self.modes)
self.matrix.set_text(self.modes[self.current_mode])
change_detected = True
# Handle DOWN button (refresh)
if self.is_button_pressed(self.button_down, 'down'):
self.clear_display()
self.matrix.set_text("Refreshing...")
current_mode = self.modes[self.current_mode]
new_data = self.data_handler.update_data(current_mode)
if new_data:
self.update_display(new_data, current_mode)
change_detected = True
return change_detected

This function implements debounced button handling. With this we can use the UP button to cycle through our display of choice (ie. stocks, ETFs, crypto, or indicators). With the DOWN button, we can refresh the API data and text input from adafruit IO.


Data Display Methods:

def show_financial(self, symbol, price, change):
"""Set financial information to display"""
try:
change_value = float(change.strip('%'))
color = COLORS['GREEN'] if change_value >= 0 else COLORS['RED']
except ValueError:
color = COLORS['WHITE']
scroll_text = f"{symbol}: ${price} ({change})"
self.set_scroll_text(scroll_text, color)

This function formats the financial API data for display. We also set a color based on the positive or negative %change. Also, we make this text display scroll.

def show_economic_indicators(self, data):
try:
self.clear_display()
# Create text areas for each indicator
gdp_label = self.matrix.add_text(
text_font=terminalio.FONT,
text_position=(2, 6),
text_scale=1,
text_color=COLORS['RED']
)
# Display formatted data
gdp_data = data['real_gdp']
gdp_t = gdp_data['modern_value'] / 1000
self.matrix.set_text(f"GDP:{gdp_t:.1f}T", gdp_label)
except Exception as e:
print(f"Error in economic indicators: {e}")

This method also formats our API data, except for three economic indicators. Since Alphavantage provides real GDP in 2012 dollars we do some math to make it more user friendly (ie. in 2024 dollars). The function will also create multiple text areas and position them at three vertical levels. Each indicator will get a different color, red, white and blue, because America! While not shown here due to length, the full function will do the same for our other two indicators, quarterly CPI change and interest rates for 10 year federal bonds.


Scrolling Text Implementation:

def update_scroll(self):
"""Update scroll position with bidirectional scrolling"""
current_time = time.monotonic()
if current_time - self.last_scroll_time >= self.scroll_interval:
if self.scroll_text:
# Update position based on direction
self.scroll_position += self.scroll_direction
# Reset if needed
if self.scroll_direction == 1 and self.scroll_position >= len(self.scroll_text):
self.scroll_position = -10
# Calculate visible portion
start_pos = max(0, self.scroll_position)
display_text = self.scroll_text[start_pos:start_pos + 10]
# Add padding if needed
if self.scroll_position < 0:
display_text = " " * abs(self.scroll_position) + self.scroll_text[0:10 + self.scroll_position]
self.matrix.set_text(display_text)
self.last_scroll_time = current_time

This function will implement a smooth scrolling animation to our financial data. It also handles text padding, positioning, timing and resets.


The main loop:

def main():
# Initialize systems
data_handler = DataHandler()
display = IntegratedDisplay(data_handler)
while True:
# Handle button presses and display updates
change_detected = display.handle_buttons()
if change_detected:
current_mode = display.modes[display.current_mode]
stored_data = data_handler.data_store[current_mode]['data']
if stored_data:
display.update_display(stored_data, current_mode)
# Update scroll position for non-economic modes
if display.current_mode != 3:
display.update_scroll()
time.sleep(0.01)

This function will first initialize our display and data handler. It will then start a loop that is continuously check for button presses. When buttons are pressed, the loop will handle display animation changes, either to our financial data or economic indicator.

Here Is How It Is Supposed to Work

Final Project Demonstration for Physical Computing and Robotics at Boston College