"""Strategy endpoints — list strategies, weight history, metrics.""" from __future__ import annotations from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Request, status from services.api_gateway.auth.middleware import get_current_user from sqlalchemy import select, desc, func router = APIRouter(prefix="/api/strategies", tags=["strategies"]) @router.get("") async def list_strategies( request: Request, _user: dict = Depends(get_current_user), ) -> list[dict]: """All strategies with current weights and computed performance fields.""" from shared.models.trading import Strategy, Trade, TradeStatus db = request.app.state.db_session_factory async with db() as session: result = await session.execute(select(Strategy)) strategies = result.scalars().all() # Compute per-strategy stats from trades table strategy_stats: dict[UUID, dict] = {} for s in strategies: trades_result = await session.execute( select(Trade).where( Trade.strategy_id == s.id, Trade.status == TradeStatus.FILLED, ) ) trades = trades_result.scalars().all() total_trades = len(trades) winning = sum(1 for t in trades if t.pnl is not None and t.pnl > 0) total_pnl = sum(t.pnl for t in trades if t.pnl is not None) win_rate = winning / total_trades if total_trades > 0 else 0.0 strategy_stats[s.id] = { "win_rate": round(win_rate, 4), "total_pnl": round(total_pnl, 2), "total_trades": total_trades, } return [ { "id": str(s.id), "name": s.name, "description": s.description, "current_weight": s.current_weight, "active": s.active, "win_rate": strategy_stats[s.id]["win_rate"], "total_pnl": strategy_stats[s.id]["total_pnl"], "total_trades": strategy_stats[s.id]["total_trades"], "created_at": s.created_at.isoformat() if s.created_at else None, } for s in strategies ] @router.get("/weight-history") async def get_all_weight_history( request: Request, _user: dict = Depends(get_current_user), ) -> list[dict]: """Aggregated weight history pivoted by timestamp for chart display. Returns data in the format: ``[{"timestamp": "...", "momentum": 0.35, "mean_reversion": 0.30, ...}, ...]`` """ from shared.models.trading import StrategyWeightHistory, Strategy db = request.app.state.db_session_factory async with db() as session: result = await session.execute( select(StrategyWeightHistory, Strategy.name) .join(Strategy, StrategyWeightHistory.strategy_id == Strategy.id) .order_by(StrategyWeightHistory.created_at) .limit(200) ) rows = result.all() # Pivot: group by timestamp, create one object per timestamp # with strategy names as keys and new_weight as values from collections import OrderedDict pivoted: OrderedDict[str, dict] = OrderedDict() for h, name in rows: ts = h.created_at.isoformat() if h.created_at else "" if ts not in pivoted: pivoted[ts] = {"timestamp": ts} pivoted[ts][name] = h.new_weight return list(pivoted.values()) @router.get("/{strategy_id}/history") async def get_strategy_weight_history( strategy_id: UUID, request: Request, _user: dict = Depends(get_current_user), ) -> list[dict]: """Weight history for a specific strategy.""" from shared.models.trading import StrategyWeightHistory, Strategy db = request.app.state.db_session_factory async with db() as session: # Verify strategy exists strategy = ( await session.execute( select(Strategy).where(Strategy.id == strategy_id) ) ).scalar_one_or_none() if strategy is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Strategy not found", ) result = await session.execute( select(StrategyWeightHistory) .where(StrategyWeightHistory.strategy_id == strategy_id) .order_by(desc(StrategyWeightHistory.created_at)) ) history = result.scalars().all() return [ { "id": str(h.id), "old_weight": h.old_weight, "new_weight": h.new_weight, "reason": h.reason, "created_at": h.created_at.isoformat() if h.created_at else None, } for h in history ] @router.get("/{strategy_id}/metrics") async def get_strategy_metrics( strategy_id: UUID, request: Request, _user: dict = Depends(get_current_user), ) -> list[dict]: """Performance metrics over time for a specific strategy.""" from shared.models.timeseries import StrategyMetric db = request.app.state.db_session_factory async with db() as session: result = await session.execute( select(StrategyMetric) .where(StrategyMetric.strategy_id == strategy_id) .order_by(desc(StrategyMetric.timestamp)) .limit(100) ) metrics = result.scalars().all() return [ { "timestamp": m.timestamp.isoformat(), "win_rate": m.win_rate, "total_pnl": m.total_pnl, "trade_count": m.trade_count, "sharpe_ratio": m.sharpe_ratio, } for m in metrics ]