Portfolio Risk Analysis
Parallel risk and compliance checks, narrated into a report as it streams.
What it does
Holdings in, a narrated risk report out
Post tickers, weights, and a risk profile. It computes real portfolio statistics — volatility, Sharpe, VaR, drawdown, a full covariance matrix, benchmark beta — checks the result against profile-driven compliance rules, proposes a rebalance, and streams all of it to the browser as each stage finishes, ending in an AI-narrated summary.
Architecture
One FastAPI process, a real parallel fan-out
Browser
POST holdings + risk profile, read the response as an SSE stream via fetch() — not EventSource, since this is a POST.
FastAPI service
Single process. Validates the request, schedules the pipeline on a worker thread, bridges progress back to the event loop via an asyncio.Queue.
runs in parallel
Risk Agent
Pulls 1y price history via yfinance (behind a 5-min cache), computes volatility, Sharpe, VaR, drawdown, correlation & covariance matrices, benchmark beta.
Compliance Agent
Checks weight-sum, concentration, position-size, and sector rules against the active risk-profile preset.
Rebalancing Engine
Caps any over-limit position, redistributes freed weight by inverse-volatility score, re-estimates portfolio volatility from the covariance matrix.
Reporting Agent → Gemini · third-party
Sends a structured prompt, parses the response as strict JSON. On any failure, timeout, or a too-short reply, falls back to a fully deterministic, metric-driven narrative instead.
Aggregator
Merges portfolio, risk, benchmark, performance, compliance, and rebalancing results into one schema — its own SSE stage, emitted after the report.
Browser
Aggregated JSON renders as risk cards, a correlation heatmap, and the narrated report.
Risk and Compliance genuinely run at the same time — a two-worker ThreadPoolExecutor, since both yfinance and the Gemini SDK are synchronous and would otherwise block the event loop.
Worth knowing
Built for LangGraph, shipped on a thread pool
The original design called for LangGraph to own this orchestration. What's actually running today is simpler — a supervisor that fans work out to a thread pool and merges it back, with a deterministic fallback if Gemini errors. It does the job the graph would have done, with fewer moving parts to debug; the LangGraph rewrite is still on the roadmap, not abandoned.