Skip to content

About

Multi-agent portfolio risk analysis with live market intelligence, RAG and compliance checks.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Intelligent Risk Analysis

Multi-agent portfolio risk analysis with live market intelligence, RAG and compliance checks.

Give it a portfolio and a question, for example "How exposed am I to US–China tensions and higher-for-longer rates?". A team of AI agents built with LangGraph then:

  • gathers quant risk metrics and your internal knowledge;
  • researches the question in parallel using the knowledge base plus live web search over MCP;
  • combines and compliance-checks the findings, including against your investment policy;
  • returns a risk score, action and rationale for every ticker.

Python LangGraph FastAPI Next.js Chroma MCP

⚠️ Output is AI-generated from public information and your uploaded documents. It is not investment advice.


Contents


Features

🧭 Planner agent Reads the portfolio, question, quant metrics and knowledge base, then selects only the specialists that matter and writes a focused brief for each
⚡ Parallel specialists Macro, Micro, Geopolitical and Sector agents run concurrently, each with web search, the knowledge base and market data
🌐 Live information via MCP Web search through a Model Context Protocol server: DuckDuckGo by default (no key), Tavily or Brave optional
📚 RAG knowledge base Upload research, filings, notes and your investment policy; web research is cached and reused; past recommendations are kept as memory
📈 Quant risk metrics Volatility, max drawdown, beta, VaR, correlations, sector weights and effective holdings, computed from 1 year of prices
🧮 Risk analyser Combines all evidence into portfolio and per-ticker risk, calibrated against the quant metrics
✅ Compliance agent Checks coverage, grounding, guarantee language, consistency, relevance and policy. It can send the report back for revision, or send specific specialists back for targeted re-research
🎯 Recommendations One action (Strong Buy → Sell) per ticker, with a blended risk score, confidence, rationale, risks, opportunities and change vs. the previous view
📡 Live progress Server-Sent Events stream every agent's status to the UI as it happens

Architecture

flowchart LR
    user(["👤 User"])

    subgraph FE["Frontend · Next.js 15"]
        direction TB
        ui_in["Portfolio + question"]
        ui_kb["Knowledge base panel"]
        ui_tl["Live agent timeline"]
        ui_res["Results: scores, metrics,<br/>reports, sources"]
    end

    subgraph BE["Backend · FastAPI"]
        direction TB
        api["REST + SSE API"]
        wf["LangGraph workflow<br/>(11 nodes)"]
        api --> wf
    end

    subgraph EXT["External services"]
        direction TB
        llm["LLM<br/>Claude via LangChain"]
        mcp["MCP search server<br/>DuckDuckGo / Tavily / Brave"]
        yf["Yahoo Finance<br/>via yfinance"]
    end

    subgraph DATA["Local storage"]
        direction TB
        chroma[("Chroma vector store<br/>documents · research · memory")]
    end

    user --> FE
    ui_in -- "POST /api/analyze/stream" --> api
    ui_kb -- "POST/GET/DELETE /api/documents" --> api
    api -- "SSE status / update / result" --> ui_tl
    api -- "final result" --> ui_res

    wf -- "structured output,<br/>tool calling" --> llm
    wf -- "stdio MCP tools" --> mcp
    wf -- "prices, fundamentals" --> yf
    wf <-- "retrieve / cache / remember" --> chroma
Loading

Key design decisions

  • The graph is the orchestrator. Agent order, parallelism and loops are explicit edges in LangGraph, not left to the LLM. Only which specialists run is decided by the planner.
  • Specialists work in two phases: a tool-calling research loop, then a separate structured-output call that writes the report. This keeps typed outputs reliable and provider-agnostic.
  • Deterministic checks sit alongside LLM checks. Ticker coverage, score/level bands and score blending are enforced in code, so the LLM can't skip them.
  • Graceful degradation. If search, market data, retrieval or a specialist fails, the run continues and the problem appears in warnings.
  • Everything stays local except the LLM, search and price calls. Embeddings run locally (ONNX), and uploaded documents never leave the server; only retrieved passages are sent to the LLM.

