EPIC-017: Investment Portfolio Management (100% Self-Developed)¶
Status: π‘ Planned
Vision Anchor:decision-1-portfolio-self-developed
Phase: 5 (Asset Tracking)
Duration: 6-8 weeks
Priority: P1 (High Priority - Post Two-Stage Review)
Dependencies: EPIC-002 (Double-Entry Core), EPIC-003 (Statement Parsing), EPIC-011 (Asset Lifecycle P0)
π― Objective¶
Build a 100% self-developed investment portfolio management system with comprehensive holdings tracking, performance metrics, and brokerage statement auto-parsing. This is a fully integrated, self-hosted solution.
Core Features: - Holdings Dashboard: Ticker, quantity, cost basis, market value, P&L - Performance Metrics: XIRR, time-weighted return, money-weighted return - Asset Allocation: Sector, geography, asset class breakdowns - Dividend Tracking: Dividend income, yield calculations - Cost Basis Methods: FIFO, LIFO, Average Cost - Brokerage Parsing: Auto-parse Moomoo, Futu, Interactive Brokers statements
Out of Scope (v1): - Trading execution (buy/sell orders) - Real-time market data streaming - Options/futures tracking - Crypto wallet integration
π₯ Multi-Role Review¶
| Role | Focus | Review Opinion |
|---|---|---|
| ποΈ Architect | Data Model | Extend atomic_positions (EPIC-011 Layer 2) for holdings. Use managed_positions (Layer 3) for cost basis calculations. |
| π Accountant | Accounting Integration | Buy/sell transactions β Journal entries. Realized P&L = Income account. Unrealized P&L = valuation adjustment. |
| π» Developer | Implementation | Extend extraction service (EPIC-003) for brokerage statements. Reuse market data service for price updates. |
| π§ͺ Tester | Validation | Test: XIRR calculation accuracy, cost basis methods (FIFO/LIFO/AvgCost), dividend accrual, P&L reconciliation. |
| π PM | User Experience | Dashboard = Quick overview. Detail pages for deep-dive. Manual price update UI (user updates every few months). |
| πΉ Investor | Domain Expert | XIRR is critical for multi-currency portfolios. Sector allocation helps rebalancing. Dividend tracking for tax reporting. |
β Task Checklist¶
Phase 1: Data Model & Holdings Tracking β 2 weeks¶
Data Model (Backend)¶
- [ ] Extend
atomic_positionsmodel (EPIC-011 Layer 2) - Already exists:
asset_identifier,quantity,market_value,currency,broker - Add fields:
asset_typeENUM (stock,bond,etf,mutual_fund) - Add fields:
sectorVARCHAR(50),geographyVARCHAR(50) (for allocation) - [ ] Extend
managed_positionsmodel (EPIC-011 Layer 3) - Already exists:
quantity,cost_basis,acquisition_date,status - Add fields:
cost_basis_methodENUM (FIFO,LIFO,AvgCost) - Add fields:
unrealized_pnlDECIMAL(18,2),realized_pnlDECIMAL(18,2) - [ ] Create
dividend_incometable - Fields:
id,user_id,position_id,payment_date,amount,currency,type(ordinary/qualified) - Links to
managed_positions - [ ] Create
market_data_overridetable (for manual price updates) - Fields:
id,user_id,asset_identifier,price_date,price,currency,source(manual/api) - Use Case: User updates prices every few months via UI
Backend Services¶
- [ ]
services/portfolio.pyβ Portfolio management service - [ ]
get_holdings(user_id)β Get current holdings summary- Return: ticker, quantity, cost basis, market value, unrealized P&L
- Group by account (brokerage)
- [ ]
calculate_realized_pnl(user_id, date_range)β Calculate realized P&L- Use cost basis method (FIFO/LIFO/AvgCost)
- Return: list of realized gains/losses
- [ ]
calculate_unrealized_pnl(user_id, as_of_date)β Calculate unrealized P&L- Market value - cost basis
- Return: total unrealized gain/loss
- [ ]
update_market_prices(user_id, price_updates)β Manual price update- Insert into
market_data_override - Recalculate unrealized P&L
- Insert into
- [ ]
services/performance.pyβ Performance metrics service - [ ]
calculate_xirr(user_id, account_id)β XIRR calculation- Use
scipy.optimize.newtonornumpy.irr - Return: annualized return (%)
- Use
- [ ]
calculate_time_weighted_return(user_id, date_range)β TWR calculation- Formula: TWR = [(1 + R1) Γ (1 + R2) Γ ... Γ (1 + Rn)] - 1
- Return: period return (%)
- [ ]
calculate_money_weighted_return(user_id, date_range)β MWR calculation- Alias for XIRR (IRR of cash flows)
- [ ]
services/allocation.pyβ Asset allocation service - [ ]
get_sector_allocation(user_id)β Sector breakdown- Return: list of (sector, market_value, percentage)
- [ ]
get_geography_allocation(user_id)β Geography breakdown- Return: list of (country, market_value, percentage)
- [ ]
get_asset_class_allocation(user_id)β Asset class breakdown- Return: list of (asset_type, market_value, percentage)
API Endpoints¶
- [ ]
GET /api/portfolio/holdingsβ Get holdings summary - [ ]
GET /api/portfolio/performanceβ Get performance metrics (XIRR, TWR, MWR) - [ ]
GET /api/portfolio/allocationβ Get asset allocation (sector, geography, asset class) - [ ]
GET /api/portfolio/dividendsβ Get dividend income history - [ ]
POST /api/portfolio/prices/updateβ Manual price update - Request body:
[{"ticker": "AAPL", "price": 150.00, "date": "2026-02-25"}]
Tests¶
- [ ]
test_get_holdings()β Holdings summary with mock data - [ ]
test_calculate_realized_pnl_fifo()β FIFO cost basis - [ ]
test_calculate_realized_pnl_lifo()β LIFO cost basis - [ ]
test_calculate_realized_pnl_avgcost()β Average cost basis - [ ]
test_calculate_unrealized_pnl()β Unrealized P&L calculation - [ ]
test_update_market_prices()β Manual price update - [ ]
test_calculate_xirr()β XIRR accuracy (compare with Excel XIRR) - [ ]
test_calculate_time_weighted_return()β TWR calculation - [ ]
test_get_sector_allocation()β Sector allocation breakdown
Phase 2: Brokerage Statement Parsing β 2 weeks¶
Backend Services¶
- [ ] Extend
services/extraction.pyβ Add brokerage statement parsing - [ ]
parse_moomoo_statement(file_path, user_id)β Moomoo statement parser- Extract: transactions (buy/sell), holdings snapshot, dividends
- Return: list of
atomic_positions,atomic_transactions
- [ ]
parse_futu_statement(file_path, user_id)β Futu statement parser - [ ]
parse_interactive_brokers_statement(file_path, user_id)β IB statement parser - [ ]
detect_broker(file_path)β Auto-detect broker from PDF metadata- Check: PDF title, header text, logo
- Return: broker name (moomoo/futu/ib/unknown)
- [ ] Update
services/processing_account.pyβ Handle investment transactions - [ ]
process_buy_transaction(txn)β Create journal entry for stock purchase- Debit: Asset:Investment:Securities (increase)
- Credit: Asset:Cash (decrease)
- [ ]
process_sell_transaction(txn)β Create journal entry for stock sale- Debit: Asset:Cash (increase)
- Credit: Asset:Investment:Securities (decrease)
- Income/Expense: Realized P&L
- [ ]
process_dividend_transaction(txn)β Create journal entry for dividend- Debit: Asset:Cash (increase)
- Credit: Income:Dividend (increase)
AI Parsing Prompts (Gemini)¶
- [ ] Create
prompts/brokerage_statement.txtβ Brokerage statement parsing prompt - Extract: Account number, period, holdings (ticker, quantity, value), transactions (date, type, ticker, quantity, price)
- Confidence scoring: High (table extracted), Medium (partial data), Low (manual fallback)
- [ ] Create
prompts/dividend_notice.txtβ Dividend notice parsing prompt - Extract: Ticker, payment date, amount per share, total amount
API Endpoints¶
- [ ]
POST /api/statements/uploadβ Extend to support brokerage statements - Auto-detect broker via
detect_broker() - Route to appropriate parser (moomoo/futu/ib)
- [ ]
GET /api/statements/{id}/holdingsβ Get holdings from statement - Return: list of holdings extracted from statement
Tests¶
- [ ]
test_parse_moomoo_statement()β Moomoo statement parsing (use fixture PDF) - [ ]
test_parse_futu_statement()β Futu statement parsing - [ ]
test_parse_interactive_brokers_statement()β IB statement parsing - [ ]
test_detect_broker_moomoo()β Broker detection (Moomoo PDF) - [ ]
test_detect_broker_futu()β Broker detection (Futu PDF) - [ ]
test_process_buy_transaction()β Buy transaction β journal entry - [ ]
test_process_sell_transaction()β Sell transaction β journal entry + realized P&L - [ ]
test_process_dividend_transaction()β Dividend transaction β journal entry
Phase 3: Frontend Dashboard & Manual Price UI β 2-3 weeks¶
Frontend UI¶
- [ ]
/portfolioβ Portfolio Dashboard Page - [ ] Holdings table
- Columns: Ticker, Quantity, Cost Basis, Market Value, Unrealized P&L (%, $)
- Sortable, searchable
- [ ] Performance summary cards
- XIRR, Time-Weighted Return, Money-Weighted Return
- Period filters: YTD, 1Y, 3Y, All Time
- [ ] Asset allocation charts
- Pie chart: Sector allocation
- Pie chart: Geography allocation
- Bar chart: Asset class allocation
- [ ] Dividend income timeline
- Bar chart: Monthly dividend income (last 12 months)
- [ ]
/portfolio/[ticker]β Holding Detail Page - [ ] Transaction history (buy/sell)
- [ ] Dividend history
- [ ] Cost basis breakdown (FIFO/LIFO/AvgCost comparison)
- [ ] Performance chart (line chart: market value over time)
- [ ]
/portfolio/pricesβ Manual Price Update Page - [ ] Price entry form
- Input: Ticker, Price, Date
- Batch entry: CSV upload support
- [ ] Current prices table
- Columns: Ticker, Current Price, Last Update Date, Source (manual/api)
- Edit inline
- [ ] Update history log
- Show: Date, Ticker, Old Price, New Price, Updated By
Frontend Components¶
- [ ]
components/portfolio/HoldingsTable.tsxβ Holdings table component - [ ]
components/portfolio/PerformanceCard.tsxβ Performance metric card - [ ]
components/portfolio/AllocationChart.tsxβ Asset allocation pie/bar chart - [ ]
components/portfolio/DividendTimeline.tsxβ Dividend income chart - [ ]
components/portfolio/PriceUpdateForm.tsxβ Manual price update form - [ ]
components/portfolio/TransactionHistory.tsxβ Transaction history list
Tests¶
- [ ] Manual UI test: Portfolio dashboard loads with mock data
- [ ] Manual UI test: Manual price update form submits successfully
- [ ] Manual UI test: Holdings table sorting/filtering
- [ ] Manual UI test: Allocation charts render correctly
Phase 4: Integration & Performance Optimization β 1-2 weeks¶
Backend Optimization¶
- [ ] Batch price updates β Update multiple tickers in single request
- [ ] Cache allocation results β Redis cache for expensive aggregations
- [ ] Index optimization β Add indexes on
managed_positions.user_id,dividend_income.position_id
Data Migration¶
- [ ] Migrate existing asset data from EPIC-011 to new schema
- Add
asset_type,sector,geographyto existing positions - Backfill cost basis data
Integration Tests¶
- [ ]
test_end_to_end_buy_sell_cycle()β Full cycle: upload statement β parse β create journal entries β calculate P&L - [ ]
test_dividend_accrual_to_income()β Dividend transaction β journal entry β income statement - [ ]
test_unrealized_pnl_balance_sheet()β Unrealized P&L β asset revaluation β balance sheet
π§ͺ Test Cases¶
Test Organization: Tests organized by feature blocks using ACx.y.z numbering. Coverage: See
apps/backend/tests/portfolio/
AC17.1: Holdings & P&L Tracking¶
| ID | Test Case | Test Function | File | Priority |
|---|---|---|---|---|
| AC17.1.1 | Holdings Summary | test_get_holdings_happy_path |
portfolio/test_portfolio_service.py |
P0 |
| AC17.1.2 | FIFO Cost Basis | TBD |
TBD (test to be implemented) |
P0 |
| AC17.1.3 | LIFO Cost Basis | TBD |
TBD (test to be implemented) |
P0 |
| AC17.1.4 | Average Cost Basis | TBD |
TBD (test to be implemented) |
P0 |
| AC17.1.5 | Unrealized P&L Calculation | test_unrealized_pnl_happy_path |
portfolio/test_portfolio_service.py |
P0 |
| AC17.1.6 | Manual Price Update | test_update_prices_happy |
portfolio/test_portfolio_service.py |
P1 |
AC17.2: Performance Metrics¶
| ID | Test Case | Test Function | File | Priority |
|---|---|---|---|---|
| AC17.2.1 | XIRR Accuracy (within 0.01% of Excel) | test_xirr_with_realistic_data |
portfolio/test_performance_service.py |
P0 |
| AC17.2.2 | Time-Weighted Return | test_time_weighted_return_with_period |
portfolio/test_performance_service.py |
P0 |
| AC17.2.3 | Money-Weighted Return | test_money_weighted_return_with_data |
portfolio/test_performance_service.py |
P1 |
AC17.3: Asset Allocation¶
| ID | Test Case | Test Function | File | Priority |
|---|---|---|---|---|
| AC17.3.1 | Sector Allocation Breakdown | test_sector_allocation_with_positions |
portfolio/test_allocation_service.py |
P1 |
| AC17.3.2 | Geography Allocation Breakdown | test_geography_allocation_with_positions |
portfolio/test_allocation_service.py |
P1 |
| AC17.3.3 | Asset Class Allocation Breakdown | test_asset_class_allocation_with_positions |
portfolio/test_allocation_service.py |
P1 |
AC17.4: Brokerage Statement Parsing¶
| ID | Test Case | Test Function | File | Priority |
|---|---|---|---|---|
| AC17.4.1 | Moomoo Statement Parsing | TBD |
TBD (test to be implemented) |
P0 |
| AC17.4.2 | Futu Statement Parsing | TBD |
TBD (test to be implemented) |
P1 |
| AC17.4.3 | Interactive Brokers Parsing | TBD |
TBD (test to be implemented) |
P1 |
| AC17.4.4 | Broker Auto-Detection (Moomoo) | TBD |
TBD (test to be implemented) |
P1 |
| AC17.4.5 | Broker Auto-Detection (Futu) | TBD |
TBD (test to be implemented) |
P1 |
AC17.5: Investment Accounting (Journal Entries)¶
| ID | Test Case | Test Function | File | Priority |
|---|---|---|---|---|
| AC17.5.1 | Buy Transaction β Journal Entry | TBD |
TBD (test to be implemented) |
P0 |
| AC17.5.2 | Sell Transaction β Journal Entry + Realized P&L | TBD |
TBD (test to be implemented) |
P0 |
| AC17.5.3 | Dividend β Journal Entry β Income Statement | TBD |
TBD (test to be implemented) |
P0 |
| AC17.5.4 | Unrealized P&L β Balance Sheet | test_unrealized_pnl_happy_path |
portfolio/test_portfolio_service.py |
P0 |
AC17.6: Integration & End-to-End¶
| ID | Test Case | Test Function | File | Priority |
|---|---|---|---|---|
| AC17.6.1 | Full Buy/Sell Cycle | TBD |
TBD (test to be implemented) |
P0 |
| AC17.6.2 | Dividend Accrual to Income | TBD |
TBD (test to be implemented) |
P0 |
Traceability Result:
- Total AC IDs: 22
- Requirements converted to AC IDs: 100% (EPIC-017 Must Have checklist)
- Requirements with test references: 100% (some TBD β tests to be implemented)
- Test files: 3 implemented (test_portfolio_service.py, test_performance_service.py, test_allocation_service.py); 3 TBD (test_cost_basis_methods.py, test_brokerage_parsing.py, integration tests)
π Acceptance Criteria¶
π’ Must Have¶
| Standard | Verification | Weight |
|---|---|---|
| XIRR calculation accuracy within 0.01% | AC17.2.1: test_calculate_xirr |
π΄ Critical |
| Cost basis methods (FIFO/LIFO/AvgCost) accurate | AC17.1.2βAC17.1.4 | π΄ Critical |
| Brokerage statements auto-parsed (Moomoo, Futu, IB) | AC17.4.1βAC17.4.3 | Required |
| Manual price update UI functional | AC17.1.6: test_update_market_prices |
Required |
| Holdings dashboard shows real-time P&L | AC17.6.1: test_end_to_end_buy_sell_cycle |
Required |
| Dividend income β journal entry β income statement | AC17.5.3, AC17.6.2 | Required |
| Asset allocation charts accurate | AC17.3.1βAC17.3.3 | Required |
π Nice to Have¶
| Standard | Verification | Status |
|---|---|---|
| CSV export for holdings | API endpoint | β³ |
| Mobile-responsive dashboard | Responsive design | β³ |
| Sector/geography auto-classification (ML) | AI service | β³ |
| Real-time price API integration | Market data API (Alpha Vantage, Yahoo Finance) | β³ |
π« Not Acceptable¶
- XIRR calculation error > 0.1% (indicates formula bug)
- Cost basis calculation wrong (causes P&L errors)
- Brokerage statements not parsed (manual entry required)
- Holdings dashboard stale (prices not updated)
- Unrealized P&L not reflected in balance sheet (accounting equation violation)
π SSOT References¶
- schema.md β
atomic_positions,managed_positions, database models - accounting.md β Journal entry rules for investment transactions
- extraction.md β Statement parsing patterns (extend for brokerage)
- vision.md Decision 1 β Portfolio management strategy (updated to 100% self-developed)
π Deliverables¶
Backend¶
- [ ]
apps/backend/src/models/portfolio.pyβ Portfolio models (extendatomic_positions,managed_positions) - [ ]
apps/backend/src/models/dividend.pyβ Dividend income model - [ ]
apps/backend/src/services/portfolio.pyβ Holdings, P&L calculations - [ ]
apps/backend/src/services/performance.pyβ XIRR, TWR, MWR calculations - [ ]
apps/backend/src/services/allocation.pyβ Asset allocation service - [ ]
apps/backend/src/services/extraction.pyβ Extend with brokerage parsers - [ ]
apps/backend/src/routers/portfolio.pyβ Portfolio API endpoints - [ ]
apps/backend/tests/portfolio/β Test suite test_portfolio_service.pytest_performance_metrics.pytest_allocation_service.pytest_brokerage_parsing.pytest_cost_basis_methods.py
Frontend¶
- [ ]
apps/frontend/src/app/(main)/portfolio/page.tsxβ Portfolio dashboard - [ ]
apps/frontend/src/app/(main)/portfolio/[ticker]/page.tsxβ Holding detail page - [ ]
apps/frontend/src/app/(main)/portfolio/prices/page.tsxβ Manual price update page - [ ]
apps/frontend/src/components/portfolio/β Portfolio components HoldingsTable.tsxPerformanceCard.tsxAllocationChart.tsxDividendTimeline.tsxPriceUpdateForm.tsx
Documentation¶
- [ ] Update
docs/ssot/extraction.mdβ Add brokerage statement parsing section - [ ] Update
README.mdβ Add portfolio management feature description
π Technical Debt¶
| Item | Priority | Planned Resolution |
|---|---|---|
| Real-time market data API | P2 | v2.0 (Alpha Vantage, Yahoo Finance integration) |
| Options/futures tracking | P3 | v2.0 (complex derivatives support) |
| Tax-loss harvesting | P3 | v2.0 (tax optimization features) |
| Sector/geography auto-classification | P2 | v2.0 (ML-based classification) |
π Known Issues & Gaps¶
- [ ] Manual Price Update Frequency: User must update prices manually. Real-time API integration deferred to v2.0.
- [ ] Brokerage Statement Coverage: Only Moomoo, Futu, IB supported in v1. Other brokers require manual entry.
- [ ] Cost Basis Edge Cases: Complex scenarios (stock splits, mergers) not handled in v1.
- [ ] Multi-Currency XIRR: XIRR calculation assumes single currency. Multi-currency portfolios require FX conversion.
β Q&A (Clarification Required)¶
Q1: Portfolio feature scope β Confirmed by user¶
Question: Should v1 include all metrics (XIRR, TWR, MWR, sector allocation, dividends)?
Impact: Backend service scope
User Answer: "Full-featured" (implement all metrics)
Decision: β Full scope in v1: XIRR, TWR, MWR, sector allocation, geography allocation, dividend tracking, cost basis methods.
Q2: Market data source β Confirmed by user¶
Question: Should v1 use real-time API (Alpha Vantage, Yahoo Finance) or manual entry?
Impact: Market data service design
User Answer: "ζδΈͺ UI η»΄ζ€ε§οΌθΏη±»εΊζ¬δΈε°±ζ―ε δΈͺζζΉδΈζ¬‘θε·²" (Manual UI, updated every few months)
Decision: β Manual price update UI in v1. Real-time API deferred to v2.0.
Q3: Brokerage statement parsing β Confirmed by user¶
Question: Should brokerage statements be uploaded like bank statements?
Impact: Extraction service extension
User Answer: "δΉζ―δΈδΌ statement ε§οΌθͺε¨θ§£ζ" (Upload statements, auto-parse)
Decision: β Extend EPIC-003 extraction service to support Moomoo, Futu, Interactive Brokers statements.
Q4: Cost basis method preference¶
Question: Should users choose cost basis method (FIFO/LIFO/AvgCost) per account or globally?
Impact:managed_positionsmodel (account-level vs user-level setting)
Status: β³ Pending user clarification
Q5: Unrealized P&L accounting treatment¶
Question: Should unrealized P&L be reflected in balance sheet immediately, or only on statement date?
Impact: Asset revaluation journal entry timing
Status: β³ Pending user clarification
Q6: Dividend tax withholding¶
Question: Should dividend income be recorded gross (before tax) or net (after withholding)?
Impact:dividend_incomemodel, journal entry
Status: β³ Pending user clarification
π Timeline¶
| Phase | Content | Duration | Status |
|---|---|---|---|
| Phase 1 | Data Model & Holdings Tracking | 2 weeks | β³ Planned |
| Week 1 | Extend models, create portfolio service | ||
| Week 2 | Performance metrics, allocation service, API endpoints | ||
| Phase 2 | Brokerage Statement Parsing | 2 weeks | β³ Planned |
| Week 3 | Extend extraction service (Moomoo, Futu, IB parsers) | ||
| Week 4 | Processing account integration, tests | ||
| Phase 3 | Frontend Dashboard & Manual Price UI | 2-3 weeks | β³ Planned |
| Week 5 | Portfolio dashboard, holdings table | ||
| Week 6 | Allocation charts, dividend timeline | ||
| Week 7 | Manual price update page, holding detail page | ||
| Phase 4 | Integration & Performance Optimization | 1-2 weeks | β³ Planned |
| Week 8 | Batch optimization, caching, migration |
Total Estimate: 6-8 weeks (depends on clarification response time)
π Related EPICs¶
- EPIC-002: Double-Entry Core β Journal entries for investment transactions
- EPIC-003: Statement Parsing β Extend for brokerage statements
- EPIC-011: Asset Lifecycle β
atomic_positions,managed_positionsdata model foundation - EPIC-005: Reporting β Unrealized P&L in balance sheet, realized P&L in income statement
π Success Metrics (Post-Launch)¶
- Brokerage Statement Parsing Accuracy: β₯ 95% (holdings, transactions, dividends extracted correctly)
- XIRR Calculation Accuracy: Within 0.01% of Excel XIRR
- Holdings Dashboard Load Time: < 2s (with 1000+ holdings)
- Manual Price Update Frequency: User updates prices β€ 1x per month (low-frequency, high-value)
- User Adoption: β₯ 80% of users with investment accounts use portfolio dashboard
Last updated: February 2026
π FE/UI Audit (April 2026)¶
Audit Date: 2026-04-06 | Auditor: AI Agent (Sisyphus) | Scope: Frontend completeness, UX quality, accessibility
Executive Summary¶
Backend: β
FULLY IMPLEMENTED (1,226 lines across 3 services + 234 lines router + 104 lines schemas)
- portfolio.py (628 lines) β Holdings CRUD, P&L calculations, price updates
- performance.py (413 lines) β XIRR, TWR, MWR calculations
- allocation.py (183 lines) β Sector/geography/asset class allocation
- portfolio router (234 lines) β All API endpoints operational
- portfolio schemas (104 lines) β Pydantic request/response models
- Test coverage: 1,780 lines, 64 test functions across 4 test files
Frontend: β NOT IMPLEMENTED β The entire portfolio frontend is missing. Only /assets page from EPIC-011 exists, which is a basic position tracker and does NOT fulfill EPIC-017 requirements.
Inventory: What Exists¶
| File | Lines | Purpose | Covers EPIC-017? |
|---|---|---|---|
assets/page.tsx |
280 | Position tracker from EPIC-011: KPI cards (Total Positions, Active Holdings, Total Cost Basis), currency allocation bar, grouped by broker, status filters | β οΈ Partial β shows basic holdings but lacks performance metrics, market value, P&L, allocation charts, dividends |
components/charts/PieChart.tsx |
94 | Generic SVG donut chart | π οΈ Reusable for allocation |
components/charts/TrendChart.tsx |
95 | Generic SVG line/area chart | π οΈ Reusable for performance over time |
components/charts/BarChart.tsx |
55 | Generic SVG bar chart | π οΈ Reusable for dividend timeline |
components/charts/SankeyChart.tsx |
186 | ECharts sankey (cash flow) | β Not relevant |
Sidebar.tsx line 39 |
β | "Portfolio" nav item points to /assets, not /portfolio |
β οΈ Wrong route |
Gap Analysis¶
π΄ Critical Gaps (entire feature missing)¶
| # | Gap | EPIC Requirement | Status |
|---|---|---|---|
| G1 | No /portfolio route |
EPIC Phase 3 specifies /portfolio dashboard page, /portfolio/[ticker] detail page, /portfolio/prices price update page |
apps/frontend/src/app/(main)/portfolio/ directory does not exist. Zero files. |
| G2 | No portfolio components | EPIC specifies HoldingsTable.tsx, PerformanceCard.tsx, AllocationChart.tsx, DividendTimeline.tsx, PriceUpdateForm.tsx, TransactionHistory.tsx |
apps/frontend/src/components/portfolio/ directory does not exist. Zero files. |
| G3 | No portfolio API client functions | Backend exposes /api/portfolio/holdings, /api/portfolio/performance, /api/portfolio/allocation, /api/portfolio/prices |
lib/api.ts has zero portfolio-specific functions. lib/types.ts has only ManagedPosition from EPIC-011, no portfolio response types. |
| G4 | No performance metrics display | XIRR, TWR (Time-Weighted Return), MWR (Money-Weighted Return) β core portfolio feature | No UI anywhere shows these calculations. Backend computes them; frontend does not consume them. |
| G5 | No market value or P&L display | Current holdings should show: Market Value, Unrealized P&L, Realized P&L | /assets page shows only Cost Basis (book value). No market value column, no P&L. |
| G6 | No allocation charts | Sector, geography, asset class breakdown with pie/donut charts | No portfolio-specific charts. Generic PieChart.tsx exists and is reusable, but no page or component consumes it for allocation data. |
| G7 | No dividend tracking UI | Dividend timeline, yield calculations, ex-date tracking | Zero implementation. |
| G8 | No market price update page | Manual price update form (user updates prices monthly) | Zero implementation. Backend has POST /api/portfolio/prices endpoint ready. |
| G9 | No holding detail page | /portfolio/[ticker] with transaction history, performance chart, cost basis breakdown |
Zero implementation. |
| G10 | No frontend tests | EPIC requires frontend test coverage for portfolio features | Zero portfolio frontend tests (backend has 64 test functions). |
π‘ Important Gaps (existing /assets page shortcomings)¶
| # | Gap | Detail |
|---|---|---|
| G11 | Sidebar routing mismatch | "Portfolio" nav item routes to /assets instead of /portfolio. When portfolio pages are built, this needs updating. |
| G12 | /assets page uses wrong API |
Calls /api/assets/positions (EPIC-011) instead of /api/portfolio/holdings (EPIC-017). Different data shape β positions vs holdings with market data. |
| G13 | No cost basis method selection | EPIC-017 supports FIFO/LIFO/AvgCost. /assets page shows a single cost basis number with no method indicator or toggle. |
| G14 | Currency allocation bar is custom, not chart component | /assets builds its own allocation bar instead of using the generic PieChart.tsx. Inconsistent with design system. |
What Can Be Reused¶
The project has chart infrastructure and design patterns ready for portfolio features:
| Asset | Reusable For |
|---|---|
PieChart.tsx (94 lines) |
Allocation breakdown (sector, geography, asset class) |
TrendChart.tsx (95 lines) |
Performance over time (XIRR trend, NAV history) |
BarChart.tsx (55 lines) |
Dividend timeline (monthly/quarterly bars) |
lib/api.ts wrapper |
All portfolio API calls (just add typed functions) |
| CSS design tokens | --chart-1 through --chart-5 palette, .card, .badge-*, .page-header |
ConfirmDialog, Sheet, Toast |
UI primitives for price update confirmation, holding detail panel, success/error feedback |
@tanstack/react-query |
Already installed, used by /assets page. Use for portfolio data fetching. |
decimal.js |
Already installed. Use for frontend P&L display precision. |
Recommendations (Priority Order)¶
- [P0] Create
/portfolioroute with holdings dashboard β This is the single highest-impact deliverable. Show holdings table with columns: Ticker, Name, Quantity, Avg Cost, Market Price, Market Value, Unrealized P&L, P&L %. ConsumeGET /api/portfolio/holdings. - [P0] Add performance summary cards β Display XIRR, TWR, MWR from
GET /api/portfolio/performance. UsePerformanceCardcomponent with period selector (1M, 3M, 6M, 1Y, YTD, All). - [P0] Add portfolio TypeScript types β Define
PortfolioHolding,PerformanceMetrics,AllocationBreakdown,DividendRecord,PriceUpdatetypes inlib/types.tsmatching backend schemas. - [P1] Build allocation charts page section β Use existing
PieChart.tsxto show sector/geography/asset class allocation fromGET /api/portfolio/allocation. - [P1] Build market price update page β
/portfolio/priceswith form to update prices. BackendPOST /api/portfolio/pricesis ready. - [P1] Build holding detail page β
/portfolio/[ticker]with transaction history, performance chart, cost basis breakdown (FIFO/LIFO/AvgCost toggle). - [P2] Add dividend timeline β Use
BarChart.tsxfor monthly dividend income visualization. - [P2] Update Sidebar routing β Change "Portfolio" nav from
/assetsto/portfolio. Consider keeping/assetsas a sub-feature or deprecating it. - [P3] Write frontend tests β Test holdings table rendering, performance card calculations display, allocation chart data transformation, price update form validation.
Implementation Estimate¶
Given that the backend is 100% complete and chart infrastructure exists, the frontend build-out is estimated at:
| Phase | Deliverable | Effort |
|---|---|---|
| 1 | Portfolio types + API functions + holdings dashboard | 2-3 days |
| 2 | Performance cards + allocation charts | 1-2 days |
| 3 | Price update page + holding detail page | 2-3 days |
| 4 | Dividend timeline + frontend tests | 1-2 days |
| Total | 6-10 days |
FE/UI Audit appended: April 2026
π UI Gap Audit (April 2026) β Dividends, Cost Basis, Realized P&L Frontend¶
Origin: UI gap audit against vision.md decision 1 (100% self-developed portfolio with XIRR/TWR/MWR, dividend tracking, cost basis methods). Backend portfolio APIs are planned but the frontend has no surfaces for dividend history, cost-basis selection, or realized P&L per holding.
Acceptance Criteria¶
- [ ] AC17.7.1 Holding detail page
/portfolio/[ticker]renders three tabs:Overview,Dividends,Realized P&L - [ ] AC17.7.2 Dividends tab lists historical dividend events
{ex_date, pay_date, amount, currency, reinvested}fromGET /api/portfolio/{ticker}/dividends - [ ] AC17.7.3 Cost-basis method selector (
FIFO/LIFO/AvgCost) on holding detail page persists per-holding viaPATCH /api/portfolio/{ticker}and re-fetches realized P&L - [ ] AC17.7.4 Realized P&L tab shows lot-level table
{lot_id, acquired_date, sold_date, quantity, basis, proceeds, gain_loss, holding_period}fromGET /api/portfolio/{ticker}/realized - [ ] AC17.7.5 Portfolio summary card on dashboard adds
realized_pnl_ytdanddividend_income_ytdfigures fromGET /api/portfolio/summary - [ ] AC17.7.6 Frontend test mounts HoldingDetailPage, switches to Dividends tab, and asserts dividend row labels render
Priority: P1 β depends on backend portfolio API delivery; surfaces vision-critical metrics. Estimated effort: 5-7 days frontend (3 tabs + cost-basis selector + summary additions); backend dividend/realized endpoints tracked in core EPIC-017 scope.