TradingAgents/docs/portfolio/00_overview.md

270 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Portfolio Manager Agent — Design Overview
<!-- Last verified: 2026-03-20 -->
## Feature Description
The Portfolio Manager Agent (PMA) is an autonomous agent that manages a simulated
investment portfolio end-to-end. It performs the following actions in sequence:
1. **Initiates market research** — triggers the existing `ScannerGraph` to produce a
macro watchlist of top candidate tickers.
2. **Initiates per-ticker analysis** — feeds scan results into the existing
`MacroBridge` / `TradingAgentsGraph` pipeline for high-conviction candidates.
3. **Loads current holdings** — queries the Supabase database for the active portfolio
state (positions, cash balance, sector weights).
4. **Requests lightweight holding reviews** — for each existing holding, runs a
quick `HoldingReviewerAgent` (quick_think) that checks price action and recent
news — no full bull/bear debate needed.
5. **Computes portfolio-level risk metrics** — pure Python, no LLM:
Sharpe ratio, Sortino ratio, beta, 95 % VaR, max drawdown, sector concentration,
correlation matrix, and what-if buy/sell scenarios.
6. **Makes allocation decisions** — the Portfolio Manager Agent (deep_think +
memory) reads all inputs and outputs a structured JSON with sells, buys, holds,
target cash %, and detailed rationale.
7. **Executes mock trades** — validates decisions against constraints, records trades
in Supabase, updates holdings, and takes an immutable snapshot.
---
## Architecture Decision: Supabase (PostgreSQL) + Filesystem
```
┌─────────────────────────────────────────────────┐
│ Supabase (PostgreSQL) │
│ │
│ portfolios holdings trades snapshots │
│ │
│ "What do I own right now?" │
│ "What trades did I make?" │
│ "What was my portfolio value on date X?" │
└────────────────────┬────────────────────────────┘
│ report_path column
┌─────────────────────────────────────────────────┐
│ Filesystem (reports/) │
│ │
│ reports/daily/{date}/ │
│ market/ ← scan output │
│ {TICKER}/ ← per-ticker analysis │
│ portfolio/ │
│ holdings_review.json │
│ risk_metrics.json │
│ pm_decision.json │
│ pm_decision.md (human-readable) │
│ │
│ "Why did I decide this?" │
│ "What was the macro context?" │
│ "What did the risk model say?" │
└─────────────────────────────────────────────────┘
```
**Rationale:**
| Concern | Storage | Why |
|---------|---------|-----|
| Transactional integrity (trades) | Supabase | ACID, foreign keys, row-level security |
| Fast portfolio queries (weights, cash) | Supabase | SQL aggregations |
| LLM reports (large text, markdown) | Filesystem | Avoids bloating the DB |
| Agent memory / rationale | Filesystem | Easy to inspect and version |
| Audit trail of decisions | Filesystem | Markdown readable by humans |
The `report_path` column in the `portfolios` table points to the daily portfolio
subdirectory on disk: `reports/daily/{date}/portfolio/`.
### Data Access Layer: raw `psycopg2` (no ORM)
The Python code talks to Supabase PostgreSQL directly via `psycopg2` using the
**pooler connection string** (`SUPABASE_CONNECTION_STRING`). No ORM (Prisma,
SQLAlchemy) and no `supabase-py` REST client is used.
**Why `psycopg2` over `supabase-py`?**
- Direct SQL gives full control — transactions, upserts, `RETURNING *`, CTEs.
- No dependency on Supabase's PostgREST schema cache or API key types.
- `psycopg2-binary` is a single pip install with zero non-Python dependencies.
- 4 tables with straightforward CRUD don't benefit from an ORM or REST wrapper.
**Connection:**
- Uses `SUPABASE_CONNECTION_STRING` env var (pooler URI format).
- Passwords with special characters are auto-URL-encoded by `SupabaseClient._fix_dsn()`.
- Typical pooler URI: `postgresql://postgres.<ref>:<password>@aws-1-<region>.pooler.supabase.com:6543/postgres`
**Why not Prisma / SQLAlchemy?**
- Prisma requires Node.js runtime — extra non-Python dependency.
- SQLAlchemy adds dependency overhead for 4 simple tables.
- Plain SQL migration files are readable, versionable, and Supabase-native.
> Full rationale: `docs/agent/decisions/012-portfolio-no-orm.md`
---
## 5-Phase Workflow
```
┌────────────────────────────────────────────────────────────────────────────┐
│ PHASE 1 (parallel) │
│ │
│ 1a. ScannerGraph.scan(date) 1b. Load Holdings + Fetch Prices │
│ → macro_scan_summary.json → List[Holding] with │
│ watchlist of top candidates current_price, current_value │
└───────────────────────────────────┬───────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────┐
│ PHASE 2 (parallel) │
│ │
│ 2a. New Candidate Analysis 2b. Holding Re-evaluation │
│ MacroBridge.run_all_tickers() HoldingReviewerAgent (quick_think)│
│ Full bull/bear pipeline per 7-day price + 3-day news │
│ HIGH/MEDIUM conviction → JSON: signal/confidence/reason │
│ candidates that are NOT urgency per holding │
│ already held │
└───────────────────────────────────┬───────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────┐
│ PHASE 3 (Python, no LLM) │
│ │
│ Risk Metrics Computation │
│ • Sharpe ratio (annualised, rf = 0) │
│ • Sortino ratio (downside deviation) │
│ • Portfolio beta (vs SPY) │
│ • 95 % VaR (historical simulation, 30-day window) │
│ • Max drawdown (peak-to-trough, 90-day window) │
│ • Sector concentration (weight per GICS sector) │
│ • Correlation matrix (all holdings) │
│ • What-if scenarios (buy X, sell Y → new weights) │
└───────────────────────────────────┬───────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────┐
│ PHASE 4 Portfolio Manager Agent (deep_think + memory) │
│ │
│ Reads: macro context, holdings, candidate signals, re-eval signals, │
│ risk metrics, budget constraint, past decisions (memory) │
│ │
│ Outputs structured JSON: │
│ { │
│ "sells": [{"ticker": "X", "shares": 10, "reason": "..."}], │
│ "buys": [{"ticker": "Y", "shares": 5, "reason": "..."}], │
│ "holds": ["Z"], │
│ "target_cash_pct": 0.08, │
│ "rationale": "...", │
│ "risk_summary": "..." │
│ } │
└───────────────────────────────────┬───────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────┐
│ PHASE 5 Trade Execution (Mock) │
│ │
│ • Validate decisions against constraints (position size, sector, cash) │
│ • Record each trade in Supabase (trades table) │
│ • Update holdings (avg cost basis, shares) │
│ • Deduct / credit cash balance │
│ • Take immutable portfolio snapshot │
│ • Save PM decision + risk report to filesystem │
└────────────────────────────────────────────────────────────────────────────┘
```
---
## Agent Specifications
### Portfolio Manager Agent (PMA)
| Property | Value |
|----------|-------|
| LLM tier | `deep_think` |
| Memory | Enabled — reads previous PM decision files from filesystem |
| Output format | Structured JSON (validated before trade execution) |
| Invocation | Once per run, after Phases 13 |
**Prompt inputs:**
- Macro scan summary (top candidates + context)
- Current holdings list (ticker, shares, avg cost, current price, weight, sector)
- Candidate analysis signals (BUY/SELL/HOLD per ticker from Phase 2a)
- Holding review signals (signal, confidence, reason, urgency per holding from Phase 2b)
- Risk metrics report (Phase 3 output)
- Budget constraint (available cash)
- Portfolio constraints (see below)
- Previous decision (last PM decision file for memory continuity)
### Holding Reviewer Agent
| Property | Value |
|----------|-------|
| LLM tier | `quick_think` |
| Memory | Disabled |
| Output format | Structured JSON |
| Tools | `get_stock_data` (7-day window), `get_news` (3-day window), RSI, MACD |
| Invocation | Once per existing holding (parallelisable) |
**Output schema per holding:**
```json
{
"ticker": "AAPL",
"signal": "HOLD",
"confidence": 0.72,
"reason": "Price action neutral; no material news. RSI 52, MACD flat.",
"urgency": "LOW"
}
```
---
## PM Agent Constraints
These constraints are **hard limits** enforced during Phase 5 (trade execution).
The PM Agent is also instructed to respect them in its prompt.
| Constraint | Value |
|------------|-------|
| Max position size | 15 % of portfolio value |
| Max sector exposure | 35 % of portfolio value |
| Min cash reserve | 5 % of portfolio value |
| Max number of positions | 15 |
---
## PM Risk Management Rules
These rules trigger specific actions and are part of the PM Agent's system prompt:
| Trigger | Action |
|---------|--------|
| Portfolio beta > 1.3 | Reduce cyclical / high-beta positions |
| Sector exposure > 35 % | Diversify — sell smallest position in that sector |
| Sharpe ratio < 0.5 | Raise cash reduce overall exposure |
| Max drawdown > 15 % | Go defensive — reduce equity allocation |
| Daily 95 % VaR > 3 % | Reduce position sizes to lower tail risk |
---
## 10-Phase Implementation Roadmap
| Phase | Deliverable | Effort |
|-------|-------------|--------|
| 1 | Data foundation (this PR) — models, DB, filesystem, repository | ~23 days |
| 2 | Holding Reviewer Agent | ~1 day |
| 3 | Risk metrics engine (Phase 3 of workflow) | ~12 days |
| 4 | Portfolio Manager Agent (LLM, structured output) | ~2 days |
| 5 | Trade execution engine (Phase 5 of workflow) | ~1 day |
| 6 | Full orchestration graph (LangGraph) tying all phases | ~2 days |
| 7 | CLI command `pm run` | ~0.5 days |
| 8 | End-to-end integration tests | ~1 day |
| 9 | Performance tuning + concurrency (Phase 2 parallelism) | ~1 day |
| 10 | Documentation, memory system update, PR review | ~0.5 days |
**Total estimate: ~1522 days**
---
## References
- `tradingagents/pipeline/macro_bridge.py` — existing scan → per-ticker bridge
- `tradingagents/report_paths.py` — filesystem path conventions
- `tradingagents/default_config.py` — config pattern to follow
- `tradingagents/agents/scanners/` — scanner agent examples
- `tradingagents/graph/scanner_setup.py` — parallel graph node patterns