import logging from fastapi import APIRouter, Depends, HTTPException, Query from typing import List, Optional, Dict, Any from pydantic import BaseModel from datetime import datetime from src.stats.trading_stats import TradingStats from ..dependencies import get_stats logger = logging.getLogger(__name__) router = APIRouter(prefix="/analytics", tags=["analytics"]) # Pydantic models for response data class OverallStats(BaseModel): balance: float initial_balance: float total_pnl: float total_return_pct: float win_rate: float profit_factor: float total_trades: int total_wins: int total_losses: int expectancy: float avg_trade_pnl: float avg_win_pnl: float avg_loss_pnl: float largest_win: float largest_loss: float largest_win_token: str largest_loss_token: str max_drawdown: float max_drawdown_pct: float best_token: str best_token_pnl: float worst_token: str worst_token_pnl: float total_volume: float class TokenStats(BaseModel): token: str total_pnl: float win_rate: float profit_factor: float total_trades: int winning_trades: int losing_trades: int roe_percentage: float entry_volume: float exit_volume: float avg_duration: str largest_win: float largest_loss: float class PeriodStats(BaseModel): period: str period_formatted: str has_trades: bool pnl: float pnl_pct: float roe: float trades: int volume: float class PerformanceMetrics(BaseModel): sharpe_ratio: Optional[float] max_consecutive_wins: int max_consecutive_losses: int avg_trade_duration: str best_roe_trade: Optional[Dict[str, Any]] worst_roe_trade: Optional[Dict[str, Any]] volatility: float risk_metrics: Dict[str, Any] @router.get("/stats", response_model=OverallStats) async def get_overall_stats( stats: TradingStats = Depends(get_stats) ): """Get overall trading statistics - equivalent to /stats command.""" try: # Get performance stats perf_stats = stats.get_performance_stats() basic_stats = stats.get_basic_stats() return OverallStats( balance=basic_stats.get('current_balance', 0.0), initial_balance=perf_stats.get('initial_balance', 0.0), total_pnl=perf_stats.get('total_pnl', 0.0), total_return_pct=(perf_stats.get('total_pnl', 0.0) / max(perf_stats.get('initial_balance', 1.0), 1.0) * 100), win_rate=perf_stats.get('win_rate', 0.0), profit_factor=perf_stats.get('profit_factor', 0.0), total_trades=perf_stats.get('total_trades', 0), total_wins=perf_stats.get('total_wins', 0), total_losses=perf_stats.get('total_losses', 0), expectancy=perf_stats.get('expectancy', 0.0), avg_trade_pnl=perf_stats.get('avg_trade_pnl', 0.0), avg_win_pnl=perf_stats.get('avg_win_pnl', 0.0), avg_loss_pnl=perf_stats.get('avg_loss_pnl', 0.0), largest_win=perf_stats.get('largest_win', 0.0), largest_loss=perf_stats.get('largest_loss', 0.0), largest_win_token=perf_stats.get('largest_win_token', 'N/A'), largest_loss_token=perf_stats.get('largest_loss_token', 'N/A'), max_drawdown=perf_stats.get('max_drawdown', 0.0), max_drawdown_pct=perf_stats.get('max_drawdown_pct', 0.0), best_token=perf_stats.get('best_token', 'N/A'), best_token_pnl=perf_stats.get('best_token_pnl', 0.0), worst_token=perf_stats.get('worst_token', 'N/A'), worst_token_pnl=perf_stats.get('worst_token_pnl', 0.0), total_volume=perf_stats.get('total_entry_volume', 0.0) ) except Exception as e: logger.error(f"Error getting overall stats: {e}") raise HTTPException(status_code=500, detail=f"Error getting overall stats: {str(e)}") @router.get("/stats/{token}", response_model=TokenStats) async def get_token_stats( token: str, stats: TradingStats = Depends(get_stats) ): """Get detailed statistics for a specific token - equivalent to /stats {token} command.""" try: token_data = stats.get_token_detailed_stats(token.upper()) if not token_data or token_data.get('summary_total_trades', 0) == 0: raise HTTPException( status_code=404, detail=f"No trading data found for {token.upper()}" ) perf_summary = token_data.get('performance_summary', {}) return TokenStats( token=token.upper(), total_pnl=perf_summary.get('total_pnl', 0.0), win_rate=perf_summary.get('win_rate', 0.0), profit_factor=perf_summary.get('profit_factor', 0.0), total_trades=perf_summary.get('completed_trades', 0), winning_trades=perf_summary.get('total_wins', 0), losing_trades=perf_summary.get('total_losses', 0), roe_percentage=(perf_summary.get('total_pnl', 0.0) / max(perf_summary.get('completed_entry_volume', 1.0), 1.0) * 100), entry_volume=perf_summary.get('completed_entry_volume', 0.0), exit_volume=perf_summary.get('completed_exit_volume', 0.0), avg_duration=perf_summary.get('avg_trade_duration', 'N/A'), largest_win=perf_summary.get('largest_win', 0.0), largest_loss=perf_summary.get('largest_loss', 0.0) ) except HTTPException: raise except Exception as e: logger.error(f"Error getting token stats for {token}: {e}") raise HTTPException(status_code=500, detail=f"Error getting token stats: {str(e)}") @router.get("/performance", response_model=List[TokenStats]) async def get_performance_ranking( limit: int = Query(20, ge=1, le=50), stats: TradingStats = Depends(get_stats) ): """Get performance ranking of all tokens - equivalent to /performance command.""" try: token_performance = stats.get_token_performance(limit=limit) result = [] for token_data in token_performance: result.append(TokenStats( token=token_data['token'], total_pnl=token_data.get('total_realized_pnl', 0.0), win_rate=token_data.get('win_rate', 0.0), profit_factor=token_data.get('profit_factor', 0.0), total_trades=token_data.get('total_completed_cycles', 0), winning_trades=token_data.get('winning_cycles', 0), losing_trades=token_data.get('losing_cycles', 0), roe_percentage=token_data.get('roe_percentage', 0.0), entry_volume=token_data.get('total_entry_volume', 0.0), exit_volume=token_data.get('total_exit_volume', 0.0), avg_duration=token_data.get('average_trade_duration_formatted', 'N/A'), largest_win=token_data.get('largest_winning_cycle_pnl', 0.0), largest_loss=token_data.get('largest_losing_cycle_pnl', 0.0) )) return result except Exception as e: logger.error(f"Error getting performance ranking: {e}") raise HTTPException(status_code=500, detail=f"Error getting performance ranking: {str(e)}") @router.get("/daily", response_model=List[PeriodStats]) async def get_daily_stats( limit: int = Query(10, ge=1, le=30), stats: TradingStats = Depends(get_stats) ): """Get daily performance stats - equivalent to /daily command.""" try: daily_stats = stats.get_daily_stats(limit=limit) result = [] for day_data in daily_stats: result.append(PeriodStats( period=day_data['date'], period_formatted=day_data['date_formatted'], has_trades=day_data['has_trades'], pnl=day_data['pnl'], pnl_pct=day_data['pnl_pct'], roe=day_data['roe'], trades=day_data['trades'], volume=day_data.get('volume', 0.0) )) return result except Exception as e: logger.error(f"Error getting daily stats: {e}") raise HTTPException(status_code=500, detail=f"Error getting daily stats: {str(e)}") @router.get("/weekly", response_model=List[PeriodStats]) async def get_weekly_stats( limit: int = Query(10, ge=1, le=20), stats: TradingStats = Depends(get_stats) ): """Get weekly performance stats - equivalent to /weekly command.""" try: weekly_stats = stats.get_weekly_stats(limit=limit) result = [] for week_data in weekly_stats: result.append(PeriodStats( period=week_data['week'], period_formatted=week_data['week_formatted'], has_trades=week_data['has_trades'], pnl=week_data['pnl'], pnl_pct=week_data['pnl_pct'], roe=week_data.get('roe', 0.0), trades=week_data['trades'], volume=week_data['volume'] )) return result except Exception as e: logger.error(f"Error getting weekly stats: {e}") raise HTTPException(status_code=500, detail=f"Error getting weekly stats: {str(e)}") @router.get("/monthly", response_model=List[PeriodStats]) async def get_monthly_stats( limit: int = Query(12, ge=1, le=24), stats: TradingStats = Depends(get_stats) ): """Get monthly performance stats - equivalent to /monthly command.""" try: monthly_stats = stats.get_monthly_stats(limit=limit) result = [] for month_data in monthly_stats: result.append(PeriodStats( period=month_data['month'], period_formatted=month_data['month_formatted'], has_trades=month_data['has_trades'], pnl=month_data['pnl'], pnl_pct=month_data['pnl_pct'], roe=month_data.get('roe', 0.0), trades=month_data['trades'], volume=month_data['volume'] )) return result except Exception as e: logger.error(f"Error getting monthly stats: {e}") raise HTTPException(status_code=500, detail=f"Error getting monthly stats: {str(e)}") @router.get("/metrics", response_model=PerformanceMetrics) async def get_performance_metrics( stats: TradingStats = Depends(get_stats) ): """Get advanced performance metrics including risk analysis.""" try: perf_stats = stats.get_performance_stats() risk_metrics = stats.performance_calculator.get_risk_metrics() # Calculate additional metrics best_roe = perf_stats.get('best_roe_trade', {}) worst_roe = perf_stats.get('worst_roe_trade', {}) return PerformanceMetrics( sharpe_ratio=risk_metrics.get('sharpe_ratio'), max_consecutive_wins=0, # TODO: Implement max_consecutive_losses=0, # TODO: Implement avg_trade_duration="N/A", # TODO: Calculate from stats best_roe_trade=best_roe if best_roe else None, worst_roe_trade=worst_roe if worst_roe else None, volatility=0.0, # TODO: Calculate risk_metrics=risk_metrics ) except Exception as e: logger.error(f"Error getting performance metrics: {e}") raise HTTPException(status_code=500, detail=f"Error getting performance metrics: {str(e)}") @router.get("/balance-history") async def get_balance_history( days: int = Query(30, ge=1, le=365), stats: TradingStats = Depends(get_stats) ): """Get balance history for charting.""" try: balance_data, balance_stats = stats.performance_calculator.get_balance_history(days=days) return { "balance_history": balance_data, "stats": balance_stats, "days": days } except Exception as e: logger.error(f"Error getting balance history: {e}") raise HTTPException(status_code=500, detail=f"Error getting balance history: {str(e)}") @router.get("/summary") async def get_analytics_summary( stats: TradingStats = Depends(get_stats) ): """Get analytics summary for overview page.""" try: # Get key metrics perf_stats = stats.get_performance_stats() daily_stats = stats.get_daily_stats(limit=7) top_tokens = stats.get_token_performance(limit=5) # Calculate recent trend recent_pnl = sum(day['pnl'] for day in daily_stats if day['has_trades']) recent_trades = sum(day['trades'] for day in daily_stats if day['has_trades']) return { "performance_overview": { "total_pnl": perf_stats.get('total_pnl', 0.0), "win_rate": perf_stats.get('win_rate', 0.0), "profit_factor": perf_stats.get('profit_factor', 0.0), "total_trades": perf_stats.get('total_trades', 0), "max_drawdown_pct": perf_stats.get('max_drawdown_pct', 0.0) }, "recent_performance": { "pnl_7d": recent_pnl, "trades_7d": recent_trades, "daily_avg": recent_pnl / 7 if recent_pnl else 0.0 }, "top_tokens": top_tokens[:3], # Top 3 performers "last_updated": datetime.now().isoformat() } except Exception as e: logger.error(f"Error getting analytics summary: {e}") raise HTTPException(status_code=500, detail=f"Error getting analytics summary: {str(e)}")