浏览代码

Enhance InfoCommands to improve balance and performance reporting

- Refactored balance reporting to provide detailed breakdowns of USDC and other assets, including total, available, and in-use amounts.
- Updated performance metrics to display initial balance and overall P&L with improved formatting.
- Added system status information to the balance report, indicating trading engine activity and last update time.
- Introduced inline keyboard for quick actions related to trading commands, enhancing user interaction.
Carles Sentis 3 月之前
父节点
当前提交
d7dcc87e53
共有 1 个文件被更改,包括 69 次插入84 次删除
  1. 69 84
      src/commands/info_commands.py

+ 69 - 84
src/commands/info_commands.py

@@ -6,7 +6,7 @@ Info Commands - Handles information-related Telegram commands.
 import logging
 import logging
 from datetime import datetime, timezone, timedelta
 from datetime import datetime, timezone, timedelta
 from typing import Optional, Dict, Any, List
 from typing import Optional, Dict, Any, List
-from telegram import Update
+from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
 from telegram.ext import ContextTypes
 from telegram.ext import ContextTypes
 
 
 from src.config.config import Config
 from src.config.config import Config
@@ -69,95 +69,75 @@ class InfoCommands:
         try:
         try:
             balance = self.trading_engine.get_balance()
             balance = self.trading_engine.get_balance()
             if balance:
             if balance:
-                balance_text = "💰 <b>Account Balance</b>\n\n"
-                
-                # Debug: Show raw balance structure (can be removed after debugging)
-                logger.debug(f"Raw balance data: {balance}")
-                
-                # CCXT balance structure includes 'free', 'used', and 'total'
-                total_balance = balance.get('total', {})
-                free_balance = balance.get('free', {})
-                used_balance = balance.get('used', {})
-                
-                # Get total portfolio value
-                total_portfolio_value = 0
-                
-                # Show USDC balance prominently
-                if 'USDC' in total_balance:
-                    usdc_total = float(total_balance['USDC'])
-                    usdc_free = float(free_balance.get('USDC', 0))
-                    usdc_used = float(used_balance.get('USDC', 0))
-                    
-                    balance_text += f"💵 <b>USDC:</b>\n"
-                    balance_text += f"   📊 Total: ${usdc_total:,.2f}\n"
-                    balance_text += f"   ✅ Available: ${usdc_free:,.2f}\n"
-                    balance_text += f"   🔒 In Use: ${usdc_used:,.2f}\n\n"
-                    
-                    total_portfolio_value += usdc_total
-                
-                # Show other non-zero balances
-                other_assets = []
-                for asset, amount in total_balance.items():
-                    if asset != 'USDC' and float(amount) > 0:
-                        other_assets.append((asset, float(amount)))
-                
-                if other_assets:
-                    balance_text += "📊 <b>Other Assets:</b>\n"
-                    for asset, amount in other_assets:
-                        free_amount = float(free_balance.get(asset, 0))
-                        used_amount = float(used_balance.get(asset, 0))
-                        
-                        balance_text += f"💵 <b>{asset}:</b>\n"
-                        balance_text += f"   📊 Total: {amount:.6f}\n"
-                        balance_text += f"   ✅ Available: {free_amount:.6f}\n"
-                        balance_text += f"   🔒 In Use: {used_amount:.6f}\n\n"
+                usdc_total = 0.0
+                usdc_free = 0.0
+                usdc_used = 0.0
+
+                if 'USDC' in balance.get('total', {}):
+                    usdc_total = float(balance['total']['USDC'])
+                    usdc_free = float(balance.get('free', {}).get('USDC', 0))
+                    usdc_used = float(balance.get('used', {}).get('USDC', 0))
+
+                balance_text_parts = [
+                    f"💰 <b>Account Balance</b>\n",
+                    f"   💵 Total USDC: ${usdc_total:,.2f}",
+                    f"   ✅ Available USDC: ${usdc_free:,.2f}",
+                    f"   🔒 USDC In Use: ${usdc_used:,.2f}"
+                ]
+
+                other_assets_text = []
+                for asset, amount_val in balance.get('total', {}).items():
+                    if asset != 'USDC' and float(amount_val) > 0:
+                        free_amount = float(balance.get('free', {}).get(asset, 0))
+                        other_assets_text.append(f"   🪙 {asset}: {float(amount_val):.6f} (Free: {free_amount:.6f})")
                 
                 
-                # Portfolio summary
-                usdc_balance = float(total_balance.get('USDC', 0))
+                if other_assets_text:
+                    balance_text_parts.append("\n📊 <b>Other Assets:</b>")
+                    balance_text_parts.extend(other_assets_text)
+
+                # Performance Metrics
                 stats = self.trading_engine.get_stats()
                 stats = self.trading_engine.get_stats()