Agent workflow

flowchart TD
    start((START))

    subgraph CTX["① Context · parallel"]
        md["📈 market_data<br/>quant metrics from prices"]
        kn["📚 knowledge<br/>RAG: passages, policy, prior views"]
    end

    pl{{"🧭 planner<br/>select specialists + briefs"}}

    subgraph SPEC["② Specialists · parallel, only those selected"]
        ma["🌍 macro"]
        mi["🏢 micro"]
        ge["🛡️ geopolitical"]
        se["🏭 sector"]
    end

    ra["🧮 risk_analyser<br/>combine evidence + metrics"]
    co{"✅ compliance<br/>coverage · grounding · guarantees<br/>consistency · relevance · policy"}
    rec["🎯 recommendation<br/>action + blended score per ticker"]
    mem["💾 memory<br/>store views in knowledge base"]
    stop((END))

    start --> md & kn
    md & kn --> pl
    pl -. "selected" .-> ma & mi & ge & se
    ma & mi & ge & se --> ra
    ra --> co
    co -- "passed, or retries exhausted" --> rec
    co -- "fail: fix the report" --> ra
    co -- "fail: missing evidence →<br/>targeted re-research" --> SPEC
    rec --> mem --> stop
Loading
Node Type What it does
market_data code Downloads 1 year of prices for all tickers plus the benchmark in one batch. Computes returns, annualised volatility, max drawdown, beta, 1-day 95% VaR, a per-ticker quant score, portfolio volatility/VaR/beta, pairwise correlations, sector weights and effective holdings
knowledge code (RAG) Retrieves passages relevant to the question, investment-policy passages and each ticker's most recent stored view
planner LLM Selects specialists (all four when the question is broad) and writes a 2–4 sentence brief for each. User-forced agents are always added
macro / micro / geopolitical / sector LLM + tools Retrieve from the knowledge base, then research with web search (MCP), search_knowledge_base and (micro/sector) get_market_snapshot. They write a structured report with findings, severity, affected tickers and sources, and cache the web research
risk_analyser LLM Produces portfolio and per-ticker risk scores, themes and concentration risks, and answers the question directly
compliance code + LLM Checks ticker coverage in code, then has the LLM review the other checks. Outcomes: pass, revise (back to the risk analyser) or re-research (back to named specialists). Shares a limit of MAX_COMPLIANCE_REVISIONS (2 by default)
recommendation LLM + code Writes one recommendation per ticker. Code back-fills any missing ticker, blends the analyst and quant scores, and attaches the previous view
memory code Stores each ticker's view so the next run can compare against it

Sequence of a run

sequenceDiagram
    autonumber
    actor U as User
    participant FE as Next.js UI
    participant API as FastAPI
    participant G as LangGraph
    participant KB as Chroma KB
    participant YF as yfinance
    participant LLM as LLM
    participant MCP as MCP search

    U->>FE: Portfolio + question
    FE->>API: POST /api/analyze/stream
    API->>G: astream(initial state)
    API-->>FE: status: market_data, knowledge running

    par Context gathering
        G->>YF: 1y prices + fundamentals
        YF-->>G: metrics, quant scores
    and
        G->>KB: search(question), policy(), prior_views()
        KB-->>G: passages, policy, previous views
    end
    API-->>FE: status: planner running

    G->>LLM: Plan(portfolio, metrics, KB summary)
    LLM-->>G: agents + briefs
    API-->>FE: status: specialists running / skipped

    par Each selected specialist
        G->>KB: retrieve for brief
        loop up to SPECIALIST_MAX_STEPS
            G->>LLM: research step (tools bound)
            LLM-->>G: tool calls
            G->>MCP: search / fetch_content
            MCP-->>G: web results
        end
        G->>KB: cache web research
        G->>LLM: SpecialistReport (structured)
        LLM-->>G: findings + sources
    end
    API-->>FE: status: each specialist done

    loop until passed or revisions exhausted
        G->>LLM: RiskReport
        LLM-->>G: scores, themes
        G->>LLM: ComplianceResult (+ policy)
        LLM-->>G: pass / issues / research requests
        alt evidence gap
            G->>LLM: named specialists re-research
        else report problem
            Note over G: back to risk_analyser with feedback
        end
    end

    G->>LLM: FinalRecommendation
    LLM-->>G: action + score per ticker
    Note over G: blend scores, back-fill tickers, attach prior views
    G->>KB: store views (memory)
    API-->>FE: result (full JSON)
    FE-->>U: Scores, actions, metrics, reports, sources
