What your competitors are learning at NVIDIA GTC

Learn more

Tutorial

How to build a real-time stock watchlist with Redis

March 19, 202615 minute read
William Johnston
William Johnston
TL;DR:
Build a real-time stock watchlist that uses Redis for JSON-backed stock docs, Redis Search search, time series for trades and price bars, sets for the watchlist, Top-K for trending symbols, and pub/sub for WebSocket fanout. The app runs with FastAPI, Next.js, and Docker in replay mode — no API keys required.
Note: This tutorial uses the code from the following git repository:

#What you'll learn

  • How to store stock metadata as JSON docs in Redis and index them with Redis Search
  • How to use Redis sets to manage a watchlist
  • How to record trades and OHLCV price bars with Redis time series
  • How to track trending symbols with a Top-K probabilistic data structure
  • How to use pub/sub to push real-time updates over WebSockets
  • How to combine six Redis data structures in one app without external stores

#What you'll build

You'll build a four-service stock watchlist app:
  • A data loader that reads NASDAQ symbols from CSV and stores them as JSON docs in Redis
  • A FastAPI server that exposes REST and WebSocket endpoints backed by Redis
  • A stream service that generates market data (replay mode by default, or live via Alpaca)
  • A Next.js frontend that renders a watchlist, price chart, news feed, and trending panel
The default flow uses replay mode, so you can run the full app with Docker and no external API keys.

#What is a real-time stock watchlist?

A stock watchlist tracks a set of symbols a user cares about and shows live price changes, charts, and news. "Real-time" means the UI updates as new trades and bars arrive, without polling or page refreshes.
The challenge is keeping all of that state — the symbol catalog, the user's watchlist, price history, trending rankings, and push notifications — fast and consistent. That is where Redis fits.

#Why use Redis for a real-time stock watchlist?

This app needs six kinds of state, and Redis handles all of them in one fast data layer:
  • JSON stores each stock as a structured document with symbol, name, sector, industry, and news
  • Redis Search indexes those docs so the UI can search by symbol prefix
  • Sets track which symbols are on the user's watchlist
  • Time series records every trade price and OHLCV bar with millisecond timestamps
  • Top-K maintains a probabilistic leaderboard of the most-traded symbols
  • Pub/sub notifies the API server when new data arrives so it can push updates over WebSockets
Using one data layer for all six keeps the architecture simple. You don't need a separate search engine, a message broker, a time series database, and a document store — Redis does all of it.

#How does the app work?

The app has four services that all connect to one Redis instance:
The read path looks like this:
  1. The UI opens three WebSocket connections (trades, bars, trending).
  2. The stream service writes market data to Redis time series and publishes events.
  3. The API server receives pub/sub messages and pushes updates to the UI over WebSockets.
  4. The UI re-renders prices, charts, and the trending panel in real time.

#Prerequisites

  • Docker and Docker Compose
  • Git
  • Basic familiarity with Redis commands and Python
If you need a Redis refresher first, start with the Redis quick start and Python client guide.

#Step 1. Clone the repo

#Step 2. Configure environment variables

Copy the sample file:
The defaults work for replay mode. No API keys are needed.

#Step 3. Run the app with Docker

Once all four containers are healthy:
  • UI: http://localhost:3000
  • API: http://localhost:8000/api/1.0
  • Redis: redis://localhost:6379
Open the UI, click the demo button, and the watchlist populates with sample symbols. The stream service starts generating synthetic trades and bars immediately.

#How does the app store stock docs in Redis?

The data loader reads NASDAQ symbols from a CSV file and stores each one as a JSON doc in Redis. Each doc contains the symbol, company name, sector, industry, and an empty news array.
The key pattern is stocks:{SYMBOL}, so Apple is stored at stocks:AAPL.
Before saving any docs, the data loader creates a Redis Search index so the API can search stocks by symbol, name, sector, or industry:
This creates an index named stocks:index over every JSON doc with the stocks: prefix. The sortable=True flag on symbol lets the search results come back in alphabetical order.

#How does the app search stocks with Redis Search?

When a user types into the search bar, the UI sends the query to the API, which runs a prefix search against the index:
The @symbol:{normalized}* query matches any symbol that starts with the user's input. If the user types "AA", the search returns AAPL, AAL, AACG, and every other symbol starting with AA — sorted alphabetically and capped at 100 results.
Under the hood, this maps to the Redis command:

#How does the app track the watchlist with Redis sets?

The watchlist is a Redis set stored at the key watchlist. Adding and removing symbols uses SADD and SREM:
Sets guarantee uniqueness — you can't add the same symbol twice. SISMEMBER checks membership in O(1), SADD adds in O(1), and SMEMBERS returns all members when the API needs the full watchlist.
After every add or remove, the API publishes a watchlist-updated event so the stream service knows to start or stop generating data for that symbol.

#How does the app store time series data?

