Tech Tutorial - February 24 2026 053007
Welcome to today’s deep‑dive tutorial! We’ll walk through building a real‑time stock ticker dashboard that streams live price updates straight to a browser using FastAPI, WebSockets, and Plotly. By the end of this guide you’ll have a fully functional web app that can be deployed on any cloud provider, and you’ll understand the core concepts that power modern real‑time applications.
Real‑time data is everywhere—from financial markets to IoT sensors—and delivering it efficiently requires a blend of asynchronous server logic and lightweight client rendering. FastAPI’s async capabilities make it a perfect match for WebSocket communication, while Plotly’s JavaScript library lets us create interactive charts with minimal code. Let’s start by setting up a clean development environment.
Prerequisites & Environment Setup
Make sure you have Python 3.11 or newer installed, along with pip. We’ll also need uvicorn for serving FastAPI, fastapi itself, and websockets for low‑level testing. On the frontend side, Plotly.js will be loaded from a CDN, so no extra npm packages are required.
Open a terminal and run the following commands to create a virtual environment and install the necessary packages:
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install fastapi uvicorn websockets pandas yfinance
We’ll use yfinance to fetch live stock data for demonstration purposes. Feel free to replace it with any market data API you prefer.
Creating the FastAPI Backend
The backend will expose two endpoints: a traditional REST route for fetching the latest snapshot and a WebSocket route that pushes continuous updates. Let’s start with the basic FastAPI app skeleton.
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
import yfinance as yf
import asyncio
import json
app = FastAPI()
# In‑memory store for connected websockets
connections: set[WebSocket] = set()
@app.get("/price/{symbol}")
async def get_price(symbol: str):
"""Return the latest price for a given ticker symbol."""
ticker = yf.Ticker(symbol)
data = ticker.history(period="1d", interval="1m").iloc[-1]
return {"symbol": symbol.upper(), "price": round(data["Close"], 2)}
The /price/{symbol} endpoint is useful for initial page loads or for clients that prefer polling over push. Next, we’ll add the WebSocket handler that continuously streams price updates.
@app.websocket("/ws/{symbol}")
async def websocket_endpoint(websocket: WebSocket, symbol: str):
await websocket.accept()
connections.add(websocket)
try:
while True:
ticker = yf.Ticker(symbol)
data = ticker.history(period="1d", interval="1m").iloc[-1]
payload = {
"symbol": symbol.upper(),
"price": round(data["Close"], 2),
"timestamp": data.name.isoformat()
}
await websocket.send_text(json.dumps(payload))
await asyncio.sleep(1) # push updates every second
except WebSocketDisconnect:
connections.remove(websocket)
Notice the use of asyncio.sleep to throttle the feed—sending a message every second strikes a good balance between freshness and bandwidth consumption.
Pro tip: If you anticipate many concurrent users, consider moving the data fetching logic to a background task that writes to a Redis pub/sub channel. Then each WebSocket can simply subscribe to Redis, dramatically reducing API calls to the external data source.
Running the Server
Save the code above as main.py and launch the server with Uvicorn. The --reload flag is handy during development because it automatically restarts the server on file changes.
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
Visit http://localhost:8000/docs to explore the automatically generated OpenAPI UI. You’ll see both the REST endpoint and the WebSocket route listed—FastAPI makes testing WebSockets as easy as clicking a button.
Building the Frontend with Plotly
The client side will consist of a simple HTML page that opens a WebSocket connection, receives price updates, and feeds them into a Plotly line chart. This approach keeps the frontend lightweight and framework‑agnostic.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Real‑Time Stock Ticker</title>
<script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 2rem; }
#chart { width: 100%; height: 500px; }
</style>
</head>
<body>
<h1>Live {{symbol}} Price</h1>
<div id="chart"></div>
<script>
const symbol = "AAPL"; // change to any ticker you like
const ws = new WebSocket(`ws://${location.host}/ws/${symbol}`);
const timestamps = [];
const prices = [];
const layout = {
title: `${symbol} Live Price`,
xaxis: { title: "Time" },
yaxis: { title: "Price (USD)" }
};
Plotly.newPlot('chart', [{ x: timestamps, y: prices, mode: 'lines', name: symbol }], layout);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
timestamps.push(new Date(data.timestamp));
prices.push(data.price);
// Keep only the last 60 points (1 minute of data)
if (timestamps.length > 60) {
timestamps.shift();
prices.shift();
}
Plotly.update('chart', { x: [timestamps], y: [prices] });
};
ws.onclose = () => console.warn("WebSocket connection closed");
</script>
</body>
</html>
The script establishes a WebSocket connection as soon as the page loads, then continuously appends new data points to the chart. By limiting the array to the latest 60 entries we keep the visualization responsive.
Why Plotly?
Plotly provides out‑of‑the‑box interactivity—zoom, pan, and hover tooltips—without any extra configuration. It also supports real‑time updates via the Plotly.update method, which is perfect for streaming data.
Testing the End‑to‑End Flow
With the server running and the HTML file saved as index.html, open the file in a browser (or serve it via a simple static server). You should see a line chart that begins to move as price updates arrive. If you open the browser console you’ll notice a steady stream of JSON messages arriving from the backend.
To verify the REST endpoint, you can use curl or your browser:
curl http://localhost:8000/price/AAPL
# {"symbol":"AAPL","price":172.34}
Both the WebSocket and the REST route fetch data from the same yfinance source, ensuring consistency across the app.
Pro tip: When testing locally, disable any corporate proxy that might interfere with WebSocket connections. If you see “WebSocket is closed before the connection is established,” it’s often a proxy issue.
Scaling Considerations
While the demo works great on a single machine, production deployments need to handle many simultaneous connections. Here are three strategies to scale gracefully:
- Horizontal scaling with a load balancer: Deploy multiple FastAPI instances behind an Nginx or HAProxy layer. Use sticky sessions or a shared pub/sub system (Redis, RabbitMQ) to broadcast updates to all workers.
- Background data collector: Instead of each WebSocket pulling data from
yfinance, run a single async task that fetches prices every second and pushes them to a channel. This reduces third‑party API rate‑limit pressure. - Containerization: Package the app in a Docker image, define health checks, and orchestrate with Kubernetes. Autoscaling can then react to CPU or network metrics.
Implementing these patterns early saves you from costly refactors later, especially when you move from a hobby project to a SaaS product.
Deploying to the Cloud
FastAPI works seamlessly with most cloud platforms. Below is a quick guide for deploying to Render, which offers free web services with automatic HTTPS.
- Push your code to a GitHub repository.
- Create a new “Web Service” on Render and link the repo.
- Set the build command to
pip install -r requirements.txtand the start command touvicorn main:app --host 0.0.0.0 --port $PORT. - Enable “Always On” if you need the WebSocket to stay alive after idle periods.
After a few minutes Render will provide a public URL like https://stock-dashboard.onrender.com. Update the WebSocket URL in your HTML to use wss:// instead of ws:// for secure communication.
Pro tip: Cloud providers often terminate idle connections after 5‑10 minutes. To keep WebSockets alive, send a ping message from the client every 30 seconds using ws.send('ping') and handle it on the server side.
Extending the Dashboard
Now that you have a working prototype, consider adding these enhancements to make the app production‑ready:
- Multiple symbols: Allow users to input a comma‑separated list of tickers and render a separate trace for each.
- Historical zoom: Load the past hour’s data via the REST endpoint and let Plotly’s built‑in range selector handle zooming.
- Alert system: Trigger browser notifications when a price crosses a user‑defined threshold.
- Authentication: Secure the WebSocket endpoint with JWT tokens if you plan to expose premium data.
Each of these features can be added incrementally without breaking the existing architecture, thanks to the clear separation between data ingestion (backend) and visualization (frontend).
Performance Monitoring
Monitoring latency and error rates is crucial for a real‑time app. FastAPI integrates well with Prometheus; simply add the prometheus-fastapi-instrumentator library and expose a /metrics endpoint.
from prometheus_fastapi_instrumentator import Instrumentator
instrumentator = Instrumentator()
instrumentator.instrument(app).expose(app)
Grafana dashboards can then visualize WebSocket connection counts, message latency, and CPU usage. Set alerts to fire if the average round‑trip time exceeds 200 ms.
Pro tip: Useuvicorn --log-level debuglocally to see detailed connection lifecycle logs. In production, switch tolog-level infoand ship logs to a centralized system like Loki or ELK.
Conclusion
We’ve built a full‑stack real‑time stock ticker using FastAPI, WebSockets, and Plotly, explored scaling patterns, and touched on deployment and monitoring best practices. The same architecture applies to any scenario where low‑latency streaming is required—chat applications, live sports scores, or IoT dashboards.
Remember, the magic lies in keeping the data pipeline asynchronous, offloading heavy work to background tasks, and letting the client handle rendering. With these principles in your toolbox, you’re ready to tackle more ambitious real‑time projects on Codeyaan and beyond.