Skip to content
Anirudh Rao
← All work

Full-stack capstone with live market data

Stock Portfolio Tracker.

Team capstone for CSC 478: a FastAPI + React/TypeScript app with JWT auth, trades validated against live Finnhub quotes, a watchlist, and real-time portfolio valuation.

A full-stack stock portfolio tracker built as a team capstone for CSC 478, the software engineering capstone at the University of Illinois Springfield. Users sign up, build portfolios, buy and sell against live market prices, keep a watchlist, and see what their holdings are worth right now.

The problem

Tracking investments across brokerages usually means spreadsheets that are stale the moment you close them. The goal was a single place where positions, trade history, and live prices stay in sync, with real accounts so each user only ever sees their own money.

Architecture

The backend is a FastAPI service split into routers for auth, users, portfolios, stocks, and analytics, with interactive API docs served alongside it. The frontend is a React 18 + TypeScript app built with Vite, NextUI, and Recharts. Market data comes from Finnhub through a small async client covering quotes, symbol search, company profiles, price targets, and analyst recommendation trends.

How it works

Authentication and ownership

Passwords are hashed with bcrypt, and logins return a signed JWT with an expiry. A get_current_user dependency decodes the token on every protected route, and every portfolio query is filtered by the caller's user id, so one account can never read or trade in another's portfolio, even by guessing ids. On the client, an axios interceptor attaches the token to each request, and a PrivateRoute wrapper keeps unauthenticated users on the sign-in page.

Trades against live prices

A trade first validates the symbol against a live Finnhub quote. Sells are checked against current holdings and rejected with a clear error if they'd go negative. Every trade is recorded as a transaction that captures the market price at that moment, with sells stored as negative quantities, so the history doubles as an audit trail.

python · 7 lines
if transaction.type == "SELL":
    current_holdings = existing_stock.quantity if existing_stock else 0
    if current_holdings < transaction.quantity:
        raise HTTPException(
            status_code=400,
            detail=f"Insufficient holdings. Current: {current_holdings}, Requested: {transaction.quantity}"
        )

Live valuation and allocation

The analytics endpoints price every holding at the current quote, total the portfolio, and compute each position's share of it. The frontend turns that into allocation and performance charts, alongside the watchlist, transaction history, and a market news feed.

What I'd change next

  • Fetch quotes concurrently. Valuation awaits one Finnhub call per holding in sequence, so latency grows with portfolio size; asyncio.gather plus a short-lived quote cache would flatten it.
  • Keep one source of truth for positions. Holdings live both in a stocks table and in the transaction log, which can drift; deriving positions from transactions alone removes that risk.
  • Move the JWT out of localStorage into an httpOnly cookie, and restrict CORS to the frontend's origin.
  • Keep the SQLite database file out of version control and move to Postgres for anything beyond a demo.
esc
↑↓ to move · ↵ to selectk=16