+                initial_balance = 0.0
+                pnl = 0.0
+                pnl_percent = 0.0
+                pnl_emoji = "⚪"
+
                 if stats:
                 if stats:
                     basic_stats = stats.get_basic_stats()
                     basic_stats = stats.get_basic_stats()
-                    initial_balance = basic_stats.get('initial_balance', usdc_balance)
-                    pnl = usdc_balance - initial_balance
+                    initial_balance = basic_stats.get('initial_balance', usdc_total) # Fallback to current total if no initial
+                    if initial_balance is None: # Should not happen if basic_stats is fetched
+                        initial_balance = usdc_total
+                    
+                    pnl = usdc_total - initial_balance
                     pnl_percent = (pnl / initial_balance * 100) if initial_balance > 0 else 0
                     pnl_percent = (pnl / initial_balance * 100) if initial_balance > 0 else 0
                     pnl_emoji = "🟢" if pnl >= 0 else "🔴"
                     pnl_emoji = "🟢" if pnl >= 0 else "🔴"
-                    
-                    balance_text += f"💼 <b>Portfolio Summary:</b>\n"
-                    balance_text += f"   💰 Total Value: ${total_portfolio_value:,.2f}\n"
-                    balance_text += f"   🚀 Available for Trading: ${float(free_balance.get('USDC', 0)):,.2f}\n"
-                    balance_text += f"   🔒 In Active Use: ${float(used_balance.get('USDC', 0)):,.2f}\n\n"
-                    balance_text += f"📊 <b>Performance:</b>\n"
-                    balance_text += f"   💵 Initial: ${initial_balance:,.2f}\n"
-                    balance_text += f"   {pnl_emoji} P&L: ${pnl:,.2f} ({pnl_percent:+.2f}%)\n"
                 
                 
+                balance_text_parts.append("\n📈 <b>Performance:</b>")
+                balance_text_parts.append(f"   💵 Initial Balance: ${initial_balance:,.2f}")
+                balance_text_parts.append(f"   {pnl_emoji} Overall P&L: ${pnl:,.2f} ({pnl_percent:+.2f}%)")
+
+                # System Status
                 trading_engine_active = "✅ Active" if self.trading_engine else "❌ Inactive (Error)"
                 trading_engine_active = "✅ Active" if self.trading_engine else "❌ Inactive (Error)"
+                balance_text_parts.append("\n⚙️ <b>System Status:</b>")
+                balance_text_parts.append(f"• Trading Engine: {trading_engine_active}")
+                balance_text_parts.append(f"• Data Source: Exchange (Live)") # Balance is usually live
+                balance_text_parts.append(f"• Last Update: {datetime.now().strftime('%H:%M:%S')}")
                 
                 
-                # Construct the balance message
-                balance_text = f"""
-💰 <b>Account Balance & Info</b>
-
-💰 <b>Account Balance:</b>
-   💵 Total: ${usdc_total:,.2f}
-   ✅ Available: ${usdc_free:,.2f}
-   🔒 In Use: ${usdc_used:,.2f}
-
-📊 <b>Portfolio Summary:</b>
-   💰 Total Value: ${total_portfolio_value:,.2f}
-   🚀 Available for Trading: ${float(free_balance.get('USDC', 0)):,.2f}
-   🔒 In Active Use: ${float(used_balance.get('USDC', 0)):,.2f}
-
-⚙️ <b>System Status:</b>
-• Trading Engine: {trading_engine_active}
-• Data Source: Cached (updated on heartbeat)
-
-⏰ Last Update: {datetime.now().strftime('%H:%M:%S')}
-                """
+                final_message = "\n".join(balance_text_parts)
+                
+                # Quick Actions Keyboard (same as before)
+                quick_actions = [
+                    [InlineKeyboardButton("Positions (/p)", callback_data="/positions"), InlineKeyboardButton("Orders (/o)", callback_data="/orders")],
+                    [InlineKeyboardButton("Trades (/tr)", callback_data="/trades"), InlineKeyboardButton("Stats (/st)", callback_data="/stats")],
+                    [InlineKeyboardButton("Performance (/pf)", callback_data="/performance"), InlineKeyboardButton("Commands (/c)", callback_data="/commands")]
+                ]
+                quick_action_markup = InlineKeyboardMarkup(quick_actions)
 
 
-                await reply_method(text=balance_text.strip(), parse_mode='HTML')
+                await reply_method(text=final_message.strip(), parse_mode='HTML', reply_markup=quick_action_markup)
             else:
             else:
                 await reply_method(text="❌ Could not fetch balance information", parse_mode='HTML')
                 await reply_method(text="❌ Could not fetch balance information", parse_mode='HTML')
         except Exception as e:
         except Exception as e:
