Skip to content

udhawan97/FolioSenseAI

Repository files navigation

FolioSenseAI

FolioSenseAI

Your folio, finally making sense.

Release v4.1 361 tests Python FastAPI Claude optional SQLite Bootstrap Chart.js MIT License

CI Pylint CodeQL


FolioSenseAI dashboard
Demo numbers. Real anxiety simulation.

FolioSenseAI architecture


FolioSenseAI is a self-hosted portfolio intelligence dashboard built on FastAPI + SQLite + vanilla JS. It turns live market data and a modular analytics engine into Hold / Add / Trim / Exit verdicts — optionally upgraded with Anthropic Claude narration when you bring a key. Runs entirely on your machine. No account. No subscription. No hot takes from the cloud unless you ask for them.

Contents


⚡ What This Is

Audience What you get
Investors Live market context, a prioritised action plan, and Hold / Add / Trim / Exit verdicts with a thesis — not just a sparkline and a prayer
Developers A production-patterned Python + FastAPI + SQLite project: 14+ modular services, mocked-service tests, a clean REST API, and no frontend framework tax. React was not harmed because it was never invited.
Recruiters A full-stack AI/data product demonstrating API design, multi-layer caching, Anthropic SDK integration, analytics UX, CI/CD, and security hygiene — from scratch, in one repo

🧭 Architecture at a Glance

flowchart LR
    user["User opens local dashboard"]
    ui["Vanilla JS + Bootstrap UI<br/>Holdings, Analytics, News"]
    api["FastAPI backend<br/>REST endpoints"]
    intelligence["Portfolio intelligence engine<br/>signals, risk, exposure, regimes"]
    storage["Local SQLite database<br/>holdings, snapshots, AI cache"]
    market["Yahoo Finance via yfinance<br/>prices, history, headlines"]
    claude["Optional Claude layer<br/>action plans and narration"]

    user --> ui
    ui --> api
    api --> intelligence
    api <--> storage
    intelligence <--> market
    intelligence --> claude
    claude --> intelligence
    intelligence --> api
    api --> ui
Loading

In plain English: the browser talks to a local FastAPI server, the server pulls market data and portfolio records, the analytics engine turns that into verdicts and charts, and Claude can optionally add richer explanations. The product still works without Claude because the core investment logic runs locally.

📋 What's new in v4.1
  • In-dashboard API key panel — click the brand mark, paste sk-ant-*, save. Key is validated client-side and server-side before touching disk. Server reconnects in-process. No terminal, no restart.
  • Live token cost tracking — every Claude call accumulates real token counts. The cost HUD shows actual input/output tokens, a live cost figure, and a predicted per-run annotation — not a cache estimate.
  • Holdings table auto-refresh + first-click expand — prices refresh on interval; rows expand on the very first click.
  • Sector graph proportional fills — bars now scale relative to the top sector, with a +N more overflow note and a dot + track layout.

From v4.0: Portfolio Action Plan (Hold/Add/Trim/Exit with thesis, 24h cached), regime-aware context (SPY/TLT/VIX/UUP chip), News tab (live headlines + Claude cross-holding theme clusters).

From v3.x: localStorage instant table paint, shared .info cache across six services, background startup warmup, Overview/Holdings/Analytics zones, Base/Bull/Bear scenarios, look-through exposure, peer-relative positioning, earnings proximity flags.


🎯 Engineering Highlights

The design decisions worth a second look, for anyone reading the code.

