"""Portfolio endpoints — current value, positions, equity curve.""" from __future__ import annotations from datetime import datetime, timedelta, timezone from enum import Enum from fastapi import APIRouter, Depends, Query, Request from services.api_gateway.auth.middleware import get_current_user from sqlalchemy import select, desc router = APIRouter(prefix="/api/portfolio", tags=["portfolio"]) class HistoryPeriod(str, Enum): ONE_DAY = "1d" ONE_WEEK = "1w" ONE_MONTH = "1m" THREE_MONTHS = "3m" ONE_YEAR = "1y" def _period_to_timedelta(period: HistoryPeriod) -> timedelta: """Convert a period enum value to a timedelta.""" mapping = { HistoryPeriod.ONE_DAY: timedelta(days=1), HistoryPeriod.ONE_WEEK: timedelta(weeks=1), HistoryPeriod.ONE_MONTH: timedelta(days=30), HistoryPeriod.THREE_MONTHS: timedelta(days=90), HistoryPeriod.ONE_YEAR: timedelta(days=365), } return mapping[period] @router.get("") async def get_portfolio( request: Request, _user: dict = Depends(get_current_user), ) -> dict: """Current portfolio summary — value, cash, buying power, daily P&L.""" from shared.models.timeseries import PortfolioSnapshot db = request.app.state.db_session_factory async with db() as session: latest = ( await session.execute( select(PortfolioSnapshot) .order_by(desc(PortfolioSnapshot.timestamp)) .limit(1) ) ).scalar_one_or_none() if latest is None: return { "total_value": 0.0, "cash": 0.0, "buying_power": 0.0, "daily_pnl": 0.0, } return { "total_value": latest.total_value, "cash": latest.cash, "buying_power": latest.cash, "daily_pnl": latest.daily_pnl, } @router.get("/positions") async def get_positions( request: Request, _user: dict = Depends(get_current_user), ) -> list[dict]: """All open positions with unrealized P&L.""" from shared.models.trading import Position db = request.app.state.db_session_factory async with db() as session: result = await session.execute(select(Position)) positions = result.scalars().all() return [ { "id": str(p.id), "ticker": p.ticker, "qty": p.qty, "avg_entry": p.avg_entry, "unrealized_pnl": p.unrealized_pnl or 0.0, "stop_loss": p.stop_loss, "take_profit": p.take_profit, } for p in positions ] @router.get("/history") async def get_portfolio_history( request: Request, _user: dict = Depends(get_current_user), period: HistoryPeriod = Query(default=HistoryPeriod.ONE_MONTH), ) -> list[dict]: """Equity curve from portfolio_snapshots over a given period.""" from shared.models.timeseries import PortfolioSnapshot since = datetime.now(timezone.utc) - _period_to_timedelta(period) db = request.app.state.db_session_factory async with db() as session: result = await session.execute( select(PortfolioSnapshot) .where(PortfolioSnapshot.timestamp >= since) .order_by(PortfolioSnapshot.timestamp) ) snapshots = result.scalars().all() return [ { "timestamp": s.timestamp.isoformat(), "total_value": s.total_value, "cash": s.cash, "positions_value": s.positions_value, "daily_pnl": s.daily_pnl, } for s in snapshots ]