Sfoglia il codice sorgente

Refactor performance metrics in info_commands and performance_calculator to prioritize ROE.

- Updated token performance ranking to sort by Return on Equity (ROE) instead of total P&L, enhancing the focus on investment efficiency.
- Adjusted performance display in info_commands to emphasize ROE as the primary metric, providing clearer insights for users.
- Enhanced performance_calculator to calculate and return ROE for tokens, facilitating better performance analysis.
- Incorporated largest ROE-based trades into trading_stats for comprehensive performance reporting.
Carles Sentis 1 settimana fa
parent
commit
8085d7062c

+ 19 - 18
src/commands/info_commands.py

@@ -909,14 +909,11 @@ class InfoCommands:
             )
             return
         
-        # Sort tokens by total P&L (best to worst) - token_performance is already a list of dicts
-        sorted_tokens = sorted(
-            token_performance,
-            key=lambda x: x.get('total_realized_pnl', 0),
-            reverse=True
-        )
+        # Tokens are already sorted by ROE from the performance calculator
+        sorted_tokens = token_performance
         
-        performance_text = "🏆 <b>Token Performance Ranking</b>\n\n"
+        performance_text = "🏆 <b>Token Performance Ranking</b>\n"
+        performance_text += "<i>Ordered by ROE (Return on Equity)</i>\n\n"
         
         # Add ranking with emojis
         for i, stats_data in enumerate(sorted_tokens, 1):
@@ -930,20 +927,22 @@ class InfoCommands:
             else:
                 rank_emoji = f"#{i}"
             
-            # P&L emoji
+            # Get ROE and P&L data
+            roe_percentage = stats_data.get('roe_percentage', 0)
             total_pnl = stats_data.get('total_realized_pnl', 0)
-            pnl_emoji = "🟢" if total_pnl >= 0 else "🔴"
             
-            # Calculate PnL percentage from entry volume
-            entry_volume = stats_data.get('completed_entry_volume', 0)
-            pnl_percentage = (total_pnl / entry_volume * 100) if entry_volume > 0 else 0
+            # ROE emoji (primary metric)
+            roe_emoji = "🟢" if roe_percentage >= 0 else "🔴"
+            
+            # P&L emoji (secondary metric)
+            pnl_emoji = "🟢" if total_pnl >= 0 else "🔴"
             
             token_name = stats_data.get('token', 'N/A')
             completed_trades = stats_data.get('total_completed_cycles', 0)
             
-            # Format the line
+            # Format the line - emphasize ROE as primary metric
             performance_text += f"{rank_emoji} <b>{token_name}</b>\n"
-            performance_text += f"   {pnl_emoji} P&L: ${total_pnl:,.2f} ({pnl_percentage:+.1f}%)\n"
+            performance_text += f"   {roe_emoji} ROE: {roe_percentage:+.2f}% | {pnl_emoji} P&L: ${total_pnl:,.2f}\n"
             performance_text += f"   📊 Trades: {completed_trades}"
             
             # Add win rate if there are completed trades
@@ -1020,16 +1019,18 @@ class InfoCommands:
         avg_win_str = formatter.format_price_with_symbol(perf_summary.get('avg_win', 0))
         avg_loss_str = formatter.format_price_with_symbol(perf_summary.get('avg_loss', 0))
         
-        # Calculate PnL percentage
+        # Calculate ROE (Return on Equity)
         entry_vol = perf_summary.get('completed_entry_volume', 0)
