Tutorial Updated March 14, 2026

Build a Stock Dashboard in Python with Free API [2026 Guide]

A complete, step-by-step tutorial. Build a real-time stock dashboard with Python using Streamlit, Flask+Chart.js, or Dash by Plotly — fetching live data from the MarketLens API. Portfolio tracking, watchlists, technical indicators, and news feed included. Everything runs on the free tier (500 calls/day).

Related guides: Quickstart Guide · API Reference (222+ endpoints) · Build a Stock Screener · Build a Trading Bot · Python SDK

By MarketLens Team Updated March 14, 2026 25 min read 222+ endpoints Used by 500+ developers

Prerequisites

  • Python 3.8+ installed
  • pip (comes with Python)
  • A free MarketLens API key (500 calls/day, no credit card)
  • Basic knowledge of Python and HTML

What you'll build in this tutorial:

Real-time stock price dashboard (Flask+Chart.js)
Streamlit dashboard with live charts
Portfolio tracking with P&L
Watchlist with auto-refresh
Technical indicators (RSI, MACD, Bollinger)
Dash by Plotly alternative
News feed integration
Live API demo (try in your browser)

Free stock data with 500 calls/day

No credit card required. Use code FOUNDER50 for 50% off Pro when you need higher limits.

Get Free API Key →
1

Project Setup

Create a project folder and install the dependencies:

Bash
mkdir stock-dashboard && cd stock-dashboard
python -m venv venv
source venv/bin/activate   # Windows: venv\Scripts\activate

pip install flask requests python-dotenv

Create a .env file for your API key:

.env
MARKETLENS_API_KEY=your_api_key_here

Get your free key at marketlens.dev/checkout. See our API reference for all available endpoints.

2

Fetch Stock Data

Let's create a helper module to fetch data from the MarketLens API. We'll use two endpoints: real-time quotes and historical data.

Python — api_client.py
import os
import requests
from dotenv import load_dotenv

load_dotenv()

BASE_URL = "https://marketlens.dev/api/v1"
API_KEY = os.getenv("MARKETLENS_API_KEY")
HEADERS = {"X-API-Key": API_KEY}


