Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Real-Time Portfolio Analytics Dashboard

A full-stack portfolio dashboard that tracks holdings, refreshes prices on a 60-second poll, charts allocation and performance, surfaces market news, and generates Claude-powered portfolio briefs.

The frontend is a React + Vite single-page app. The backend is a Spring Boot REST API backed by an in-memory H2 database. Live data is pulled from Finnhub (quotes), NewsAPI (headlines), and the Anthropic Messages API (insights). Each integration has a graceful local fallback so the dashboard remains usable while keys are being configured.

Stack

Frontend

  • React 18, Vite 5
  • Axios for API calls
  • Recharts for the line / pie / bar visualizations
  • lucide-react icons

Backend

  • Java 17, Spring Boot 3.3.5
  • Spring Web (REST), Spring WebFlux (WebClient for outbound HTTP), Spring Data JPA, Bean Validation
  • H2 in-memory database (auto-seeded with demo holdings)

External services

Project structure

portfolio-analytics-dashboard/
├── backend/
│   ├── pom.xml
│   └── src/main/
│       ├── java/com/example/portfolioanalytics/
│       │   ├── PortfolioAnalyticsApplication.java
│       │   ├── config/         # CORS + WebClient beans
│       │   ├── controller/     # REST endpoints
│       │   ├── model/          # Holding entity + DTOs
│       │   ├── repository/     # Spring Data JPA
│       │   └── service/        # Portfolio, MarketData, News, Claude
│       └── resources/application.properties
├── frontend/
│   ├── package.json
│   ├── vite.config.js
│   └── src/
│       ├── api/                # axios client + endpoint wrappers
│       ├── components/         # panels, header, holdings table
│       ├── components/charts/  # Recharts line / pie / bar
│       ├── hooks/              # usePortfolioData (poll + refresh)
│       ├── pages/Dashboard.jsx
│       └── styles/global.css
├── docker-compose.yml
├── .env.example
└── README.md

Prerequisites

  • Java 17+
  • Maven 3.9+ (or use the bundled Spring Boot plugin via ./mvnw if you add a wrapper)
  • Node.js 20+
  • Optional: Docker, for the compose-based dev setup

Configuration

Copy the example file and fill in the keys you have:

cp .env.example .env
Variable Used by Purpose
SERVER_PORT backend Port for the Spring Boot app (default 8080)
FRONTEND_ORIGINS backend Comma-separated list of allowed CORS origins
CLAUDE_API_KEY backend Anthropic API key for /api/insights/generate
CLAUDE_MODEL backend Model ID (default claude-sonnet-4-20250514)
NEWS_API_KEY backend NewsAPI key for market and company news
MARKET_DATA_PROVIDER backend Quote provider; only finnhub is implemented
MARKET_DATA_API_KEY backend Finnhub API key
VITE_API_BASE_URL frontend Base URL for backend API (default http://localhost:8080/api)

The backend imports the root .env automatically via spring.config.import=optional:file:../.env[.properties], so running mvn spring-boot:run from backend/ picks it up without extra setup.

Missing keys are not fatal. Each integration falls back to local data if its key is absent or the upstream call fails:

  • Quotes → deterministic synthetic prices seeded for common tickers (AAPL, MSFT, NVDA, GOOGL, AMZN, TSLA, META).
  • News → two demo articles tagged accordingly.
  • Insights → a locally-composed brief that flags whichever upstream condition prevented the live call (missing key, HTTP error, empty response).

Run locally

Backend

cd backend
mvn spring-boot:run

Listens on http://localhost:8080. H2 is in-memory, so the demo holdings reset on every restart. The H2 console is enabled at http://localhost:8080/h2-console (JDBC URL jdbc:h2:mem:portfolio, user sa, no password).

Frontend

In a second terminal:

cd frontend
npm install
npm run dev

Open http://localhost:5173. The dashboard polls the backend every 60 seconds and on demand via the Refresh button.

If you load the app from http://127.0.0.1:5173, keep both origins in .env:

FRONTEND_ORIGINS=http://localhost:5173,http://127.0.0.1:5173

Docker Compose (optional)

docker compose up

This boots the backend (Maven + Temurin 17) and frontend (Node 20) with the repo mounted as bind volumes and .env shared between both. Useful for a one-command spin-up; not optimized for production.

API reference

All endpoints are rooted at /api.

Portfolio

Method Path Body Description
GET /portfolio/holdings List holdings with refreshed prices, sorted by symbol
POST /portfolio/holdings Holding Create a holding (returns 201)
PUT /portfolio/holdings/{id} Holding Update an existing holding
DELETE /portfolio/holdings/{id} Delete a holding (returns 204)
GET /portfolio/summary Totals, allocation slices, and a 30-day performance series

Holding shape:

{
  "symbol": "AAPL",
  "companyName": "Apple Inc.",
  "quantity": 12,
  "averageCost": 164.50
}

symbol is required and is normalized to upper-case. quantity must be >= 0.0001. averageCost must be >= 0. currentPrice, marketValue, costBasis, unrealizedGainLoss, and unrealizedGainLossPercent are computed server-side.

News

Method Path Description
GET /news/market Eight most recent market / investing / finance headlines
GET /news/company/{symbol} Recent headlines filtered to the given ticker

Insights

Method Path Description
POST /insights/generate Sends current holdings, summary, and news to Claude and returns a structured brief

Request body:

{
  "holdings": [...],
  "summary": {...},
  "news": [...]
}

Response includes headline, summary, opportunities, risks, and generatedByClaude (boolean indicating whether the response is live or from the local fallback).

How it fits together

  1. On mount, usePortfolioData fetches holdings, summary, and news in parallel, then POSTs them to /insights/generate.
  2. The same hook re-runs every 60 seconds and on user-triggered refresh.
  3. PortfolioService recomputes currentPrice, marketValue, and costBasis on every read by calling MarketDataService.getLatestPrice(...). The 30-day performance series is synthesized from totalCosttotalValue with a small sinusoidal curve (this is illustrative, not historical).
  4. ClaudeInsightService posts a single user message to https://api.anthropic.com/v1/messages (max_tokens=700) and parses content[0].text. Any failure falls back to a deterministic local brief.

Smoke test

  1. Start the backend and frontend.
  2. Open http://localhost:5173 and confirm the seeded demo holdings (AAPL, MSFT, NVDA) appear.
  3. Add a new holding via the form; confirm it appears in the table and updates the totals.
  4. Edit quantity or average cost inline and click Save; confirm allocation and gain/loss update.
  5. Delete a holding; confirm the charts re-render.
  6. Click Refresh and watch the prices and insight panel update.
  7. Add real MARKET_DATA_API_KEY, NEWS_API_KEY, and CLAUDE_API_KEY, restart the backend, and confirm the source pill on the insights panel switches from Local fallback to Claude API and that real headlines appear.

Notes

  • .env is gitignored — never commit it.
  • H2 is in-memory by default; data resets on every backend restart. Point DATABASE_URL at a persistent JDBC URL if you want durable storage.
  • The dashboard polls every 60 s; "real-time" here means "frequently refreshed", not streaming. Wiring up server-sent events or websockets is a natural extension if you need true push.
  • Claude output is informational. Do not treat it as financial advice.

About

React + Spring Boot 3 dashboard for tracking holdings, with Finnhub quotes, NewsAPI headlines, and Claude-powered briefs.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages