|
@@ -40,172 +40,129 @@ class PerformanceCalculator:
|
|
return f"{int(days)}d {int(hours)}h"
|
|
return f"{int(days)}d {int(hours)}h"
|
|
|
|
|
|
def get_performance_stats(self) -> Dict[str, Any]:
|
|
def get_performance_stats(self) -> Dict[str, Any]:
|
|
- """Get comprehensive trading performance statistics."""
|
|
|
|
- # Get token-level stats
|
|
|
|
- token_stats = self.db._fetch_query("SELECT * FROM token_stats ORDER BY total_realized_pnl DESC")
|
|
|
|
-
|
|
|
|
- # Calculate overall aggregated metrics
|
|
|
|
- total_realized_pnl = sum(t.get('total_realized_pnl', 0) for t in token_stats)
|
|
|
|
- total_completed_cycles = sum(t.get('total_completed_cycles', 0) for t in token_stats)
|
|
|
|
- total_winning_cycles = sum(t.get('winning_cycles', 0) for t in token_stats)
|
|
|
|
- total_losing_cycles = sum(t.get('losing_cycles', 0) for t in token_stats)
|
|
|
|
- total_cancelled_cycles = sum(t.get('total_cancelled_cycles', 0) for t in token_stats)
|
|
|
|
- total_entry_volume = sum(t.get('total_entry_volume', 0) for t in token_stats)
|
|
|
|
- total_exit_volume = sum(t.get('total_exit_volume', 0) for t in token_stats)
|
|
|
|
- sum_of_winning_pnl = sum(t.get('sum_of_winning_pnl', 0) for t in token_stats)
|
|
|
|
- sum_of_losing_pnl = sum(t.get('sum_of_losing_pnl', 0) for t in token_stats)
|
|
|
|
- total_duration_seconds = sum(t.get('total_duration_seconds', 0) for t in token_stats)
|
|
|
|
-
|
|
|
|
- # Calculate derived metrics
|
|
|
|
- win_rate = (total_winning_cycles / total_completed_cycles * 100) if total_completed_cycles > 0 else 0
|
|
|
|
- average_win_amount = sum_of_winning_pnl / total_winning_cycles if total_winning_cycles > 0 else 0
|
|
|
|
- average_loss_amount = sum_of_losing_pnl / total_losing_cycles if total_losing_cycles > 0 else 0
|
|
|
|
- profit_factor = sum_of_winning_pnl / sum_of_losing_pnl if sum_of_losing_pnl > 0 else float('inf') if sum_of_winning_pnl > 0 else 0
|
|
|
|
- expectancy = (total_realized_pnl / total_completed_cycles) if total_completed_cycles > 0 else 0
|
|
|
|
- largest_winning_cycle = max((t.get('largest_winning_cycle_pnl', 0) for t in token_stats), default=0)
|
|
|
|
- largest_losing_cycle = max((t.get('largest_losing_cycle_pnl', 0) for t in token_stats), default=0)
|
|
|
|
-
|
|
|
|
- # Get entry volume for largest winning/losing trades to calculate percentages
|
|
|
|
- largest_winning_entry_volume = 0.0
|
|
|
|
- largest_losing_entry_volume = 0.0
|
|
|
|
- largest_winning_token = "N/A"
|
|
|
|
- largest_losing_token = "N/A"
|
|
|
|
-
|
|
|
|
- # Track largest trades by ROE for alternative rankings
|
|
|
|
- largest_winning_roe = 0.0
|
|
|
|
- largest_losing_roe = 0.0
|
|
|
|
- largest_winning_roe_token = "N/A"
|
|
|
|
- largest_losing_roe_token = "N/A"
|
|
|
|
- largest_winning_roe_pnl = 0.0
|
|
|
|
- largest_losing_roe_pnl = 0.0
|
|
|
|
-
|
|
|
|
- for token in token_stats:
|
|
|
|
- # Track largest absolute dollar amounts
|
|
|
|
- if token.get('largest_winning_cycle_pnl', 0) == largest_winning_cycle and largest_winning_cycle > 0:
|
|
|
|
- largest_winning_entry_volume = token.get('largest_winning_cycle_entry_volume', 0)
|
|
|
|
- largest_winning_token = token['token']
|
|
|
|
-
|
|
|
|
- if token.get('largest_losing_cycle_pnl', 0) == largest_losing_cycle and largest_losing_cycle > 0:
|
|
|
|
- largest_losing_entry_volume = token.get('largest_losing_cycle_entry_volume', 0)
|
|
|
|
- largest_losing_token = token['token']
|
|
|
|
-
|
|
|
|
- # Track largest ROE-based trades
|
|
|
|
- winning_pnl = token.get('largest_winning_cycle_pnl', 0)
|
|
|
|
- winning_entry_volume = token.get('largest_winning_cycle_entry_volume', 0)
|
|
|
|
- if winning_pnl > 0 and winning_entry_volume > 0:
|
|
|
|
- winning_roe = (winning_pnl / winning_entry_volume) * 100
|
|
|
|
- if winning_roe > largest_winning_roe:
|
|
|
|
- largest_winning_roe = winning_roe
|
|
|
|
- largest_winning_roe_token = token['token']
|
|
|
|
- largest_winning_roe_pnl = winning_pnl
|
|
|
|
-
|
|
|
|
- losing_pnl = token.get('largest_losing_cycle_pnl', 0)
|
|
|
|
- losing_entry_volume = token.get('largest_losing_cycle_entry_volume', 0)
|
|
|
|
- if losing_pnl > 0 and losing_entry_volume > 0:
|
|
|
|
- losing_roe = (losing_pnl / losing_entry_volume) * 100
|
|
|
|
- if losing_roe > largest_losing_roe:
|
|
|
|
- largest_losing_roe = losing_roe
|
|
|
|
- largest_losing_roe_token = token['token']
|
|
|
|
- largest_losing_roe_pnl = losing_pnl
|
|
|
|
-
|
|
|
|
- # Calculate percentages for largest trades
|
|
|
|
- largest_winning_percentage = (largest_winning_cycle / largest_winning_entry_volume * 100) if largest_winning_entry_volume > 0 else 0
|
|
|
|
- largest_losing_percentage = (largest_losing_cycle / largest_losing_entry_volume * 100) if largest_losing_entry_volume > 0 else 0
|
|
|
|
-
|
|
|
|
- # Average trade duration
|
|
|
|
- average_trade_duration_seconds = total_duration_seconds / total_completed_cycles if total_completed_cycles > 0 else 0
|
|
|
|
- average_trade_duration_formatted = self._format_duration(average_trade_duration_seconds)
|
|
|
|
-
|
|
|
|
- # ROI calculation
|
|
|
|
- initial_balance = float(self.db._get_metadata('initial_balance') or '0.0')
|
|
|
|
- roi_percentage = (total_realized_pnl / initial_balance * 100) if initial_balance > 0 else 0
|
|
|
|
-
|
|
|
|
- # Calculate best and worst performing tokens based on total P&L
|
|
|
|
- best_token_name = "N/A"
|
|
|
|
- best_token_pnl_pct = 0.0
|
|
|
|
- best_token_volume = 0.0
|
|
|
|
- best_token_pnl_value = 0.0
|
|
|
|
- worst_token_name = "N/A"
|
|
|
|
- worst_token_pnl_pct = 0.0
|
|
|
|
- worst_token_volume = 0.0
|
|
|
|
- worst_token_pnl_value = 0.0
|
|
|
|
-
|
|
|
|
- for token in token_stats:
|
|
|
|
- if token.get('total_completed_cycles', 0) > 0:
|
|
|
|
- total_pnl = token.get('total_realized_pnl', 0)
|
|
|
|
- entry_volume = token.get('total_entry_volume', 0)
|
|
|
|
-
|
|
|
|
- if entry_volume > 0:
|
|
|
|
- pnl_pct = (total_pnl / entry_volume) * 100
|
|
|
|
|
|
+ """Get performance stats."""
|
|
|
|
+ try:
|
|
|
|
+ # Get all token stats
|
|
|
|
+ token_stats = self.db._fetch_query(
|
|
|
|
+ "SELECT * FROM token_stats",
|
|
|
|
+ ()
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ # Get open positions
|
|
|
|
+ open_positions = self.db._fetch_query(
|
|
|
|
+ "SELECT * FROM trades WHERE status = 'position_opened'",
|
|
|
|
+ ()
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ # Initialize performance metrics
|
|
|
|
+ total_trades = 0
|
|
|
|
+ total_wins = 0
|
|
|
|
+ total_losses = 0
|
|
|
|
+ total_pnl = 0.0
|
|
|
|
+ total_entry_volume = 0.0
|
|
|
|
+ total_exit_volume = 0.0
|
|
|
|
+ largest_win = 0.0
|
|
|
|
+ largest_loss = 0.0
|
|
|
|
+ largest_win_token = "N/A"
|
|
|
|
+ largest_loss_token = "N/A"
|
|
|
|
+ largest_win_pct = 0.0
|
|
|
|
+ largest_loss_pct = 0.0
|
|
|
|
+ best_token_name = "N/A"
|
|
|
|
+ best_token_pnl_value = 0.0
|
|
|
|
+ best_token_pnl_pct = 0.0
|
|
|
|
+ best_token_volume = 0.0
|
|
|
|
+ worst_token_name = "N/A"
|
|
|
|
+ worst_token_pnl_value = 0.0
|
|
|
|
+ worst_token_pnl_pct = 0.0
|
|
|
|
+ worst_token_volume = 0.0
|
|
|
|
+
|
|
|
|
+ # Process token stats
|
|
|
|
+ for token in token_stats:
|
|
|
|
+ if token.get('total_completed_cycles', 0) > 0:
|
|
|
|
+ total_trades += token.get('total_completed_cycles', 0)
|
|
|
|
+ total_wins += token.get('winning_cycles', 0)
|
|
|
|
+ total_losses += token.get('losing_cycles', 0)
|
|
|
|
+ total_pnl += token.get('total_realized_pnl', 0)
|
|
|
|
+ total_entry_volume += token.get('total_entry_volume', 0)
|
|
|
|
+ total_exit_volume += token.get('total_exit_volume', 0)
|
|
|
|
|
|
- # Best token = highest total P&L
|
|
|
|
- if best_token_name == "N/A" or total_pnl > best_token_pnl_value:
|
|
|
|
- best_token_name = token['token']
|
|
|
|
- best_token_pnl_pct = pnl_pct
|
|
|
|
- best_token_volume = entry_volume
|
|
|
|
- best_token_pnl_value = total_pnl
|
|
|
|
|
|
+ # Track largest trades
|
|
|
|
+ token_largest_win = token.get('largest_winning_cycle_pnl', 0)
|
|
|
|
+ token_largest_loss = token.get('largest_losing_cycle_pnl', 0)
|
|
|
|
|
|
- # Worst token = lowest total P&L
|
|
|
|
- if worst_token_name == "N/A" or total_pnl < worst_token_pnl_value:
|
|
|
|
- worst_token_name = token['token']
|
|
|
|
- worst_token_pnl_pct = pnl_pct
|
|
|
|
- worst_token_volume = entry_volume
|
|
|
|
- worst_token_pnl_value = total_pnl
|
|
|
|
-
|
|
|
|
- return {
|
|
|
|
- 'total_realized_pnl': total_realized_pnl,
|
|
|
|
- 'total_completed_cycles': total_completed_cycles,
|
|
|
|
- 'total_winning_cycles': total_winning_cycles,
|
|
|
|
- 'total_losing_cycles': total_losing_cycles,
|
|
|
|
- 'total_cancelled_cycles': total_cancelled_cycles,
|
|
|
|
- 'win_rate': win_rate,
|
|
|
|
- 'total_entry_volume': total_entry_volume,
|
|
|
|
- 'total_exit_volume': total_exit_volume,
|
|
|
|
- 'total_trading_volume': total_entry_volume, # Alias for compatibility
|
|
|
|
- 'sum_of_winning_pnl': sum_of_winning_pnl,
|
|
|
|
- 'sum_of_losing_pnl': sum_of_losing_pnl,
|
|
|
|
- 'average_win_amount': average_win_amount,
|
|
|
|
- 'average_loss_amount': average_loss_amount,
|
|
|
|
- 'avg_win': average_win_amount, # Alias for compatibility
|
|
|
|
- 'avg_loss': average_loss_amount, # Alias for compatibility
|
|
|
|
- 'profit_factor': profit_factor,
|
|
|
|
- 'expectancy': expectancy,
|
|
|
|
- 'largest_winning_cycle': largest_winning_cycle,
|
|
|
|
- 'largest_losing_cycle': largest_losing_cycle,
|
|
|
|
- 'largest_win': largest_winning_cycle, # Alias for compatibility
|
|
|
|
- 'largest_loss': largest_losing_cycle, # Alias for compatibility
|
|
|
|
- 'largest_winning_percentage': largest_winning_percentage,
|
|
|
|
- 'largest_losing_percentage': largest_losing_percentage,
|
|
|
|
- 'total_wins': total_winning_cycles, # Alias for compatibility
|
|
|
|
- 'total_losses': total_losing_cycles, # Alias for compatibility
|
|
|
|
- 'roi_percentage': roi_percentage,
|
|
|
|
- 'initial_balance': initial_balance,
|
|
|
|
- 'current_balance': initial_balance + total_realized_pnl,
|
|
|
|
- 'total_duration_seconds': total_duration_seconds,
|
|
|
|
- 'average_trade_duration_seconds': average_trade_duration_seconds,
|
|
|
|
- 'average_trade_duration_formatted': average_trade_duration_formatted,
|
|
|
|
- 'avg_trade_duration': average_trade_duration_formatted, # Alias for compatibility
|
|
|
|
- 'best_performing_token': {
|
|
|
|
- 'name': best_token_name,
|
|
|
|
- 'pnl_percentage': best_token_pnl_pct,
|
|
|
|
- 'volume': best_token_volume,
|
|
|
|
- 'pnl_value': best_token_pnl_value
|
|
|
|
- },
|
|
|
|
- 'worst_performing_token': {
|
|
|
|
- 'name': worst_token_name,
|
|
|
|
- 'pnl_percentage': worst_token_pnl_pct,
|
|
|
|
- 'volume': worst_token_volume,
|
|
|
|
- 'pnl_value': worst_token_pnl_value
|
|
|
|
- },
|
|
|
|
- 'largest_winning_token': largest_winning_token,
|
|
|
|
- 'largest_losing_token': largest_losing_token,
|
|
|
|
- 'largest_winning_roe': largest_winning_roe,
|
|
|
|
- 'largest_losing_roe': largest_losing_roe,
|
|
|
|
- 'largest_winning_roe_token': largest_winning_roe_token,
|
|
|
|
- 'largest_losing_roe_token': largest_losing_roe_token,
|
|
|
|
- 'largest_winning_roe_pnl': largest_winning_roe_pnl,
|
|
|
|
- 'largest_losing_roe_pnl': largest_losing_roe_pnl
|
|
|
|
- }
|
|
|
|
|
|
+ if token_largest_win > largest_win:
|
|
|
|
+ largest_win = token_largest_win
|
|
|
|
+ largest_win_token = token['token']
|
|
|
|
+ largest_win_pct = (token_largest_win / token.get('largest_winning_cycle_entry_volume', 1)) * 100
|
|
|
|
+
|
|
|
|
+ if token_largest_loss < largest_loss:
|
|
|
|
+ largest_loss = token_largest_loss
|
|
|
|
+ largest_loss_token = token['token']
|
|
|
|
+ largest_loss_pct = (token_largest_loss / token.get('largest_losing_cycle_entry_volume', 1)) * 100
|
|
|
|
+
|
|
|
|
+ # Track best/worst tokens
|
|
|
|
+ token_pnl = token.get('total_realized_pnl', 0)
|
|
|
|
+ token_volume = token.get('total_entry_volume', 0)
|
|
|
|
+
|
|
|
|
+ if token_volume > 0:
|
|
|
|
+ token_pnl_pct = (token_pnl / token_volume) * 100
|
|
|
|
+
|
|
|
|
+ if best_token_name == "N/A" or token_pnl > best_token_pnl_value:
|
|
|
|
+ best_token_name = token['token']
|
|
|
|
+ best_token_pnl_value = token_pnl
|
|
|
|
+ best_token_pnl_pct = token_pnl_pct
|
|
|
|
+ best_token_volume = token_volume
|
|
|
|
+
|
|
|
|
+ if worst_token_name == "N/A" or token_pnl < worst_token_pnl_value:
|
|
|
|
+ worst_token_name = token['token']
|
|
|
|
+ worst_token_pnl_value = token_pnl
|
|
|
|
+ worst_token_pnl_pct = token_pnl_pct
|
|
|
|
+ worst_token_volume = token_volume
|
|
|
|
+
|
|
|
|
+ # Calculate win rate and profit factor
|
|
|
|
+ win_rate = (total_wins / total_trades * 100) if total_trades > 0 else 0
|
|
|
|
+ profit_factor = (total_pnl / abs(total_losses)) if total_losses < 0 else float('inf') if total_pnl > 0 else 0
|
|
|
|
+
|
|
|
|
+ # Calculate expectancy
|
|
|
|
+ avg_win = total_pnl / total_wins if total_wins > 0 else 0
|
|
|
|
+ avg_loss = total_losses / total_losses if total_losses > 0 else 0
|
|
|
|
+ expectancy = (avg_win * (win_rate/100)) - (abs(avg_loss) * (1 - win_rate/100))
|
|
|
|
+
|
|
|
|
+ # Get max drawdown
|
|
|
|
+ max_drawdown, max_drawdown_pct = self.get_live_max_drawdown()
|
|
|
|
+
|
|
|
|
+ return {
|
|
|
|
+ 'total_trades': total_trades,
|
|
|
|
+ 'total_wins': total_wins,
|
|
|
|
+ 'total_losses': total_losses,
|
|
|
|
+ 'win_rate': win_rate,
|
|
|
|
+ 'total_pnl': total_pnl,
|
|
|
|
+ 'total_entry_volume': total_entry_volume,
|
|
|
|
+ 'total_exit_volume': total_exit_volume,
|
|
|
|
+ 'profit_factor': profit_factor,
|
|
|
|
+ 'expectancy': expectancy,
|
|
|
|
+ 'largest_win': largest_win,
|
|
|
|
+ 'largest_loss': largest_loss,
|
|
|
|
+ 'largest_win_token': largest_win_token,
|
|
|
|
+ 'largest_loss_token': largest_loss_token,
|
|
|
|
+ 'largest_win_pct': largest_win_pct,
|
|
|
|
+ 'largest_loss_pct': largest_loss_pct,
|
|
|
|
+ 'best_token': best_token_name,
|
|
|
|
+ 'best_token_pnl': best_token_pnl_value,
|
|
|
|
+ 'best_token_pct': best_token_pnl_pct,
|
|
|
|
+ 'best_token_volume': best_token_volume,
|
|
|
|
+ 'worst_token': worst_token_name,
|
|
|
|
+ 'worst_token_pnl': worst_token_pnl_value,
|
|
|
|
+ 'worst_token_pct': worst_token_pnl_pct,
|
|
|
|
+ 'worst_token_volume': worst_token_volume,
|
|
|
|
+ 'max_drawdown': max_drawdown,
|
|
|
|
+ 'max_drawdown_pct': max_drawdown_pct,
|
|
|
|
+ 'open_positions': len(open_positions)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ except Exception as e:
|
|
|
|
+ logger.error(f"Error getting performance stats: {e}")
|
|
|
|
+ return {}
|
|
|
|
|
|
def get_token_performance(self, limit: int = 20) -> List[Dict[str, Any]]:
|
|
def get_token_performance(self, limit: int = 20) -> List[Dict[str, Any]]:
|
|
"""Get performance stats by token, sorted by total P&L (dollar amount)."""
|
|
"""Get performance stats by token, sorted by total P&L (dollar amount)."""
|