123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- 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)}")
|