Introduction
A stock dashboard is the project that makes data engineering feel real: live market data in, decisions out. Using yfinance — a free library wrapping Yahoo Finance data — you get years of price history for any ticker in one call, and this app turns it into a dashboard with interactive charts, moving averages, and multi-ticker comparison.
It is the grown-up version of the Pandas dashboard: same filtering and charting patterns, but the data updates itself from the real world. The caching strategy borrows directly from the currency converter.
Features
- Any ticker — AAPL, GOOGL, TSLA, or your favorites via text input.
- Period selector — 1 month to 5 years of history.
- Price chart — closing prices with 20/50-day moving averages.
- Volume bars — trading volume under the price line.
- Key stats row — latest price, 52-week high/low, average volume.
- Compare mode — normalized performance of multiple tickers.
Prerequisites
- Python 3.8+ — from python.org.
- Dependencies:
pip install streamlit yfinance pandas
Step 1: Create the Script
Save as stock_dashboard.py:
import streamlit as st
import yfinance as yf
import pandas as pd
st.set_page_config(page_title="Stock Dashboard", page_icon="📈", layout="wide")
st.title("📈 Stock Price Dashboard")
tickers_raw = st.text_input("Tickers (comma-separated)", value="AAPL, MSFT")
period = st.selectbox("Period", ["1mo", "3mo", "6mo", "1y", "2y", "5y"], index=3)
show_ma = st.checkbox("Show moving averages", value=True)
tickers = [t.strip().upper() for t in tickers_raw.split(",") if t.strip()]
@st.cache_data(ttl=600)
def load_history(ticker: str, period: str) -> pd.DataFrame:
df = yf.download(ticker, period=period, progress=False)
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
return df
if tickers:
if len(tickers) == 1:
df = load_history(tickers[0], period)
if df.empty:
st.error(f"No data for {tickers[0]} — check the ticker symbol.")
st.stop()
latest = df["Close"].iloc[-1]
c1, c2, c3, c4 = st.columns(4)
c1.metric(tickers[0], f"${latest:,.2f}")
c2.metric("52w high", f"${df['Close'].max():,.2f}")
c3.metric("52w low", f"${df['Close'].min():,.2f}")
c4.metric("Avg volume", f"{df['Volume'].mean() / 1e6:,.1f}M")
plot_df = df[["Close"]].copy()
if show_ma:
plot_df["MA20"] = df["Close"].rolling(20).mean()
plot_df["MA50"] = df["Close"].rolling(50).mean()
st.line_chart(plot_df)
st.bar_chart(df["Volume"])
else:
st.subheader("Normalized performance (start = 100)")
combined = pd.DataFrame()
for t in tickers:
close = load_history(t, period)["Close"]
if not close.empty:
combined[t] = close / close.iloc[0] * 100
st.line_chart(combined)
st.caption("100 = first day of the selected period, so lines compare returns fairly.")
else:
st.info("Enter at least one ticker above.")
Step 2: Run the App
streamlit run stock_dashboard.py
Try AAPL alone, then AAPL, MSFT, GOOGL together — compare mode shows which investment actually grew faster.
How It Works
yf.download() returns a DataFrame indexed by date with Open/High/Low/Close/Volume columns. Streamlit’s st.line_chart renders date-indexed frames natively — the x-axis, gridlines, and hover values come free. This is the core advantage of keeping data in Pandas shape end-to-end, exactly as in the CSV explorer.
Moving averages are one-liners — .rolling(20).mean() — yet they transform the chart, smoothing daily noise into trend lines traders actually watch. The 20/50 pair is the classic combination: short-term momentum crossing long-term trend.
Compare mode solves a subtle visualization problem: raw prices can’t share an axis (a $3,000 stock dwarfs a $50 one). Normalizing each series to start at 100 (close / close.iloc[0] * 100) turns prices into returns, making relative growth instantly readable — a trick worth stealing for any multi-series chart.
@st.cache_data(ttl=600) caches each ticker-period pair for ten minutes: instant reruns while you tweak charts, fresh-enough data for daily analysis.
Common Errors & Fixes
- Empty DataFrame for a valid-looking ticker — Yahoo uses symbols like
BRK-B(dash) notBRK.B(dot); check the exact symbol on Yahoo Finance. MultiIndexcolumn errors — recent yfinance versions return multi-level columns for single tickers; theget_level_values(0)flattening in the loader handles it.- Rate limiting (429 errors) — too many uncached calls; keep the
ttlcache and load tickers in a loop after checking the cache, never in a tight retry loop. - MA lines missing at the chart start — expected: a 50-day average needs 50 days of history before its first value; those rows are
NaNand simply don’t plot.
Key Concepts
- Date-indexed DataFrames — free, correct time-axis charts.
- Rolling windows —
.rolling(n).mean()as trend smoothing. - Normalization for comparison — index to 100 for fair multi-series charts.
- TTL caching on external data — fresh enough, fast enough.
What to Try Next
- Add candlesticks with
plotly.graph_objects.Candlestickfor OHLC detail. - Add RSI or MACD indicators — both are rolling-window math you already know.
- Log daily snapshots to CSV to build your own watchlist history, like the habit tracker.
- Add a dividend/split marker using
yf.Ticker(t).actions.
FAQ
Is yfinance data real-time?
Close — prices are delayed up to 15 minutes for many exchanges, which is fine for dashboards but not for day trading.
Does this cost anything?
No — Yahoo’s public data endpoints are free; yfinance just packages them. For production systems, use a paid API with an SLA.
Why normalize in compare mode instead of plotting raw prices?
Because the question is which grew more, not which costs more. Indexing to 100 answers the first question; raw prices visually answer the wrong one.