Pārlūkot izejas kodu

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 2 nedēļas atpakaļ
vecāks
revīzija
d7dcc87e53
1 mainītis faili ar 69 papildinājumiem un 84 dzēšanām
  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"
+                usdc_total = 0.0
-                
+                usdc_free = 0.0
-                # Debug: Show raw balance structure (can be removed after debugging)
+                usdc_used = 0.0
-                logger.debug(f"Raw balance data: {balance}")
+
-                
+                if 'USDC' in balance.get('total', {}):
-                # CCXT balance structure includes 'free', 'used', and 'total'
+                    usdc_total = float(balance['total']['USDC'])
-                total_balance = balance.get('total', {})
+                    usdc_free = float(balance.get('free', {}).get('USDC', 0))
-                free_balance = balance.get('free', {})
+                    usdc_used = float(balance.get('used', {}).get('USDC', 0))
-                used_balance = balance.get('used', {})
+
-                
+                balance_text_parts = [
-                # Get total portfolio value
+                    f"💰 <b>Account Balance</b>\n",
-                total_portfolio_value = 0
+                    f"   💵 Total USDC: ${usdc_total:,.2f}",
-                
+                    f"   ✅ Available USDC: ${usdc_free:,.2f}",
-                # Show USDC balance prominently
+                    f"   🔒 USDC In Use: ${usdc_used:,.2f}"
-                if 'USDC' in total_balance:
+                ]
-                    usdc_total = float(total_balance['USDC'])
+
-                    usdc_free = float(free_balance.get('USDC', 0))
+                other_assets_text = []
-                    usdc_used = float(used_balance.get('USDC', 0))
+                for asset, amount_val in balance.get('total', {}).items():
-                    
+                    if asset != 'USDC' and float(amount_val) > 0:
-                    balance_text += f"💵 <b>USDC:</b>\n"
+                        free_amount = float(balance.get('free', {}).get(asset, 0))
-                    balance_text += f"   📊 Total: ${usdc_total:,.2f}\n"
+                        other_assets_text.append(f"   🪙 {asset}: {float(amount_val):.6f} (Free: {free_amount:.6f})")
-                    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"
                 
                 
-                # Portfolio summary
+                if other_assets_text:
-                usdc_balance = float(total_balance.get('USDC', 0))
+                    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)
+                    initial_balance = basic_stats.get('initial_balance', usdc_total) # Fallback to current total if no initial
-                    pnl = usdc_balance - initial_balance
+                    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
+                final_message = "\n".join(balance_text_parts)
-                balance_text = f"""
+                
-💰 <b>Account Balance & Info</b>
+                # Quick Actions Keyboard (same as before)
-
+                quick_actions = [
-💰 <b>Account Balance:</b>
+                    [InlineKeyboardButton("Positions (/p)", callback_data="/positions"), InlineKeyboardButton("Orders (/o)", callback_data="/orders")],
-   💵 Total: ${usdc_total:,.2f}
+                    [InlineKeyboardButton("Trades (/tr)", callback_data="/trades"), InlineKeyboardButton("Stats (/st)", callback_data="/stats")],
-   ✅ Available: ${usdc_free:,.2f}
+                    [InlineKeyboardButton("Performance (/pf)", callback_data="/performance"), InlineKeyboardButton("Commands (/c)", callback_data="/commands")]
-   🔒 In Use: ${usdc_used:,.2f}
+                ]
-
+                quick_action_markup = InlineKeyboardMarkup(quick_actions)
-📊 <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')}
-                """
 
 
-                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
+                    individual_position_value = position_trade.get('position_value')
-                    total_position_value += current_pos_value_at_mark
+                    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"   🏦 Total Positions Value: ${total_position_value:,.2f}\n"
-                positions_text += f"   {portfolio_emoji} Total P&L: ${total_unrealized:,.2f}\n\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"),