Loading

RAG knowledge base

flowchart LR
    subgraph IN["Ingestion"]
        up["📄 Uploads<br/>PDF · TXT · MD · HTML · CSV · JSON"]
        web["🌐 Specialist web results"]
        runs["🎯 Final recommendations"]
    end

    split["Chunk<br/>1200 chars, 150 overlap"]
    emb["Embed locally<br/>all-MiniLM-L6-v2 (ONNX)"]

    subgraph STORE["Chroma · persistent"]
        docs[("documents<br/>type, tickers, page")]
        res[("research<br/>url, query, fetched_at, tickers")]
        memc[("memory<br/>ticker, action, score, run_at")]
    end

    subgraph OUT["Retrieval"]
        r1["knowledge node:<br/>question passages, policy, prior views"]
        r2["specialists:<br/>brief-based retrieval +<br/>search_knowledge_base tool"]
        r3["compliance + recommendation:<br/>policy enforcement"]
    end

    up --> split --> emb --> docs
    web --> split
    emb --> res
    runs --> memc
    docs & res --> r1 & r2
    docs --> r3
    memc --> r1
Loading
Collection Contents Used by
documents Your uploads, typed as research_report, filing, news, note or policy, optionally tagged with tickers Specialists (evidence), compliance and recommendation (policy)
research Web results found by specialists, tagged with the portfolio tickers they mention. Upserted by URL, so re-fetching refreshes them Later runs, while younger than RESEARCH_TTL_DAYS (7 by default)
memory One entry per ticker per run: action, score, rationale Planner, risk analyser, recommendation ("was Hold → now Reduce")

Retrieval runs an unfiltered semantic query plus a ticker-filtered one and merges the results. Documents tagged with your tickers rank higher without hiding untagged ones. Internal sources are cited as kb:<file>#p<page> or kb:<file>#c<chunk>.


Scoring

All scores are 0–100, where higher means riskier:

Score 0–20 21–40 41–60 61–80 81–100
Level Low Moderate Elevated High Severe
flowchart LR
    ev["Specialist evidence"] --> an["Analyst score<br/>(risk analyser → recommendation)"]
    px["1y price history"] --> q["Quant score<br/>50% volatility · 30% drawdown · 20% beta"]
    an -- "× 0.7" --> f(("Final score"))
    q -- "× 0.3" --> f
Loading
  • Quant score anchors: annual volatility 10% → 0 and 70% → 100; drawdown 0% → 0 and −60% → 100; beta 0 → 0 and 2.5 → 100.
  • The blend weight is QUANT_SCORE_WEIGHT; set it to 0 to use the analyst score only. The UI shows both components (A 70 · Q 30).
  • Action weighs risk against opportunity, so a high-risk name with strong upside can still be Hold.
  • Confidence (0–1) reflects source quality, agreement between specialists and the compliance outcome.

Tech stack

Layer Technology
Agent orchestration LangGraph, LangChain init_chat_model
LLM Claude (anthropic:claude-sonnet-5-5 by default); any LangChain provider works
Live search MCP via langchain-mcp-adapters; duckduckgo-mcp-server by default
RAG Chroma with built-in ONNX embeddings, pypdf, langchain-text-splitters
Market data yfinance, pandas, numpy
API FastAPI, Server-Sent Events, Pydantic v2
Frontend Next.js 15 (App Router), React 19, TypeScript, plain CSS with light and dark themes