Dual-mode intelligence, single interface/api/ai/* serves both engines without branching at the route layer. The deterministic Local Intelligence engine (investment_signal.py, market_regime.py, portfolio_exposure.py, and friends) runs signals, scenarios, exposure, and regime analysis with zero external calls. Claude upgrades the same responses with narration when a key is present. No key = still fully functional. This isn't a placeholder fallback; it's the default mode for most users.

Multi-layer caching with clear contracts — quote prices are cached in-memory (60 s); Yahoo Finance .info is shared across six previously independent callers via get_ticker_info() (5 min market hours / 1 h closed); the holdings table paints from localStorage before the first HTTP response returns; AI responses persist to SQLite for 24 h with portfolio-drift invalidation so rebalancing clears a stale plan without manual intervention.

Zero-restart API key configurationPOST /api/ai/configure-key validates the key with ^sk-ant-[A-Za-z0-9_\-]{20,300}$ on both client and server, writes a single line to .env, and calls reinitialize_client() to hot-swap the Anthropic SDK client in-process. Uvicorn keeps running, the HUD mode chip flips from Local → Claude, and no terminal was opened.

Frontend without a build step — one HTML shell, vanilla JS, Bootstrap 5, Chart.js loaded from CDN. Analytics tabs instantiate Chart.js lazily on first activation. The design system is entirely CSS custom properties — no Sass, no PostCSS, no node_modules. The localStorage snapshot technique means the holdings table is interactive before the first API response lands.

Modular, independently testable services — each service has a single responsibility and is tested with mocked external calls, so the full suite runs offline and deterministically. _collect_portfolio_signals_core() is extracted and shared between the investment-signals endpoint and the action-plan endpoint to avoid computing the signal pipeline twice.

361 tests, four CI checks — pytest (mocked Yahoo Finance and Anthropic), Pylint, pip-audit (known CVE scanning), CodeQL (static analysis), dependency review, and a hygiene workflow that refuses secrets, databases, and OS noise. All checks run on Python 3.11 and 3.12.


✨ Features

Dashboard zones

Zone What's there
Overview Live P&L, weighted sector graph, market regime chip, portfolio briefing from Claude or Local Intelligence
Holdings Per-ticker verdicts with confidence ranges and time horizons, move explanations, auto-refresh prices, expand for deep intelligence
Analytics 5 sub-tabs — Performance, Risk, Exposure, Signals, Markets — with 20+ Chart.js widgets and per-chart AI insight lines
News Live headlines for every holding, deduped and cached; Claude adds a portfolio briefing and cross-holding theme clusters
📊 Analytics widgets
Tab Widgets
Performance Return breakdown, cumulative P&L, projection vs S&P 500, benchmark tracker, monthly return heatmap
Risk Risk/reward scatter, correlation matrix, HHI concentration score, max drawdown, beta, rolling volatility
Exposure Sector tilt, geographic look-through, theme overlap
Signals Conviction gap analysis, confidence spectrum
Markets World index grid, macro alignment, geographic alignment

Each tab has a per-tab AI insight bar and per-widget tip cards via /api/ai/analytics-insights. Widgets load lazily — only the active tab instantiates Chart.js.

Intelligence engine

Every holding gets a verdict derived from benchmark, macro, sector, volume, and event context. Each verdict carries a confidence range, time horizon (auto / trade / core / anchor), and Base / Bull / Bear scenarios with probability bars.

🧬 How the engine is built

The core pipeline runs in investment_signal.py and calls out to modular services:

Service Responsibility
investment_signal.py Core pipeline — horizon weights, confidence ranges, scenario builders, modifier hooks
market_regime.py SPY/TLT/VIX/UUP regime detection, cached daily
portfolio_exposure.py Look-through sector/country/theme, HHI concentration, duplicate detection
peer_relative.py Own-range percentile vs peer median
event_calendar.py Earnings proximity — 14-day flag, confidence cap
verdict_calibration.py Snapshot logging for future hit-rate accountability
verdict_ai_enhancement.py Claude tension gating — nudges only when inputs conflict; agreement skips the AI call
holding_intelligence.py Deep per-ticker context, lazy-loaded on row expand

Claude adds probability splits to scenarios and narrative quips when force_local=False. The gating logic means Claude is only invoked when it has something interesting to say — a signal conflict, an unusual scenario divergence — not on every request.

Verdict snapshots are logged to the verdict_snapshots SQLite table for future calibration reporting. The data is accumulating. The accountability will follow.

Action Plan

Claude reads the full portfolio signal snapshot and returns a prioritised Hold / Add / Trim / Exit bucket plan with a thesis, top moves, and a market regime chip. Cached 24 h with portfolio-drift invalidation — rebalancing clears the stale plan automatically. Falls back to a deterministic local plan with plain-language headlines when Claude is unavailable.

🗂️ Caching strategy
Layer What TTL
In-memory (Python dict) Yahoo Finance .info per ticker 5 min (market hours) / 1 h (closed)
In-memory (Python dict) Quote prices 60 s
localStorage (browser) Full holdings table snapshot Until next successful live response
SQLite ai_summaries Action Plan, briefing, analytics insights 24 h with portfolio-drift invalidation
SQLite verdict_snapshots Per-ticker verdict history Persistent (calibration data)

A daemon thread warms quotes, 1-year history closes, and world market data for all active holdings on server startup. The first real dashboard request hits a warm cache.


🏗️ Tech Stack

Layer Stack
Backend Python 3.11+ · FastAPI 0.136.3 · Uvicorn 0.48.0
Data SQLite · SQLAlchemy 2.0.50 · Pydantic 2.13.4
Market Data yfinance 1.4.1 · Yahoo Finance
AI Anthropic SDK 0.105.2 · Claude (optional) · Local Intelligence fallback
Analytics pandas 3.0.3 · numpy 2.4.6
Frontend Bootstrap 5.3.2 · Bootstrap Icons · Chart.js 4.4.0 · Vanilla JS
Quality pytest · Pylint · pip-audit · Dependency Review · CodeQL · security hygiene workflow

No blockchain. No NFTs. No "AI agentic alpha swarm." We all survived.


🚀 Install & Run

FolioSenseAI runs entirely on your computer. No account needed, no data leaves your machine.

One prerequisite: Python 3.11+ — download here, click the yellow button, run the installer.

Windows only: on the first installer screen, check "Add Python to PATH" before clicking Install.

🍎 Mac — one command

Open Terminal (⌘ Space → Terminal → Enter), paste, and press Enter:

curl -fsSL https://raw.githubusercontent.com/udhawan97/FolioSenseAI/release-v4.1/scripts/install-mac.sh | bash

Downloads, installs, and places a FolioSenseAI shortcut on your Desktop. Browser opens automatically. Next time: double-click the Desktop shortcut.

🪟 Windows — one command

Open PowerShell (Win+R → powershell → Enter), paste, and press Enter:

irm https://raw.githubusercontent.com/udhawan97/FolioSenseAI/release-v4.1/scripts/install-win.ps1 | iex

Downloads, installs, and places a FolioSenseAI shortcut on your Desktop. Browser opens automatically. Next time: double-click the Desktop shortcut.

Keep the Terminal / console window open while using the app — closing it stops the server. Press Ctrl+C to stop.

Claude API key (optional)

The app works fully offline with Local Intelligence. For Claude AI features — action plans, news briefings — click the brand mark in the top-left of the dashboard, paste your sk-ant-* key, and save. No restart required.

Get a key at console.anthropic.com. Action Plan responses are cached 24 h so you won't be re-billed on every refresh.

Updating

Run the install command again — it detects your existing database/ and .env, preserves them, and starts the updated app.

🩹 Something not working?
Symptom Fix
Python not found Install from python.org and open a new terminal window.
Windows: winget fails Install Python manually, check "Add to PATH", re-run the command.
Mac: curl: command not found xcode-select --install — this shouldn't happen on modern macOS.
Mac: bash: scripts/setup.sh: No such file or directory Your Desktop shortcut is outdated. Re-run the one-line install command above — it replaces the shortcut automatically.
Browser doesn't open Navigate to http://localhost:8000 manually.
localhost:8000 won't load The terminal window must stay open — it is the server.
Port 8000 is busy Stop the other app on that port, or change the port in run.py.
AI shows "Local" mode Click the brand mark in the dashboard and paste your API key.
⚙️ Manual install (advanced)
# Mac / Linux
curl -L -o FolioSenseAI.zip https://github.com/udhawan97/FolioSenseAI/archive/refs/tags/release-v4.1.zip
unzip FolioSenseAI.zip && cd FolioSenseAI-release-v4.1
./scripts/setup.sh
# Windows PowerShell
Invoke-WebRequest "https://github.com/udhawan97/FolioSenseAI/archive/refs/tags/release-v4.1.zip" -OutFile FolioSenseAI.zip
Expand-Archive FolioSenseAI.zip; cd FolioSenseAI-release-v4.1
.\scripts\setup.ps1

Daily start: ./scripts/start.sh (Mac) or .\scripts\start.ps1 (Windows).


👩‍💻 Developer Setup

python3 -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env            # Windows: copy .env.example .env
python run.py
URL Purpose
http://localhost:8000 Dashboard
http://localhost:8000/docs Interactive API docs (Swagger UI)
http://localhost:8000/health Health check
🗂️ Project structure
app/
├── main.py               — FastAPI app, middleware, static file serving, startup migrations
├── models.py             — SQLAlchemy ORM models
├── schemas.py            — Pydantic request/response contracts
├── routers/              — Route handlers: stocks, portfolio, ai, news
└── services/
    ├── stock_service.py          — quotes, history, world markets, shared .info cache
    ├── investment_signal.py      — core intelligence pipeline
    ├── holding_intelligence.py   — deep per-ticker context (lazy-loaded on expand)
    ├── ai_service.py             — Anthropic client, action_plan(), token tracking
    ├── news_service.py           — headline fetch, TTL cache, dedup
    ├── portfolio_analytics.py    — drawdown, beta, correlation, projection
    ├── portfolio_exposure.py     — look-through sector/country/theme, HHI
    ├── market_regime.py          — SPY/TLT/VIX/UUP regime detection
    ├── peer_relative.py          — own-range percentile vs peer median
    ├── event_calendar.py         — earnings proximity flags
    ├── verdict_calibration.py    — snapshot logging for hit-rate accountability
    ├── verdict_ai_enhancement.py — Claude tension gating
    └── ...
templates/
└── index.html            — single-page shell: four zones (Overview, Holdings, Analytics, News)
static/
├── js/                   — dashboard.js + analytics-charts.js
└── css/style.css         — design system via CSS custom properties
tests/                    — 361 tests; all external services mocked
scripts/                  — one-command install + start for Mac / Linux / Windows
🔌 API quick reference
Group Endpoints
Market data /api/stocks/prices, /api/stocks/history/{ticker}, /api/stocks/world-markets, /api/stocks/market-status
Portfolio /api/portfolio/holdings, /api/portfolio/value, /api/portfolio/pnl, /api/portfolio/projection, /api/portfolio/risk-metrics, /api/portfolio/correlation
Analytics /api/portfolio/drawdown, /api/portfolio/beta, /api/portfolio/rolling-volatility, /api/portfolio/sector-tilt, /api/portfolio/conviction-gaps, /api/portfolio/market-context
AI / Intelligence /api/ai/investment-signals/all, /api/ai/portfolio-summary, /api/ai/portfolio-exposure, /api/ai/verdict-calibration, /api/ai/analytics-insights, /api/ai/intelligence/{ticker}/deep, /api/ai/action-plan, /api/ai/configure-key
News /api/news/feed, /api/news/themes

Open /docs locally for the full interactive Swagger reference.


🧪 Quality & CI

python -m pytest -q
python -m compileall -q app run.py tests
python -m pylint $(git ls-files '*.py')
pip-audit -r requirements.txt

GitHub CI runs all checks on Python 3.11 and 3.12. Separate workflows cover Pylint, dependency audit/review, CodeQL static analysis, and a hygiene check that blocks secrets, databases, backups, and OS noise from the repo.

🔐 Local-first security model
  • .env and database/ are git-ignored; fresh installs start with an empty portfolio
  • CORS defaults to local origins only
  • API key is validated via regex on both client and server before touching disk; never logged
  • Claude is optional and cached; Local Intelligence makes zero external AI calls
  • Not a brokerage, advisor, oracle, or suspiciously confident uncle

🗺️ Roadmap

  • CSV import / export
  • Transaction history views
  • Calibration reporting — the verdict snapshots are accumulating, but they need more history before the hit-rate numbers stop being anecdotal

🤝 Contributing

Issues and PRs welcome. For anything beyond a small fix, open an issue first to align on scope.

  • python -m pytest -q and python -m pylint $(git ls-files '*.py') before submitting
  • Keep changes focused — context lives in the PR description, not the code

📄 License & Disclaimer

Released under the MIT License. Personal project, released for public use. Not financial advice. If you make or lose money because a dashboard on GitHub had nice colors, that is a fascinating life choice and also entirely on you.

Built with AI, caffeine, and a deeply normal interest in watching numbers move.
⭐ Star it if it helped you understand your portfolio — or if it at least insulted your diversification with better vocabulary than your last advisor.

About

AI-powered portfolio cockpit — with local intelligence fallback — that reads your book and tells you what to hold, trim, or exit, and why.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors