Skip to content

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_positions model (EPIC-011 Layer 2)
  • Already exists: asset_identifier, quantity, market_value, currency, broker
  • Add fields: asset_type ENUM (stock, bond, etf, mutual_fund)
  • Add fields: sector VARCHAR(50), geography VARCHAR(50) (for allocation)
  • [ ] Extend managed_positions model (EPIC-011 Layer 3)
  • Already exists: quantity, cost_basis, acquisition_date, status
  • Add fields: cost_basis_method ENUM (FIFO, LIFO, AvgCost)
  • Add fields: unrealized_pnl DECIMAL(18,2), realized_pnl DECIMAL(18,2)
  • [ ] Create dividend_income table
  • Fields: id, user_id, position_id, payment_date, amount, currency, type (ordinary/qualified)
  • Links to managed_positions
  • [ ] Create market_data_override table (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
  • [ ] services/performance.py β€” Performance metrics service
  • [ ] calculate_xirr(user_id, account_id) β€” XIRR calculation
    • Use scipy.optimize.newton or numpy.irr
    • Return: annualized return (%)
  • [ ] 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, geography to 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 (extend atomic_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.py
  • test_performance_metrics.py
  • test_allocation_service.py
  • test_brokerage_parsing.py
  • test_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.tsx
  • PerformanceCard.tsx
  • AllocationChart.tsx
  • DividendTimeline.tsx
  • PriceUpdateForm.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_positions model (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_income model, 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)


  • 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_positions data 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)

  1. [P0] Create /portfolio route 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 %. Consume GET /api/portfolio/holdings.
  2. [P0] Add performance summary cards β€” Display XIRR, TWR, MWR from GET /api/portfolio/performance. Use PerformanceCard component with period selector (1M, 3M, 6M, 1Y, YTD, All).
  3. [P0] Add portfolio TypeScript types β€” Define PortfolioHolding, PerformanceMetrics, AllocationBreakdown, DividendRecord, PriceUpdate types in lib/types.ts matching backend schemas.
  4. [P1] Build allocation charts page section β€” Use existing PieChart.tsx to show sector/geography/asset class allocation from GET /api/portfolio/allocation.
  5. [P1] Build market price update page β€” /portfolio/prices with form to update prices. Backend POST /api/portfolio/prices is ready.
  6. [P1] Build holding detail page β€” /portfolio/[ticker] with transaction history, performance chart, cost basis breakdown (FIFO/LIFO/AvgCost toggle).
  7. [P2] Add dividend timeline β€” Use BarChart.tsx for monthly dividend income visualization.
  8. [P2] Update Sidebar routing β€” Change "Portfolio" nav from /assets to /portfolio. Consider keeping /assets as a sub-feature or deprecating it.
  9. [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} from GET /api/portfolio/{ticker}/dividends
  • [ ] AC17.7.3 Cost-basis method selector (FIFO / LIFO / AvgCost) on holding detail page persists per-holding via PATCH /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} from GET /api/portfolio/{ticker}/realized
  • [ ] AC17.7.5 Portfolio summary card on dashboard adds realized_pnl_ytd and dividend_income_ytd figures from GET /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.