-        pnl_pct = (perf_summary.get('total_pnl', 0) / entry_vol * 100) if entry_vol > 0 else 0
+        roe_pct = (perf_summary.get('total_pnl', 0) / entry_vol * 100) if entry_vol > 0 else 0
+        roe_emoji = "🟢" if roe_pct >= 0 else "🔴"
 
 
         performance_text = f"""
 📊 <b>{token} Detailed Performance</b>
 
-🎯 <b>P&L Summary:</b>
-• {pnl_emoji} Total P&L: {total_pnl_str} ({pnl_pct:+.2f}%)
+🎯 <b>Performance Summary:</b>
+• {roe_emoji} ROE (Return on Equity): {roe_pct:+.2f}%
+• {pnl_emoji} Total P&L: {total_pnl_str}
 • 💵 Total Volume: {completed_volume_str}
 • 📈 Expectancy: {expectancy_str}
 

+ 59 - 5
src/stats/performance_calculator.py

@@ -68,13 +68,45 @@ class PerformanceCalculator:
         # 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
@@ -162,16 +194,25 @@ class PerformanceCalculator:
                 '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
         }
 
     def get_token_performance(self, limit: int = 20) -> List[Dict[str, Any]]:
-        """Get performance stats by token, sorted by total PnL."""
+        """Get performance stats by token, sorted by ROE (Return on Equity)."""
         formatter = get_formatter()
         
+        # Get all token stats first, then sort by ROE in Python
         token_stats = self.db._fetch_query(
-            "SELECT * FROM token_stats ORDER BY total_realized_pnl DESC LIMIT ?", 
-            (limit,)
+            "SELECT * FROM token_stats", 
+            ()
         )
         
         for token in token_stats:
@@ -186,6 +227,11 @@ class PerformanceCalculator:
             sum_losing = token.get('sum_of_losing_pnl', 0)
             token['profit_factor'] = sum_winning / sum_losing if sum_losing > 0 else float('inf') if sum_winning > 0 else 0
             
+            # Calculate ROE (Return on Equity) - PnL percentage based on entry volume
+            total_pnl = token.get('total_realized_pnl', 0)
+            entry_volume = token.get('total_entry_volume', 0)
+            token['roe_percentage'] = (total_pnl / entry_volume * 100) if entry_volume > 0 else 0
+            
             # Format durations
             total_duration = token.get('total_duration_seconds', 0)
             avg_duration = total_duration / total_cycles if total_cycles > 0 else 0
@@ -194,7 +240,15 @@ class PerformanceCalculator:
             # Token display name (use token as-is)
             token['display_name'] = token['token'].upper()
         
-        return token_stats
+        # Sort by ROE percentage (highest to lowest), then by total PnL as tiebreaker
+        sorted_tokens = sorted(
+            token_stats,
+            key=lambda x: (x.get('roe_percentage', 0), x.get('total_realized_pnl', 0)),
+            reverse=True
+        )
+        
+        # Return top tokens (limit)
+        return sorted_tokens[:limit]
 
     def get_balance_history(self, days: int = 30) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]:
         """Get balance history for the last N days with detailed statistics."""

+ 20 - 2
src/stats/trading_stats.py

@@ -608,8 +608,26 @@ class TradingStats:
             largest_win_pct_str = f" ({perf.get('largest_winning_percentage', 0):+.2f}%)" if perf.get('largest_winning_percentage', 0) != 0 else ""
             largest_loss_pct_str = f" ({perf.get('largest_losing_percentage', 0):+.2f}%)" if perf.get('largest_losing_percentage', 0) != 0 else ""
             
-            stats_text_parts.append(f"• Largest Winning Trade: {formatter.format_price_with_symbol(perf['largest_win'])}{largest_win_pct_str}")
-            stats_text_parts.append(f"• Largest Losing Trade: {formatter.format_price_with_symbol(-perf['largest_loss'])}{largest_loss_pct_str}")
+            # Show token names for largest trades
+            largest_win_token = perf.get('largest_winning_token', 'N/A')
+            largest_loss_token = perf.get('largest_losing_token', 'N/A')
+            
+            stats_text_parts.append(f"• Largest Winning Trade: {formatter.format_price_with_symbol(perf['largest_win'])}{largest_win_pct_str} ({largest_win_token})")
+            stats_text_parts.append(f"• Largest Losing Trade: {formatter.format_price_with_symbol(-perf['largest_loss'])}{largest_loss_pct_str} ({largest_loss_token})")
+            
+            # Add ROE-based largest trades if different from dollar-based
+            largest_win_roe_token = perf.get('largest_winning_roe_token', 'N/A')
+            largest_loss_roe_token = perf.get('largest_losing_roe_token', 'N/A')
+            largest_win_roe = perf.get('largest_winning_roe', 0)
+            largest_loss_roe = perf.get('largest_losing_roe', 0)
+            
+            if largest_win_roe_token != largest_win_token and largest_win_roe > 0:
+                largest_win_roe_pnl = perf.get('largest_winning_roe_pnl', 0)
+                stats_text_parts.append(f"• Best ROE Trade: {formatter.format_price_with_symbol(largest_win_roe_pnl)} (+{largest_win_roe:.2f}%) ({largest_win_roe_token})")
+            
+            if largest_loss_roe_token != largest_loss_token and largest_loss_roe > 0:
+                largest_loss_roe_pnl = perf.get('largest_losing_roe_pnl', 0)
+                stats_text_parts.append(f"• Worst ROE Trade: {formatter.format_price_with_symbol(-largest_loss_roe_pnl)} (-{largest_loss_roe:.2f}%) ({largest_loss_roe_token})")
 
             best_token_stats = perf.get('best_performing_token', {'name': 'N/A', 'pnl_percentage': 0.0, 'volume': 0.0, 'pnl_value': 0.0})
             worst_token_stats = perf.get('worst_performing_token', {'name': 'N/A', 'pnl_percentage': 0.0, 'volume': 0.0, 'pnl_value': 0.0})

+ 1 - 1
trading_bot.py

@@ -14,7 +14,7 @@ from datetime import datetime
 from pathlib import Path
 
 # Bot version
-BOT_VERSION = "2.3.171"
+BOT_VERSION = "2.3.172"
 
 # Add src directory to Python path
 sys.path.insert(0, str(Path(__file__).parent / "src"))