def get_quote(symbol: str) -> dict:
    """Fetch real-time stock quote."""
    resp = requests.get(
        f"{BASE_URL}/stocks/quote",
        headers=HEADERS,
        params={"symbol": symbol},
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()


def get_history(symbol: str, range: str = "3mo") -> list:
    """Fetch historical OHLCV data."""
    resp = requests.get(
        f"{BASE_URL}/stocks/history",
        headers=HEADERS,
        params={"symbol": symbol, "range": range},
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json().get("history", [])


def get_multiple_quotes(symbols: list[str]) -> list[dict]:
    """Fetch quotes for multiple symbols."""
    return [get_quote(s) for s in symbols]

This uses the /stocks/quote and /stocks/history endpoints. Check our docs for the full response schemas.

3

Build the Flask Application

Now let's create the Flask app with routes for the dashboard page and a JSON API endpoint for AJAX updates.

Python — app.py
from flask import Flask, render_template, jsonify
from api_client import get_quote, get_history, get_multiple_quotes

app = Flask(__name__)

# Watchlist — customize these symbols
WATCHLIST = ["AAPL", "GOOGL", "MSFT", "TSLA", "AMZN", "NVDA"]


@app.route("/")
def dashboard():
    """Render the main dashboard page."""
    quotes = get_multiple_quotes(WATCHLIST)
    return render_template("dashboard.html", quotes=quotes, symbols=WATCHLIST)


@app.route("/api/quotes")
def api_quotes():
    """JSON endpoint for AJAX auto-refresh."""
    quotes = get_multiple_quotes(WATCHLIST)
    return jsonify(quotes)


@app.route("/api/history/<symbol>")
def api_history(symbol):
    """JSON endpoint for chart data."""
    history = get_history(symbol, range="3mo")
    return jsonify(history)


if __name__ == "__main__":
    app.run(debug=True, port=5000)

The dashboard route fetches all quotes on page load. The two API routes serve JSON for the auto-refresh and chart updates via JavaScript.

4

Add Chart.js Visualizations

Create the HTML template at templates/dashboard.html. This uses Chart.js for line charts and a card grid for the watchlist.

HTML — templates/dashboard.html
<!DOCTYPE html>
<html>
<head>
  <title>Stock Dashboard</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body class="bg-gray-900 text-white p-8">
  <h1 class="text-3xl font-bold mb-6">Stock Dashboard</h1>

  <!-- Watchlist Cards -->
  <div id="watchlist" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-8">
    {% for q in quotes %}
    <div class="bg-gray-800 rounded-lg p-4 cursor-pointer hover:ring-2 hover:ring-green-500"
         onclick="loadChart('{{ q.symbol }}')">
      <div class="text-sm text-gray-400">{{ q.symbol }}</div>
      <div class="text-xl font-bold">${{ "%.2f"|format(q.price) }}</div>
      <div class="text-sm {{ 'text-green-400' if q.change >= 0 else 'text-red-400' }}">
        {{ "%+.2f"|format(q.change) }} ({{ "%+.1f"|format(q.change_percent) }}%)
      </div>
    </div>
    {% endfor %}
  </div>

  <!-- Price Chart -->
  <div class="bg-gray-800 rounded-lg p-6">
    <h2 id="chart-title" class="text-xl font-semibold mb-4">AAPL — 3 Month</h2>
    <canvas id="priceChart" height="100"></canvas>
  </div>

  <script>
    let chart = null;

    async function loadChart(symbol) {
      const resp = await fetch(`/api/history/${symbol}`);
      const data = await resp.json();
      const labels = data.map(d => d.date);
      const prices = data.map(d => d.close);

      document.getElementById('chart-title').textContent =
        `${symbol} — 3 Month`;

      if (chart) chart.destroy();
      chart = new Chart(document.getElementById('priceChart'), {
        type: 'line',
        data: {
          labels,
          datasets: [{
            label: symbol,
            data: prices,
            borderColor: '#10b981',
            backgroundColor: 'rgba(16,185,129,0.1)',
            fill: true, tension: 0.3,
          }],
        },
        options: {
          responsive: true,
          plugins: { legend: { display: false } },
          scales: {
            x: { ticks: { color: '#94a3b8' }, grid: { color: '#1e293b' } },
            y: { ticks: { color: '#94a3b8', callback: v => '$' + v },
                 grid: { color: '#1e293b' } },
          },
        },
      });
    }

    // Load AAPL chart on page load
    loadChart('AAPL');
  </script>
</body>
</html>

Click any stock card to load its 3-month chart. The Chart.js line chart auto-scales and displays the green MarketLens brand color.

5

Add Auto-Refresh

Add this script to your dashboard template to poll for new quotes every 30 seconds. This keeps prices live without a full page reload.

JavaScript
// Auto-refresh quotes every 30 seconds
async function refreshQuotes() {
  try {
    const resp = await fetch('/api/quotes');
    const quotes = await resp.json();
    const cards = document.getElementById('watchlist').children;

    quotes.forEach((q, i) => {
      if (cards[i]) {
        const priceEl = cards[i].querySelector('.text-xl');
        const changeEl = cards[i].querySelector('.text-sm:last-child');
        priceEl.textContent = `$${q.price.toFixed(2)}`;

        const color = q.change >= 0 ? 'text-green-400' : 'text-red-400';
        changeEl.className = `text-sm ${color}`;
        changeEl.textContent =
          `${q.change >= 0 ? '+' : ''}${q.change.toFixed(2)} ` +
          `(${q.change_percent >= 0 ? '+' : ''}${q.change_percent.toFixed(1)}%)`;
      }
    });
  } catch (err) {
    console.error('Refresh failed:', err);
  }
}

// Poll every 30 seconds (uses ~2 API calls/min = ~120/hour)
setInterval(refreshQuotes, 30000);

With 6 stocks refreshing every 30 seconds, you'll use about 12 API calls per minute — well within the free 500/day limit during a standard work session. See our pricing for higher-volume needs.

6

Why MarketLens vs Yahoo Finance Scraping

Many tutorials suggest scraping Yahoo Finance with yfinance. Here's why that's a bad idea for production dashboards:

Aspect Yahoo Finance (yfinance) MarketLens API
StabilityBreaks when Yahoo changes HTMLStable REST API, versioned
Rate limits429 errors, IP bans500 calls/day free, clear limits
LegalViolates Yahoo ToSFully licensed data
Data scopeStocks onlyStocks + crypto + forex + economic + predictions
Response time1-5 seconds<200ms (cached)
DocumentationUndocumented scraping222+ documented endpoints
7

Deploy with Gunicorn

For production, use gunicorn instead of Flask's built-in server:

Bash
pip install gunicorn

# Run with 2 workers
gunicorn app:app --bind 0.0.0.0:8000 --workers 2

# Or create a Procfile for Render.com / Railway
echo "web: gunicorn app:app --bind 0.0.0.0:\$PORT --workers 2" > Procfile

Deploy to Render.com free tier, Railway, or any VPS. Set MARKETLENS_API_KEY as an environment variable. Need more calls? Pro plans start at $29/mo.

8

Build a Streamlit Dashboard (Fastest Approach)

Streamlit is the fastest way to build a stock dashboard in Python — you can have a full-featured dashboard in under 100 lines. No HTML, no CSS, no JavaScript required.

Bash
pip install streamlit requests plotly pandas
Python — streamlit_dashboard.py
import streamlit as st
import requests
import plotly.graph_objects as go
import pandas as pd

API_KEY = st.secrets.get("MARKETLENS_API_KEY", "your_api_key_here")
BASE = "https://marketlens.dev/api/v1"
HEADERS = {"X-API-Key": API_KEY}

st.set_page_config(page_title="Stock Dashboard", layout="wide")
st.title("📈 Real-Time Stock Dashboard")

# Sidebar — Watchlist
st.sidebar.header("Watchlist")
symbols = st.sidebar.text_input("Symbols (comma-separated)", "AAPL,GOOGL,MSFT,TSLA,NVDA")
symbol_list = [s.strip().upper() for s in symbols.split(",")]

# Fetch quotes for all symbols
col1, col2, col3, col4, col5 = st.columns(5)
cols = [col1, col2, col3, col4, col5]

for i, sym in enumerate(symbol_list[:5]):
    resp = requests.get(f"{BASE}/stocks/{sym}/quote", headers=HEADERS, timeout=10)
    if resp.ok:
        q = resp.json().get("data", {})
        price = q.get("price", 0)
        change = q.get("change_percent", 0)
        with cols[i]:
            st.metric(sym, f"${price:.2f}", f"{change:+.2f}%")

# Chart — select a symbol
selected = st.selectbox("Select stock for chart:", symbol_list)

# Fetch historical data
resp = requests.get(f"{BASE}/stocks/{selected}/historical",
                    headers=HEADERS,
                    params={"range": "3mo", "interval": "1d"},
                    timeout=10)
if resp.ok:
    data = resp.json().get("data", {}).get("prices", [])
    df = pd.DataFrame(data)

    fig = go.Figure(data=[go.Candlestick(
        x=df["date"], open=df["open"], high=df["high"],
        low=df["low"], close=df["close"]
    )])
    fig.update_layout(title=f"{selected} — 3 Month",
                     template="plotly_dark",
                     xaxis_rangeslider_visible=False)
    st.plotly_chart(fig, use_container_width=True)

# Auto-refresh every 30 seconds
if st.button("🔄 Refresh Data"):
    st.rerun()

Run it:

Bash
streamlit run streamlit_dashboard.py

Deploy to Streamlit Cloud for free — just connect your GitHub repo and add MARKETLENS_API_KEY to secrets.

9

Alternative: Dash by Plotly

Dash is ideal if you want interactive Plotly charts with callbacks. Here's a minimal dashboard:

Bash
pip install dash requests pandas
Python — dash_dashboard.py
from dash import Dash, html, dcc, callback, Output, Input
import plotly.graph_objects as go
import requests
import pandas as pd

API_KEY = "your_api_key_here"
BASE = "https://marketlens.dev/api/v1"
HEADERS = {"X-API-Key": API_KEY}

app = Dash(__name__)
app.layout = html.Div([
    html.H1("Stock Dashboard", style={"color": "white"}),
    dcc.Dropdown(
        id="symbol-dropdown",
        options=[{"label": s, "value": s}
                 for s in ["AAPL", "GOOGL", "MSFT", "TSLA", "NVDA"]],
        value="AAPL",
        style={"width": "200px", "marginBottom": "20px"}
    ),
    dcc.Graph(id="price-chart"),
    dcc.Interval(id="refresh", interval=30*1000, n_intervals=0)
], style={"backgroundColor": "#0f172a", "padding": "2rem"})


@callback(Output("price-chart", "figure"),
          Input("symbol-dropdown", "value"),
          Input("refresh", "n_intervals"))
def update_chart(symbol, _):
    resp = requests.get(f"{BASE}/stocks/{symbol}/historical",
                        headers=HEADERS,
                        params={"range": "3mo", "interval": "1d"})
    data = resp.json().get("data", {}).get("prices", [])
    df = pd.DataFrame(data)

    fig = go.Figure(data=[go.Candlestick(
        x=df["date"], open=df["open"], high=df["high"],
        low=df["low"], close=df["close"]
    )])
    fig.update_layout(title=f"{symbol} — 3 Month",
                     template="plotly_dark",
                     xaxis_rangeslider_visible=False)
    return fig


if __name__ == "__main__":
    app.run(debug=True, port=8050)

Dash auto-refreshes every 30 seconds via dcc.Interval. The callback pattern makes it easy to add more panels and controls.

Need more API calls for your dashboard?

Free: 500 calls/day Pro $29/mo: 10,000 calls/day Enterprise $99/mo: 50,000 calls/day
Try It Free — 500 calls/day →
10

Add Portfolio, Watchlist, News & Technical Indicators

Here are the key features to make your dashboard production-ready. Each uses a different MarketLens API endpoint:

Portfolio Tracking

Python
def get_portfolio_value(holdings):
    """Calculate portfolio value and P&L.

    holdings = [{"symbol": "AAPL", "shares": 10, "cost_basis": 150.00}, ...]
    """
    total_value = 0
    total_cost = 0
    results = []

    for h in holdings:
        resp = requests.get(f"{BASE}/stocks/{h['symbol']}/quote",
                            headers=HEADERS, timeout=10)
        quote = resp.json().get("data", {})
        price = quote.get("price", 0)
        value = price * h["shares"]
        cost = h["cost_basis"] * h["shares"]
        pnl = value - cost
        pnl_pct = (pnl / cost * 100) if cost else 0

        results.append({
            "symbol": h["symbol"],
            "shares": h["shares"],
            "price": price,
            "value": value,
            "pnl": pnl,
            "pnl_pct": pnl_pct
        })
        total_value += value
        total_cost += cost

    return {
        "positions": results,
        "total_value": total_value,
        "total_pnl": total_value - total_cost,
        "total_pnl_pct": ((total_value - total_cost) / total_cost * 100) if total_cost else 0
    }

Technical Indicators

Python
def get_technical_indicators(symbol):
    """Fetch RSI, MACD, SMA, EMA, Bollinger Bands, ATR in one call."""
    resp = requests.get(f"{BASE}/technical/{symbol}/summary",
                        headers=HEADERS, timeout=10)
    data = resp.json().get("data", {})

    # Display key signals
    rsi = data.get("rsi", {}).get("value", "N/A")
    macd = data.get("macd", {})
    signal = "Bullish" if macd.get("histogram", 0) > 0 else "Bearish"
    sma_20 = data.get("sma", {}).get("sma_20", "N/A")
    bb = data.get("bollinger_bands", {})

    return {
        "rsi": rsi,
        "rsi_signal": "Oversold" if rsi < 30 else "Overbought" if rsi > 70 else "Neutral",
        "macd_signal": signal,
        "sma_20": sma_20,
        "bb_upper": bb.get("upper", "N/A"),
        "bb_lower": bb.get("lower", "N/A")
    }

# In Streamlit:
# indicators = get_technical_indicators("AAPL")
# st.metric("RSI", f"{indicators['rsi']:.1f}", indicators['rsi_signal'])

News Feed

Python
def get_stock_news(symbol, limit=5):
    """Fetch latest news for a stock."""
    resp = requests.get(f"{BASE}/news/{symbol}",
                        headers=HEADERS,
                        params={"limit": limit},
                        timeout=10)
    articles = resp.json().get("data", {}).get("articles", [])
    return articles

# In Streamlit:
# news = get_stock_news("AAPL")
# for article in news:
#     st.markdown(f"**[{article['title']}]({article['url']})**")
#     st.caption(f"{article['source']} — {article['published_at']}")

Dashboard Mockup — What You'll Build

AAPL
$185.42
+1.23%
GOOGL
$142.87
+0.87%
MSFT
$421.30
-0.45%
TSLA
$248.65
+2.14%
NVDA
$895.20
+3.21%
[ Candlestick Chart — AAPL 3 Month ]
Portfolio Value
$47,832.50
+$1,245.30 today
RSI (14)
62.4
Neutral
MACD
+2.31
Bullish crossover

This mockup shows the completed dashboard with watchlist cards, candlestick chart, portfolio tracking, and technical indicators — all powered by the MarketLens API.

Live API Demo — Try It Now

Click any button to fetch real data from the MarketLens API. See the endpoints your dashboard will call:

json
// Click a button above to see live API response

Why MarketLens vs yfinance / Alpha Vantage for Dashboards

Here's how MarketLens compares for building Python stock dashboards:

Feature MarketLens yfinance Alpha Vantage
Total Endpoints222+~15 methods~25 endpoints
StabilityStable REST API, versionedBreaks when Yahoo changes HTMLStable API
Free Tier500 calls/dayUnlimited (unofficial)25 calls/day
Real-time DataYes — 5 data sourcesDelayed 15minYes (premium)
Technical IndicatorsBuilt-in (RSI, MACD, BB, ATR)None — build your ownYes (limited free)
BacktestingBuilt-in — single API callNoneNone
Response Time<200ms (cached)1-5 seconds~500ms
Pro Pricing$29/mo (10K/day)Free (may break)$49.99/mo

Get the Free Stock Dashboard Starter Kit

Complete starter project with Flask, Streamlit, and Dash dashboards. Plug in your API key and run.

Start Building Your Dashboard for Free

Use code FOUNDER50 for 50% off forever via GitHub Sponsors

$0
Free · 500/day
$29 $14.50
Pro · 10K/day
$99 $49.50
Enterprise · 50K/day
Get Free API Key →

Ready to Build Your Stock Dashboard?

Get your API key in 10 seconds. 500 calls/day, 222+ endpoints for stocks, forex, crypto, and more.

Frequently Asked Questions

Yes. The free tier gives you 500 API calls per day — enough to update 10 stocks every 5 minutes for the entire trading day. No credit card required. Pro is $29/month for 10,000 calls/day.

Streamlit is fastest to build (under 100 lines). Flask+Chart.js gives you full frontend control. Dash by Plotly is best for interactive financial charts with callbacks. This tutorial covers all three.

Yes. This tutorial shows three approaches: Flask with Chart.js auto-refresh (polling every 30s), Streamlit with st.rerun() for live updates, and Dash with dcc.Interval callbacks. All use the MarketLens API for real-time data.

yfinance scrapes Yahoo Finance, which breaks frequently, returns 429 errors, and violates Yahoo's ToS. MarketLens provides a stable REST API with 222+ endpoints, consistent JSON, built-in backtesting, technical indicators, and portfolio tracking.

Use the MarketLens /api/v1/stocks/{symbol}/quote endpoint to fetch real-time prices for your holdings, then calculate total value, daily P&L, and allocation. This tutorial includes complete code for portfolio tracking.

MarketLens provides a /api/v1/technical/{symbol}/summary endpoint that returns SMA, EMA, RSI, MACD, Bollinger Bands, and ATR in a single call. Display these as metrics in your Streamlit sidebar or Flask template.

For Flask: use gunicorn with 2-4 workers on Render.com or Railway. For Streamlit: deploy to Streamlit Cloud (free) with one click. Set MARKETLENS_API_KEY as an environment variable.

Free tier: 500 calls/day (no credit card). Pro: $29/month for 10,000 calls/day. Enterprise: $99/month for 50,000 calls/day. Use code FOUNDER50 for 50% off via GitHub Sponsors.

Related Articles