From 7edc152491ec1d4f69b83064309ca6e120fc7b06 Mon Sep 17 00:00:00 2001 From: zephyrdark Date: Tue, 3 Feb 2026 07:11:19 +0900 Subject: [PATCH] feat: add portfolio detail API with calculated values --- backend/app/api/portfolio.py | 63 +++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/backend/app/api/portfolio.py b/backend/app/api/portfolio.py index 5eae1c5..f024de2 100644 --- a/backend/app/api/portfolio.py +++ b/backend/app/api/portfolio.py @@ -1,6 +1,7 @@ """ Portfolio management API endpoints. """ +from decimal import Decimal from typing import List from fastapi import APIRouter, Depends, HTTPException, status @@ -12,7 +13,7 @@ from app.models.portfolio import Portfolio, PortfolioType, Target, Holding, Tran from app.schemas.portfolio import ( PortfolioCreate, PortfolioUpdate, PortfolioResponse, PortfolioDetail, TargetCreate, TargetResponse, - HoldingCreate, HoldingResponse, + HoldingCreate, HoldingResponse, HoldingWithValue, TransactionCreate, TransactionResponse, RebalanceResponse, RebalanceSimulationRequest, RebalanceSimulationResponse, ) @@ -316,3 +317,63 @@ async def simulate_rebalance( portfolio = _get_portfolio(db, portfolio_id, current_user.id) service = RebalanceService(db) return service.calculate_rebalance(portfolio, additional_amount=data.additional_amount) + + +@router.get("/{portfolio_id}/detail", response_model=PortfolioDetail) +async def get_portfolio_detail( + portfolio_id: int, + current_user: CurrentUser, + db: Session = Depends(get_db), +): + """Get portfolio with calculated values.""" + portfolio = _get_portfolio(db, portfolio_id, current_user.id) + + # Get current prices + tickers = [h.ticker for h in portfolio.holdings] + service = RebalanceService(db) + prices = service.get_current_prices(tickers) + + # Calculate holding values + holdings_with_value = [] + total_value = Decimal("0") + total_invested = Decimal("0") + + for holding in portfolio.holdings: + current_price = prices.get(holding.ticker, Decimal("0")) + value = current_price * holding.quantity + invested = Decimal(str(holding.avg_price)) * holding.quantity + profit_loss = value - invested + profit_loss_ratio = (profit_loss / invested * 100) if invested > 0 else Decimal("0") + + total_value += value + total_invested += invested + + holdings_with_value.append(HoldingWithValue( + ticker=holding.ticker, + quantity=holding.quantity, + avg_price=Decimal(str(holding.avg_price)), + current_price=current_price, + value=value, + current_ratio=Decimal("0"), # Will be calculated after total + profit_loss=profit_loss, + profit_loss_ratio=profit_loss_ratio.quantize(Decimal("0.01")), + )) + + # Calculate current ratios + for h in holdings_with_value: + if total_value > 0: + h.current_ratio = (h.value / total_value * 100).quantize(Decimal("0.01")) + + return PortfolioDetail( + id=portfolio.id, + user_id=portfolio.user_id, + name=portfolio.name, + portfolio_type=portfolio.portfolio_type.value, + created_at=portfolio.created_at, + updated_at=portfolio.updated_at, + targets=portfolio.targets, + holdings=holdings_with_value, + total_value=total_value, + total_invested=total_invested, + total_profit_loss=total_value - total_invested, + )