Getting started

Prerequisites

  • Python 3.11+
  • Node.js 20+
  • An Anthropic API key (or another LangChain-supported provider; see Configuration)

uv (which provides uvx, used to launch the DuckDuckGo MCP server) is installed from requirements.txt. On first use, Chroma downloads its ~80 MB embedding model once.

1. Clone

git clone https://github.com/<your-username>/intelligent-risk-analysis.git
cd intelligent-risk-analysis

2. Backend

cd backend
python -m venv .venv
.venv\Scripts\activate            # macOS/Linux: source .venv/bin/activate
pip install -r requirements.txt
copy .env.example .env            # macOS/Linux: cp .env.example .env
# edit .env and set ANTHROPIC_API_KEY
uvicorn app.main:app --reload --port 8000

Check http://localhost:8000/api/health. It should list the search tools (search, fetch_content, …) and the knowledge-base counts.

3. Frontend

cd frontend
npm install
copy .env.local.example .env.local   # macOS/Linux: cp .env.local.example .env.local
npm run dev

Open http://localhost:3000.

4. Use it

  1. Enter holdings as weights or quantities, or paste CSV, then type your question.
  2. (Optional) Upload research or your investment policy in the Knowledge base panel.
  3. Click Run risk analysis and watch the agent timeline.
  4. Review the blended scores and actions, the quant metrics, each specialist's findings with sources, and the compliance status. Use Export JSON to save the result.

Configuration

All settings are environment variables in backend/.env (see backend/.env.example).

Variable Default Purpose
LLM_MODEL anthropic:claude-sonnet-5-5 Any init_chat_model string, e.g. anthropic:claude-opus-5-5 for deeper analysis, or openai:gpt-4.1 after pip install langchain-openai
ANTHROPIC_API_KEY — API key for the default provider
LLM_MAX_TOKENS / LLM_MAX_RETRIES 8192 / 3 Output limit and retries on transient errors
MCP_CONFIG_PATH mcp_servers.json MCP servers to load search tools from
SPECIALIST_MAX_STEPS 6 Max tool-calling rounds per specialist
SPECIALIST_TIMEOUT_S 300 Per-specialist timeout
MAX_COMPLIANCE_REVISIONS 2 Max times compliance can send work back
RAG_DIR data/chroma Knowledge base location (relative to backend/)
RAG_TOP_K / RAG_MAX_DISTANCE 6 / 0.8 Passages per query and cosine-distance cut-off
RESEARCH_TTL_DAYS 7 How long cached web research is reused
MAX_UPLOAD_MB 20 Upload size limit
BENCHMARK / HISTORY_PERIOD SPY / 1y Benchmark and look-back for quant metrics
QUANT_SCORE_WEIGHT 0.3 Share of the final score from quant metrics
FRONTEND_ORIGIN http://localhost:3000 Allowed CORS origin