-            logger.error(f"Error in balance command: {e}")
+            logger.error(f"Error in balance command: {e}", exc_info=True)
             await reply_method(text="❌ Error retrieving balance information.", parse_mode='HTML')
             await reply_method(text="❌ Error retrieving balance information.", parse_mode='HTML')
     
     
     async def positions_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def positions_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
@@ -233,8 +213,11 @@ class InfoCommands:
                     # else pnl_percentage remains 0.0
                     # else pnl_percentage remains 0.0
 
 
                     # Add to totals
                     # Add to totals
-                    current_pos_value_at_mark = abs_current_amount * mark_price
-                    total_position_value += current_pos_value_at_mark
+                    individual_position_value = position_trade.get('position_value')
+                    if individual_position_value is None: # Fallback if not in DB
+                        individual_position_value = abs_current_amount * mark_price
+                    
+                    total_position_value += individual_position_value
                     total_unrealized += unrealized_pnl
                     total_unrealized += unrealized_pnl
                     
                     
                     # --- Position Header Formatting (Emoji, Direction, Leverage) ---
                     # --- Position Header Formatting (Emoji, Direction, Leverage) ---
@@ -282,6 +265,9 @@ class InfoCommands:
                     positions_text += f"   📏 Size: {size_str} {base_asset}\n" # Use the formatted size_str
                     positions_text += f"   📏 Size: {size_str} {base_asset}\n" # Use the formatted size_str
                     positions_text += f"   💰 Entry: {entry_price_str}\n"
                     positions_text += f"   💰 Entry: {entry_price_str}\n"
                     
                     
+                    # Display individual position value
+                    positions_text += f"   🏦 Value: ${individual_position_value:,.2f}\n"
+                    
                     if mark_price != 0 and abs(mark_price - entry_price) > 1e-9: # Only show mark if significantly different
                     if mark_price != 0 and abs(mark_price - entry_price) > 1e-9: # Only show mark if significantly different
                         positions_text += f"   📈 Mark: {mark_price_str}\n"
                         positions_text += f"   📈 Mark: {mark_price_str}\n"
                     
                     
@@ -312,8 +298,8 @@ class InfoCommands:
                 # Portfolio summary
                 # Portfolio summary
                 portfolio_emoji = "🟢" if total_unrealized >= 0 else "🔴"
                 portfolio_emoji = "🟢" if total_unrealized >= 0 else "🔴"
                 positions_text += f"💼 <b>Total Portfolio:</b>\n"
                 positions_text += f"💼 <b>Total Portfolio:</b>\n"
-                positions_text += f"   💵 Total Value: ${total_position_value:,.2f}\n"
-                positions_text += f"   {portfolio_emoji} Total P&L: ${total_unrealized:,.2f}\n\n"
+                positions_text += f"   🏦 Total Positions Value: ${total_position_value:,.2f}\n"
+                positions_text += f"   {portfolio_emoji} Total Unrealized P&L: ${total_unrealized:,.2f}\n\n"
                 positions_text += f"🤖 <b>Legend:</b> 🤖 Bot-created • 🔄 External/synced\n"
                 positions_text += f"🤖 <b>Legend:</b> 🤖 Bot-created • 🔄 External/synced\n"
                 positions_text += f"💡 Use /sl [token] [price] or /tp [token] [price] to set risk management"
                 positions_text += f"💡 Use /sl [token] [price] or /tp [token] [price] to set risk management"
                 
                 
@@ -624,7 +610,8 @@ class InfoCommands:
         
         
         if market_data:
         if market_data:
             ticker = market_data.get('ticker', {})
             ticker = market_data.get('ticker', {})
-            
+            logger.debug(f"Market command: Ticker data for {symbol}: {ticker}") # Log the ticker data
+
             current_price = float(ticker.get('last', 0.0) or 0.0)
             current_price = float(ticker.get('last', 0.0) or 0.0)
             bid_price = float(ticker.get('bid', 0.0) or 0.0)
             bid_price = float(ticker.get('bid', 0.0) or 0.0)
             ask_price = float(ticker.get('ask', 0.0) or 0.0)
             ask_price = float(ticker.get('ask', 0.0) or 0.0)
@@ -1316,8 +1303,6 @@ Tap any button below for instant access to bot functions:
 💡 <b>Pro Tip:</b> These buttons work the same as typing the commands manually, but faster!
 💡 <b>Pro Tip:</b> These buttons work the same as typing the commands manually, but faster!
         """
         """
         
         
-        from telegram import InlineKeyboardButton, InlineKeyboardMarkup
-        
         keyboard = [
         keyboard = [
             [
             [
                 InlineKeyboardButton("💰 Balance", callback_data="/balance"),
                 InlineKeyboardButton("💰 Balance", callback_data="/balance"),