Skip to content

Architecture

FolioOrb architecture diagram
Layer Stack
Backend Python 3.11+, FastAPI 0.138.2, Uvicorn 0.48.0
Data SQLite, SQLAlchemy 2.0.51, Pydantic 2.13.4
Market data yfinance 1.5.1 and Yahoo Finance
Public data SEC EDGAR (filings) and US Treasury (yield curve) — keyless, public domain
AI Anthropic SDK 0.105.2, optional Claude calls (Haiku 4.5 + Sonnet 4.5), local deterministic fallback
Frontend Single HTML shell, Bootstrap 5.3.2, Bootstrap Icons, Chart.js 4.4.0, vanilla JavaScript
Desktop PyInstaller + pywebview native window, in-app auto-update
Quality pytest, Pylint, compileall, pip-audit, Dependency Review, CodeQL

The browser dashboard talks to the FastAPI app over HTTP/JSON. Routers are thin HTTP adapters: they validate request shapes, call domain modules, and translate domain errors into responses. The modules read/write SQLite, call Yahoo Finance for market data, read public filings and rates from SEC EDGAR and the US Treasury, and optionally call Anthropic Claude when narration is requested and a key is configured.

Each outside source sits behind exactly one module — stock_service for Yahoo, edgar_service for the SEC, treasury_yield_curve for the curve — so a provider can be swapped, throttled, or degraded in one place instead of at every call site. edgar_service owns the SEC’s two hard rules: a declared contact address, and a ceiling of ten requests a second.

Financial state is concentrated behind four interfaces so callers cannot accidentally use different rules for the same Portfolio:

Module Owns Important invariant
portfolio_lifecycle.py Default creation, list/create/rename/delete, owned-record cleanup Deleting a Portfolio removes its holdings, trades, snapshots, verdict history, DCA ledger, and BOOK:<id> narratives together.
portfolio_valuation.py Live valuation, cost basis, realized + unrealized return, quote quality, daily history Watchlist rows never enter totals; zero, non-finite, and missing quotes make quality explicit; total-return percentage uses open plus sold cost basis; only complete valuations may update daily snapshots.
dca_ledger.py Plan persistence, catch-up, pending/applied/dismissed transitions, exact apply/undo Catch-up is idempotent; no simulated buy mutates a holding until the user applies it; a plan with applied buys cannot be deleted until those buys are undone.
narrative_cache.py Portfolio and ticker cache reads/writes, TTL/price freshness, verdict encoding, JSON validation Portfolio narratives are isolated under BOOK:<portfolio_id>; stale or corrupt rows regenerate safely; incomplete valuations never produce confident Portfolio-level Claude narration.

Pure DCA scheduling and cost math remains in dca_service.py, while routers and AI/analytics call these interfaces instead of reaching into each other’s private helpers.

The service layer’s core is a modular Intelligence Engine split into two halves:

  • Local Intelligence — always on, fully offline. Covers investment_signal, market_regime, portfolio_exposure, and event_calendar, producing verdicts, scenarios, regime classification, exposure breakdowns, peer comparisons, and calibration data.
  • Claude AI — optional. Lives in ai_service.py, exposing generate_action_plan() and generate_news_themes(), backed by a 24-hour SQLite cache and live token cost tracking. It uses Claude Haiku 4.5 for fast narration and Claude Sonnet 4.5 for action plans.

The browser view is identical whether you open localhost:8000 yourself or launch the desktop app — the desktop build (PyInstaller + pywebview) runs the same FastAPI server in-process behind a native window, and can update itself in place.

app/
├── main.py FastAPI app, middleware, static assets, startup warmup
├── config.py Environment-backed settings
├── database.py SQLite engine/session and startup migrations
├── models.py SQLAlchemy ORM models
├── schemas.py Pydantic request/response contracts
├── routers/ Thin HTTP adapters: stocks, portfolio, DCA, AI, news, system
└── services/ Lifecycle, valuation, DCA ledger, cache, analytics, signals, data, updates
desktop/ Desktop entry point (uvicorn + native window)
packaging/ PyInstaller spec, Inno Setup script, app icons
templates/
└── index.html Dashboard shell
static/
├── css/style.css Dashboard design system
├── js/dashboard.js Main dashboard behavior
└── js/analytics-charts.js Chart.js analytics widgets