Every trade and price bar goes into Redis time series. The stream service creates seven time series keys per symbol:
Each key uses TS.CREATE with a duplicate_policy of last so the most recent value wins if two data points land at the same timestamp. The labels parameter tags each key with its symbol, which is useful for cross-key queries with TS.MGET.
When a trade arrives, the service writes the price and size to their respective time series with TS.MADD:
Bars work the same way, writing open, high, low, close, and volume in a single TS.MADD call:
The API server reads this data back with TS.RANGE to populate the price chart:
This returns the last 30 close prices in the given time window — enough to render a sparkline or candlestick chart.

#How does the app track trending symbols with Top-K?

Redis Top-K is a probabilistic data structure that tracks the most frequently seen items without storing every occurrence. The app uses it to show the 12 most-traded symbols.
At startup, the stream service reserves a Top-K sketch:
The parameters are: keep the top 12 items, with a width of 50, depth of 4, and decay of 0.9. The decay factor controls how fast old observations lose weight, so the trending list reflects recent activity.
Every time a trade is recorded, the service adds the symbol to the Top-K:
The API reads the current leaderboard with TOPK.LIST:
Top-K is a good fit here because you don't need exact counts — you need a fast, memory-efficient ranking that updates on every trade.

#How does pub/sub drive real-time updates?

The stream service publishes an event on three channels every time it processes market data:
The API server subscribes to these channels inside WebSocket route handlers. When a message arrives, the handler reads the latest data from Redis and pushes it to the connected browser:
This pattern — publish from the stream service, subscribe in the API server, push over WebSocket — keeps the UI in sync without polling. The browser opens three WebSocket connections when the page loads, and every trade, bar, and trending update flows through in real time.

#How does Redis store the full data model?

Every piece of state in the app lives in Redis. Here is the full key structure:

#Optional: live mode with Alpaca

Replay mode is the default. To switch the stream service to live Alpaca data, set these in your .env:
In live mode, the stream service connects to the Alpaca WebSocket, subscribes to real trades and bars for every symbol on the watchlist, and writes them to Redis with the same time series and pub/sub pattern. The API and UI work identically — they don't know whether the data is replayed or live.

#Troubleshooting

#The app starts but returns a Redis error

Check that REDIS_URL in your .env file points to a running Redis instance. If you're using Docker, verify the container is healthy:

#The UI loads but shows no data

Make sure all four containers are running. The data loader must finish before the API starts serving requests. Check the data container logs:

#Time series queries return empty results

The stream service needs at least a few seconds to generate data. Wait for the stream container to start, then refresh the UI. You can check whether time series data exists:

#Docker Compose fails to start

Make sure Docker is running and that ports 3000, 6379, and 8000 are not already in use.

#Next steps

#FAQ

#What is Redis time series?

Redis time series is a data type purpose-built for ingesting, querying, and aggregating timestamped data. It supports built-in downsampling, aggregation, and labeling. This app uses it to store trade prices and OHLCV bars with millisecond-precision timestamps.

#What is Top-K in Redis?

Top-K is a probabilistic data structure that tracks the K most frequently seen items in a stream of events. It uses a count-min sketch with a decay factor, so it reflects recent activity without storing every observation. This app uses it to show the 12 most-traded symbols.

#How does pub/sub work in Redis?

Pub/sub lets one part of your app publish messages to a channel and another part subscribe to that channel. Messages are delivered in real time to all active subscribers. This app uses pub/sub to notify the API server when new market data arrives, so it can push updates over WebSockets.

#Can Redis handle real-time stock data?

Yes. Redis processes commands in microseconds and handles hundreds of thousands of writes per second. Time series, pub/sub, and JSON are all designed for high-throughput, low-latency workloads — exactly what a real-time market data pipeline needs.

#Why use Redis sets for the watchlist?

Sets guarantee uniqueness and provide O(1) membership checks. When a user adds a symbol, SISMEMBER confirms it's not already there and SADD adds it — both in constant time. Sets also give you SMEMBERS to read the full watchlist in one call.

#Why use JSON instead of hashes for stock docs?

JSON lets you store nested structures like the news array directly inside the document. Hashes are flat key-value maps, so you'd need a separate key for each stock's news list. JSON also integrates with Redis Search for full-text search over nested fields.

#Do I need Alpaca credentials to follow this tutorial?

No. The default mode is replay, which generates synthetic market data from a bundled fixture file. You can switch to live Alpaca data later by setting MARKET_DATA_MODE=live and adding your credentials.

#Do I need Redis Cloud?

No. The Docker flow uses redis:alpine locally. You can switch to Redis Cloud later by updating REDIS_URL in your .env file.

#How do I add more symbols to the watchlist?

Use the search bar in the UI to find a symbol and click to add it. Or call the API directly:

#How does the replay mode generate data?

The replay service reads a base price for each symbol from a fixture file, applies a sine-wave delta on each tick, and writes the resulting trade and bar to Redis time series. It also publishes events on the trade, bar, and trending-stocks channels so the WebSocket flow works identically to live mode.

#Additional resources