DevelopmentJune 23, 20254 min read

Stock Price Dashboard using Python, Streamlit, and yfinance

Build a live stock dashboard with Python, Streamlit, and yfinance — candlestick-style charts, moving averages, and ticker comparison.

Galvan

Galvan

Founder & Creator

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:
  • code
    pip install streamlit yfinance pandas

    Step 1: Create the Script

    Save as stock_dashboard.py:

    code
    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

    code
    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) not BRK.B (dot); check the exact symbol on Yahoo Finance.
  • `MultiIndex` column errors — recent yfinance versions return multi-level columns for single tickers; the get_level_values(0) flattening in the loader handles it.
  • Rate limiting (429 errors) — too many uncached calls; keep the ttl cache 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 NaN and 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.Candlestick for 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.