Frontend: NEXT_PUBLIC_API_URL in frontend/.env.local (default http://localhost:8000).

Search provider: copy entries from backend/mcp_servers.example.json into backend/mcp_servers.json and set TAVILY_API_KEY or BRAVE_API_KEY in .env. ${VAR} placeholders are filled from the environment. Those servers run through npx, so Node.js must be on PATH.

Tracing: set LANGSMITH_TRACING=true and LANGSMITH_API_KEY to trace every agent run in LangSmith.


API reference

Method Endpoint Description
POST /api/analyze Run the full workflow and return the final JSON
POST /api/analyze/stream Same, streamed as Server-Sent Events
POST /api/documents Upload a document (multipart: file, doc_type, tickers)
GET /api/documents List documents
DELETE /api/documents/{doc_id} Delete a document
GET /api/knowledge/search?q=&tickers=&k= Inspect retrieval results
GET /api/health Model, loaded search tools, knowledge-base counts

Interactive docs: http://localhost:8000/docs.

Analyze request

{
  "portfolio": [{ "ticker": "AAPL", "weight": 60 }, { "ticker": "TSM", "weight": 40 }],
  "prompt": "How exposed am I to Taiwan risk?",
  "force_agents": ["geopolitical"]
}

Use quantity instead of weight if you prefer; missing weights fall back to equal weighting. Duplicate tickers are merged and weights are normalised to 100%.

Stream events (each is a data: {...} line)

type Payload
status node, status (running · done · skipped · error), optional detail (e.g. revision 1, follow-up research)
update node, data: the node's state update
result data: run_id, portfolio, market, knowledge, plan, analyses, risk_report, compliance, recommendation, warnings
error message

Upload a document

curl -X POST http://localhost:8000/api/documents -F "file=@investment_policy.pdf" -F "doc_type=policy" -F "tickers="

Project structure

.
├── backend/
│   ├── app/
│   │   ├── main.py                 # FastAPI: analysis (JSON + SSE), documents, knowledge search, health
│   │   ├── config.py               # Settings from .env
│   │   ├── llm.py                  # LLM factory, structured output (json_schema), retries
│   │   ├── schemas.py              # API models + structured-output models for every agent
│   │   ├── graph/
│   │   │   ├── state.py            # Shared state with reducers for parallel fan-in
│   │   │   └── workflow.py         # Nodes, edges, routing, compliance loop
│   │   ├── agents/
│   │   │   ├── context.py          # market_data, knowledge, memory nodes + KB search tool
│   │   │   ├── planner.py
│   │   │   ├── specialist.py       # macro / micro / geopolitical / sector
│   │   │   ├── risk_analyser.py
│   │   │   ├── compliance.py
│   │   │   ├── recommendation.py
│   │   │   ├── prompts.py          # All system prompts
│   │   │   └── common.py           # Shared prompt context builders
│   │   ├── rag/
│   │   │   ├── store.py            # Chroma knowledge base: documents, research, memory
│   │   │   └── ingest.py           # Web results → research cache entries
│   │   └── tools/
│   │       ├── mcp_search.py       # Loads MCP tools from mcp_servers.json
│   │       └── market_data.py      # Quant metrics + fundamentals tool
│   ├── tests/test_workflow.py      # Workflow + RAG tests (stub LLM, no API key)
│   ├── mcp_servers.json            # Active MCP servers (DuckDuckGo)
│   ├── mcp_servers.example.json    # DuckDuckGo + Tavily + Brave examples
│   ├── requirements.txt
│   └── .env.example
├── frontend/
│   ├── app/                        # layout, page, global styles
│   ├── components/                 # PortfolioInput, KnowledgeBase, AgentTimeline, Results
│   ├── lib/                        # API client (SSE over fetch) + types
│   └── package.json
├── docs/BRD.md                     # Business requirements document
└── README.md

Testing

The backend tests use a stub LLM and stub market data, so they need no API key or network. They cover:

  • the parallel fan-out and fan-in;
  • the compliance revision and targeted re-research loops;
  • score blending;
  • memory;
  • policy and document retrieval reaching the right agents;
  • the knowledge base itself.
cd backend
python -m pytest tests -q

Frontend type-check and production build:

cd frontend
npm run typecheck
npm run build

Limitations

  • Market data comes from Yahoo Finance via yfinance, which is unofficial and best-effort. Missing tickers are reported in warnings.
  • The quant score is a transparent heuristic for anchoring, not a risk model. There's no VaR back-testing or factor model.
  • Web search quality depends on the MCP provider. DuckDuckGo is free but shallower than Tavily or Brave.
  • No authentication or multi-tenancy. The knowledge base is shared by everyone using the backend, so run it locally or behind your own auth.
  • LLM output can be wrong even after compliance review. Verify sources before acting.

Documentation

  • Business Requirements Document: objectives, functional and non-functional requirements, acceptance criteria (§14 covers RAG and the workflow upgrade).

About

Multi-agent portfolio risk analysis with live market intelligence, RAG and compliance checks.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages