瀏覽代碼

Enhance Telegram bot command handling and performance tracking - Added new commands for performance metrics, daily, weekly, and monthly statistics, along with risk analysis and balance adjustments. Improved position direction handling and updated command responses for better user experience. Introduced a custom keyboard feature for quick access to commands, enhancing overall bot functionality.

Carles Sentis 5 天之前
父節點
當前提交
2f07b122a3
共有 5 個文件被更改,包括 726 次插入13 次删除
  1. 9 0
      src/bot/core.py
  2. 635 6
      src/commands/info_commands.py
  3. 58 1
      src/commands/management_commands.py
  4. 5 0
      src/commands/trading_commands.py
  5. 19 6
      src/trading/trading_engine.py

+ 9 - 0
src/bot/core.py

@@ -82,6 +82,14 @@ class TelegramTradingBot:
         self.application.add_handler(CommandHandler("trades", self.info_commands.trades_command))
         self.application.add_handler(CommandHandler("market", self.info_commands.market_command))
         self.application.add_handler(CommandHandler("price", self.info_commands.price_command))
+        self.application.add_handler(CommandHandler("performance", self.info_commands.performance_command))
+        self.application.add_handler(CommandHandler("daily", self.info_commands.daily_command))
+        self.application.add_handler(CommandHandler("weekly", self.info_commands.weekly_command))
+        self.application.add_handler(CommandHandler("monthly", self.info_commands.monthly_command))
+        self.application.add_handler(CommandHandler("risk", self.info_commands.risk_command))
+        self.application.add_handler(CommandHandler("balance_adjustments", self.info_commands.balance_adjustments_command))
+        self.application.add_handler(CommandHandler("commands", self.info_commands.commands_command))
+        self.application.add_handler(CommandHandler("c", self.info_commands.commands_command))  # Alias
         
         # Management commands
         self.application.add_handler(CommandHandler("monitoring", self.management_commands.monitoring_command))
@@ -89,6 +97,7 @@ class TelegramTradingBot:
         self.application.add_handler(CommandHandler("logs", self.management_commands.logs_command))
         self.application.add_handler(CommandHandler("debug", self.management_commands.debug_command))
         self.application.add_handler(CommandHandler("version", self.management_commands.version_command))
+        self.application.add_handler(CommandHandler("keyboard", self.management_commands.keyboard_command))
         
         # Callback and message handlers
         self.application.add_handler(CallbackQueryHandler(self.trading_commands.button_callback))

+ 635 - 6
src/commands/info_commands.py

@@ -5,6 +5,7 @@ Info Commands - Handles information-related Telegram commands.
 
 import logging
 from datetime import datetime
+from typing import Optional, Dict, Any, List
 from telegram import Update
 from telegram.ext import ContextTypes
 
@@ -120,12 +121,22 @@ class InfoCommands:
                     # Use the new position direction logic
                     position_type, exit_side, contracts = self.trading_engine.get_position_direction(position)
                     
-                    entry_price = float(position.get('entryPx', 0))
-                    mark_price = float(position.get('markPx', entry_price))
+                    # Use correct CCXT field names
+                    entry_price = float(position.get('entryPrice', 0))
+                    mark_price = float(position.get('markPrice') or 0)
                     unrealized_pnl = float(position.get('unrealizedPnl', 0))
                     
+                    # If markPrice is not available, try to get current market price
+                    if mark_price == 0:
+                        try:
+                            market_data = self.trading_engine.get_market_data(position.get('symbol', ''))
+                            if market_data and market_data.get('ticker'):
+                                mark_price = float(market_data['ticker'].get('last', entry_price))
+                        except:
+                            mark_price = entry_price  # Fallback to entry price
+                    
                     # Calculate position value
-                    position_value = contracts * mark_price
+                    position_value = abs(contracts) * mark_price
                     total_position_value += position_value
                     total_unrealized += unrealized_pnl
                     
@@ -141,7 +152,7 @@ class InfoCommands:
                     pnl_percent = (unrealized_pnl / position_value * 100) if position_value > 0 else 0
                     
                     positions_text += f"{pos_emoji} <b>{symbol} ({direction})</b>\n"
-                    positions_text += f"   📏 Size: {contracts:.6f} {symbol}\n"
+                    positions_text += f"   📏 Size: {abs(contracts):.6f} {symbol}\n"
                     positions_text += f"   💰 Entry: ${entry_price:,.2f}\n"
                     positions_text += f"   📊 Mark: ${mark_price:,.2f}\n"
                     positions_text += f"   💵 Value: ${position_value:,.2f}\n"
@@ -197,7 +208,7 @@ class InfoCommands:
                         orders_text += f"   {side_emoji} {side} {amount:.6f} @ ${price:,.2f}\n"
                         orders_text += f"   📋 Type: {order_type} | ID: {order_id}\n\n"
                 
-                orders_text += f"📊 <b>Total Orders:</b> {len(orders)}\n"
+                orders_text += f"💼 <b>Total Orders:</b> {len(orders)}\n"
                 orders_text += f"💡 Use /coo [token] to cancel orders"
                 
             else:
@@ -344,4 +355,622 @@ class InfoCommands:
             
             await update.message.reply_text(price_text.strip(), parse_mode='HTML')
         else:
-            await update.message.reply_text(f"❌ Could not fetch price for {token}") 
+            await update.message.reply_text(f"❌ Could not fetch price for {token}")
+    
+    async def performance_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /performance command to show token performance ranking or detailed stats."""
+        if not self._is_authorized(update.effective_chat.id):
+            await update.message.reply_text("❌ Unauthorized access.")
+            return
+        
+        try:
+            # Check if specific token is requested
+            if context.args and len(context.args) >= 1:
+                # Detailed performance for specific token
+                token = context.args[0].upper()
+                await self._show_token_performance(update, token)
+            else:
+                # Show token performance ranking
+                await self._show_performance_ranking(update)
+                
+        except Exception as e:
+            error_message = f"❌ Error processing performance command: {str(e)}"
+            await update.message.reply_text(error_message)
+            logger.error(f"Error in performance command: {e}")
+
+    async def _show_performance_ranking(self, update: Update):
+        """Show token performance ranking (compressed view)."""
+        stats = self.trading_engine.get_stats()
+        if not stats:
+            await update.message.reply_text("❌ Could not load trading statistics")
+            return
+            
+        token_performance = stats.get_token_performance()
+        
+        if not token_performance:
+            await update.message.reply_text(
+                "📊 <b>Token Performance</b>\n\n"
+                "📭 No trading data available yet.\n\n"
+                "💡 Performance tracking starts after your first completed trades.\n"
+                "Use /long or /short to start trading!",
+                parse_mode='HTML'
+            )
+            return
+        
+        # Sort tokens by total P&L (best to worst)
+        sorted_tokens = sorted(
+            token_performance.items(),
+            key=lambda x: x[1]['total_pnl'],
+            reverse=True
+        )
+        
+        performance_text = "🏆 <b>Token Performance Ranking</b>\n\n"
+        
+        # Add ranking with emojis
+        for i, (token, stats_data) in enumerate(sorted_tokens, 1):
+            # Ranking emoji
+            if i == 1:
+                rank_emoji = "🥇"
+            elif i == 2:
+                rank_emoji = "🥈"
+            elif i == 3:
+                rank_emoji = "🥉"
+            else:
+                rank_emoji = f"#{i}"
+            
+            # P&L emoji
+            pnl_emoji = "🟢" if stats_data['total_pnl'] >= 0 else "🔴"
+            
+            # Format the line
+            performance_text += f"{rank_emoji} <b>{token}</b>\n"
+            performance_text += f"   {pnl_emoji} P&L: ${stats_data['total_pnl']:,.2f} ({stats_data['pnl_percentage']:+.1f}%)\n"
+            performance_text += f"   📊 Trades: {stats_data['completed_trades']}"
+            
+            # Add win rate if there are completed trades
+            if stats_data['completed_trades'] > 0:
+                performance_text += f" | Win: {stats_data['win_rate']:.0f}%"
+            
+            performance_text += "\n\n"
+        
+        # Add summary
+        total_pnl = sum(stats_data['total_pnl'] for stats_data in token_performance.values())
+        total_trades = sum(stats_data['completed_trades'] for stats_data in token_performance.values())
+        total_pnl_emoji = "🟢" if total_pnl >= 0 else "🔴"
+        
+        performance_text += f"💼 <b>Portfolio Summary:</b>\n"
+        performance_text += f"   {total_pnl_emoji} Total P&L: ${total_pnl:,.2f}\n"
+        performance_text += f"   📈 Tokens Traded: {len(token_performance)}\n"
+        performance_text += f"   🔄 Completed Trades: {total_trades}\n\n"
+        
+        performance_text += f"💡 <b>Usage:</b> <code>/performance BTC</code> for detailed {Config.DEFAULT_TRADING_TOKEN} stats"
+        
+        await update.message.reply_text(performance_text.strip(), parse_mode='HTML')
+
+    async def _show_token_performance(self, update: Update, token: str):
+        """Show detailed performance for a specific token."""
+        stats = self.trading_engine.get_stats()
+        if not stats:
+            await update.message.reply_text("❌ Could not load trading statistics")
+            return
+            
+        token_stats = stats.get_token_detailed_stats(token)
+        
+        # Check if token has any data
+        if token_stats.get('total_trades', 0) == 0:
+            await update.message.reply_text(
+                f"📊 <b>{token} Performance</b>\n\n"
+                f"📭 No trading history found for {token}.\n\n"
+                f"💡 Start trading {token} with:\n"
+                f"• <code>/long {token} 100</code>\n"
+                f"• <code>/short {token} 100</code>\n\n"
+                f"🔄 Use <code>/performance</code> to see all token rankings.",
+                parse_mode='HTML'
+            )
+            return
+        
+        # Check if there's a message (no completed trades)
+        if 'message' in token_stats and token_stats.get('completed_trades', 0) == 0:
+            await update.message.reply_text(
+                f"📊 <b>{token} Performance</b>\n\n"
+                f"{token_stats['message']}\n\n"
+                f"📈 <b>Current Activity:</b>\n"
+                f"• Total Trades: {token_stats['total_trades']}\n"
+                f"• Buy Orders: {token_stats.get('buy_trades', 0)}\n"
+                f"• Sell Orders: {token_stats.get('sell_trades', 0)}\n"
+                f"• Volume: ${token_stats.get('total_volume', 0):,.2f}\n\n"
+                f"💡 Complete some trades to see P&L statistics!\n"
+                f"🔄 Use <code>/performance</code> to see all token rankings.",
+                parse_mode='HTML'
+            )
+            return
+        
+        # Detailed stats display
+        pnl_emoji = "🟢" if token_stats['total_pnl'] >= 0 else "🔴"
+        
+        performance_text = f"""
+📊 <b>{token} Detailed Performance</b>
+
+💰 <b>P&L Summary:</b>
+• {pnl_emoji} Total P&L: ${token_stats['total_pnl']:,.2f} ({token_stats['pnl_percentage']:+.2f}%)
+• 💵 Total Volume: ${token_stats['completed_volume']:,.2f}
+• 📈 Expectancy: ${token_stats['expectancy']:,.2f}
+
+📊 <b>Trading Activity:</b>
+• Total Trades: {token_stats['total_trades']}
+• Completed: {token_stats['completed_trades']}
+• Buy Orders: {token_stats['buy_trades']}
+• Sell Orders: {token_stats['sell_trades']}
+
+🏆 <b>Performance Metrics:</b>
+• Win Rate: {token_stats['win_rate']:.1f}%
+• Profit Factor: {token_stats['profit_factor']:.2f}
+• Wins: {token_stats['total_wins']} | Losses: {token_stats['total_losses']}
+
+💡 <b>Best/Worst:</b>
+• Largest Win: ${token_stats['largest_win']:,.2f}
+• Largest Loss: ${token_stats['largest_loss']:,.2f}
+• Avg Win: ${token_stats['avg_win']:,.2f}
+• Avg Loss: ${token_stats['avg_loss']:,.2f}
+        """
+        
+        # Add recent trades if available
+        if token_stats.get('recent_trades'):
+            performance_text += f"\n🔄 <b>Recent Trades:</b>\n"
+            for trade in token_stats['recent_trades'][-3:]:  # Last 3 trades
+                trade_time = datetime.fromisoformat(trade['timestamp']).strftime('%m/%d %H:%M')
+                side_emoji = "🟢" if trade['side'] == 'buy' else "🔴"
+                pnl_display = f" | P&L: ${trade.get('pnl', 0):.2f}" if trade.get('pnl', 0) != 0 else ""
+                
+                performance_text += f"• {side_emoji} {trade['side'].upper()} ${trade['value']:,.0f} @ {trade_time}{pnl_display}\n"
+        
+        performance_text += f"\n🔄 Use <code>/performance</code> to see all token rankings"
+        
+        await update.message.reply_text(performance_text.strip(), parse_mode='HTML')
+
+    async def daily_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /daily command to show daily performance stats."""
+        if not self._is_authorized(update.effective_chat.id):
+            await update.message.reply_text("❌ Unauthorized access.")
+            return
+        
+        try:
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await update.message.reply_text("❌ Could not load trading statistics")
+                return
+                
+            daily_stats = stats.get_daily_stats(10)
+            
+            if not daily_stats:
+                await update.message.reply_text(
+                    "📅 <b>Daily Performance</b>\n\n"
+                    "📭 No daily performance data available yet.\n\n"
+                    "💡 Daily stats are calculated from completed trades.\n"
+                    "Start trading to see daily performance!",
+                    parse_mode='HTML'
+                )
+                return
+            
+            daily_text = "📅 <b>Daily Performance (Last 10 Days)</b>\n\n"
+            
+            total_pnl = 0
+            total_trades = 0
+            trading_days = 0
+            
+            for day_stats in daily_stats:
+                if day_stats['has_trades']:
+                    # Day with completed trades
+                    pnl_emoji = "🟢" if day_stats['pnl'] >= 0 else "🔴"
+                    daily_text += f"📊 <b>{day_stats['date_formatted']}</b>\n"
+                    daily_text += f"   {pnl_emoji} P&L: ${day_stats['pnl']:,.2f} ({day_stats['pnl_pct']:+.1f}%)\n"
+                    daily_text += f"   🔄 Trades: {day_stats['trades']}\n\n"
+                    
+                    total_pnl += day_stats['pnl']
+                    total_trades += day_stats['trades']
+                    trading_days += 1
+                else:
+                    # Day with no trades
+                    daily_text += f"📊 <b>{day_stats['date_formatted']}</b>\n"
+                    daily_text += f"   📭 No trading activity\n\n"
+            
+            # Add summary
+            if trading_days > 0:
+                avg_daily_pnl = total_pnl / trading_days
+                avg_pnl_emoji = "🟢" if avg_daily_pnl >= 0 else "🔴"
+                
+                daily_text += f"📈 <b>Period Summary:</b>\n"
+                daily_text += f"   {avg_pnl_emoji} Total P&L: ${total_pnl:,.2f}\n"
+                daily_text += f"   📊 Trading Days: {trading_days}/10\n"
+                daily_text += f"   📈 Avg Daily P&L: ${avg_daily_pnl:,.2f}\n"
+                daily_text += f"   🔄 Total Trades: {total_trades}\n"
+            
+            await update.message.reply_text(daily_text.strip(), parse_mode='HTML')
+                
+        except Exception as e:
+            error_message = f"❌ Error processing daily command: {str(e)}"
+            await update.message.reply_text(error_message)
+            logger.error(f"Error in daily command: {e}")
+    
+    async def weekly_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /weekly command to show weekly performance stats."""
+        if not self._is_authorized(update.effective_chat.id):
+            await update.message.reply_text("❌ Unauthorized access.")
+            return
+        
+        try:
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await update.message.reply_text("❌ Could not load trading statistics")
+                return
+                
+            weekly_stats = stats.get_weekly_stats(10)
+            
+            if not weekly_stats:
+                await update.message.reply_text(
+                    "📊 <b>Weekly Performance</b>\n\n"
+                    "📭 No weekly performance data available yet.\n\n"
+                    "💡 Weekly stats are calculated from completed trades.\n"
+                    "Start trading to see weekly performance!",
+                    parse_mode='HTML'
+                )
+                return
+            
+            weekly_text = "📊 <b>Weekly Performance (Last 10 Weeks)</b>\n\n"
+            
+            total_pnl = 0
+            total_trades = 0
+            trading_weeks = 0
+            
+            for week_stats in weekly_stats:
+                if week_stats['has_trades']:
+                    # Week with completed trades
+                    pnl_emoji = "🟢" if week_stats['pnl'] >= 0 else "🔴"
+                    weekly_text += f"📈 <b>{week_stats['week_formatted']}</b>\n"
+                    weekly_text += f"   {pnl_emoji} P&L: ${week_stats['pnl']:,.2f} ({week_stats['pnl_pct']:+.1f}%)\n"
+                    weekly_text += f"   🔄 Trades: {week_stats['trades']}\n\n"
+                    
+                    total_pnl += week_stats['pnl']
+                    total_trades += week_stats['trades']
+                    trading_weeks += 1
+                else:
+                    # Week with no trades
+                    weekly_text += f"📈 <b>{week_stats['week_formatted']}</b>\n"
+                    weekly_text += f"   📭 No completed trades\n\n"
+            
+            # Add summary
+            if trading_weeks > 0:
+                total_pnl_emoji = "🟢" if total_pnl >= 0 else "🔴"
+                weekly_text += f"💼 <b>10-Week Summary:</b>\n"
+                weekly_text += f"   {total_pnl_emoji} Total P&L: ${total_pnl:,.2f}\n"
+                weekly_text += f"   🔄 Total Trades: {total_trades}\n"
+                weekly_text += f"   📈 Trading Weeks: {trading_weeks}/10\n"
+                weekly_text += f"   📊 Avg per Trading Week: ${total_pnl/trading_weeks:,.2f}"
+            else:
+                weekly_text += f"💼 <b>10-Week Summary:</b>\n"
+                weekly_text += f"   📭 No completed trades in the last 10 weeks\n"
+                weekly_text += f"   💡 Start trading to see weekly performance!"
+            
+            await update.message.reply_text(weekly_text.strip(), parse_mode='HTML')
+            
+        except Exception as e:
+            error_message = f"❌ Error processing weekly command: {str(e)}"
+            await update.message.reply_text(error_message)
+            logger.error(f"Error in weekly command: {e}")
+
+    async def monthly_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /monthly command to show monthly performance stats."""
+        if not self._is_authorized(update.effective_chat.id):
+            await update.message.reply_text("❌ Unauthorized access.")
+            return
+        
+        try:
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await update.message.reply_text("❌ Could not load trading statistics")
+                return
+                
+            monthly_stats = stats.get_monthly_stats(10)
+            
+            if not monthly_stats:
+                await update.message.reply_text(
+                    "📆 <b>Monthly Performance</b>\n\n"
+                    "📭 No monthly performance data available yet.\n\n"
+                    "💡 Monthly stats are calculated from completed trades.\n"
+                    "Start trading to see monthly performance!",
+                    parse_mode='HTML'
+                )
+                return
+            
+            monthly_text = "📆 <b>Monthly Performance (Last 10 Months)</b>\n\n"
+            
+            total_pnl = 0
+            total_trades = 0
+            trading_months = 0
+            
+            for month_stats in monthly_stats:
+                if month_stats['has_trades']:
+                    # Month with completed trades
+                    pnl_emoji = "🟢" if month_stats['pnl'] >= 0 else "🔴"
+                    monthly_text += f"📅 <b>{month_stats['month_formatted']}</b>\n"
+                    monthly_text += f"   {pnl_emoji} P&L: ${month_stats['pnl']:,.2f} ({month_stats['pnl_pct']:+.1f}%)\n"
+                    monthly_text += f"   🔄 Trades: {month_stats['trades']}\n\n"
+                    
+                    total_pnl += month_stats['pnl']
+                    total_trades += month_stats['trades']
+                    trading_months += 1
+                else:
+                    # Month with no trades
+                    monthly_text += f"📅 <b>{month_stats['month_formatted']}</b>\n"
+                    monthly_text += f"   📭 No completed trades\n\n"
+            
+            # Add summary
+            if trading_months > 0:
+                total_pnl_emoji = "🟢" if total_pnl >= 0 else "🔴"
+                monthly_text += f"💼 <b>10-Month Summary:</b>\n"
+                monthly_text += f"   {total_pnl_emoji} Total P&L: ${total_pnl:,.2f}\n"
+                monthly_text += f"   🔄 Total Trades: {total_trades}\n"
+                monthly_text += f"   📈 Trading Months: {trading_months}/10\n"
+                monthly_text += f"   📊 Avg per Trading Month: ${total_pnl/trading_months:,.2f}"
+            else:
+                monthly_text += f"💼 <b>10-Month Summary:</b>\n"
+                monthly_text += f"   📭 No completed trades in the last 10 months\n"
+                monthly_text += f"   💡 Start trading to see monthly performance!"
+            
+            await update.message.reply_text(monthly_text.strip(), parse_mode='HTML')
+            
+        except Exception as e:
+            error_message = f"❌ Error processing monthly command: {str(e)}"
+            await update.message.reply_text(error_message)
+            logger.error(f"Error in monthly command: {e}")
+    
+    async def risk_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /risk command to show advanced risk metrics."""
+        if not self._is_authorized(update.effective_chat.id):
+            await update.message.reply_text("❌ Unauthorized access.")
+            return
+        
+        try:
+            # Get current balance for context
+            balance = self.trading_engine.get_balance()
+            current_balance = 0
+            if balance and balance.get('total'):
+                current_balance = float(balance['total'].get('USDC', 0))
+            
+            # Get risk metrics and basic stats
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await update.message.reply_text("❌ Could not load trading statistics")
+                return
+                
+            risk_metrics = stats.get_risk_metrics()
+            basic_stats = stats.get_basic_stats()
+            
+            # Check if we have enough data for risk calculations
+            if basic_stats['completed_trades'] < 2:
+                await update.message.reply_text(
+                    "📊 <b>Risk Analysis</b>\n\n"
+                    "📭 <b>Insufficient Data</b>\n\n"
+                    f"• Current completed trades: {basic_stats['completed_trades']}\n"
+                    f"• Required for risk analysis: 2+ trades\n"
+                    f"• Daily balance snapshots: {len(stats.data.get('daily_balances', []))}\n\n"
+                    "💡 <b>To enable risk analysis:</b>\n"
+                    "• Complete more trades to generate returns data\n"
+                    "• Bot automatically records daily balance snapshots\n"
+                    "• Risk metrics will be available after sufficient trading history\n\n"
+                    "📈 Use /stats for current performance metrics",
+                    parse_mode='HTML'
+                )
+                return
+            
+            # Format the risk analysis message
+            risk_text = f"""
+📊 <b>Risk Analysis & Advanced Metrics</b>
+
+🎯 <b>Risk-Adjusted Performance:</b>
+• Sharpe Ratio: {risk_metrics['sharpe_ratio']:.3f}
+• Sortino Ratio: {risk_metrics['sortino_ratio']:.3f}
+• Annual Volatility: {risk_metrics['volatility']:.2f}%
+
+📉 <b>Drawdown Analysis:</b>
+• Maximum Drawdown: {risk_metrics['max_drawdown']:.2f}%
+• Value at Risk (95%): {risk_metrics['var_95']:.2f}%
+
+💰 <b>Portfolio Context:</b>
+• Current Balance: ${current_balance:,.2f}
+• Initial Balance: ${basic_stats['initial_balance']:,.2f}
+• Total P&L: ${basic_stats['total_pnl']:,.2f}
+• Days Active: {basic_stats['days_active']}
+
+📊 <b>Risk Interpretation:</b>
+"""
+            
+            # Add interpretive guidance
+            sharpe = risk_metrics['sharpe_ratio']
+            if sharpe > 2.0:
+                risk_text += "• 🟢 <b>Excellent</b> risk-adjusted returns (Sharpe > 2.0)\n"
+            elif sharpe > 1.0:
+                risk_text += "• 🟡 <b>Good</b> risk-adjusted returns (Sharpe > 1.0)\n"
+            elif sharpe > 0.5:
+                risk_text += "• 🟠 <b>Moderate</b> risk-adjusted returns (Sharpe > 0.5)\n"
+            elif sharpe > 0:
+                risk_text += "• 🔴 <b>Poor</b> risk-adjusted returns (Sharpe > 0)\n"
+            else:
+                risk_text += "• ⚫ <b>Negative</b> risk-adjusted returns (Sharpe < 0)\n"
+            
+            max_dd = risk_metrics['max_drawdown']
+            if max_dd < 5:
+                risk_text += "• 🟢 <b>Low</b> maximum drawdown (< 5%)\n"
+            elif max_dd < 15:
+                risk_text += "• 🟡 <b>Moderate</b> maximum drawdown (< 15%)\n"
+            elif max_dd < 30:
+                risk_text += "• 🟠 <b>High</b> maximum drawdown (< 30%)\n"
+            else:
+                risk_text += "• 🔴 <b>Very High</b> maximum drawdown (> 30%)\n"
+            
+            volatility = risk_metrics['volatility']
+            if volatility < 10:
+                risk_text += "• 🟢 <b>Low</b> portfolio volatility (< 10%)\n"
+            elif volatility < 25:
+                risk_text += "• 🟡 <b>Moderate</b> portfolio volatility (< 25%)\n"
+            elif volatility < 50:
+                risk_text += "• 🟠 <b>High</b> portfolio volatility (< 50%)\n"
+            else:
+                risk_text += "• 🔴 <b>Very High</b> portfolio volatility (> 50%)\n"
+            
+            risk_text += f"""
+
+💡 <b>Risk Definitions:</b>
+• <b>Sharpe Ratio:</b> Risk-adjusted return (excess return / volatility)
+• <b>Sortino Ratio:</b> Return / downside volatility (focuses on bad volatility)
+• <b>Max Drawdown:</b> Largest peak-to-trough decline
+• <b>VaR 95%:</b> Maximum expected loss 95% of the time
+• <b>Volatility:</b> Annualized standard deviation of returns
+
+📈 <b>Data Based On:</b>
+• Completed Trades: {basic_stats['completed_trades']}
+• Daily Balance Records: {len(stats.data.get('daily_balances', []))}
+• Trading Period: {basic_stats['days_active']} days
+
+🔄 Use /stats for trading performance metrics
+            """
+            
+            await update.message.reply_text(risk_text.strip(), parse_mode='HTML')
+            
+        except Exception as e:
+            error_message = f"❌ Error processing risk command: {str(e)}"
+            await update.message.reply_text(error_message)
+            logger.error(f"Error in risk command: {e}")
+    
+    async def balance_adjustments_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /balance_adjustments command to show deposit/withdrawal history."""
+        if not self._is_authorized(update.effective_chat.id):
+            await update.message.reply_text("❌ Unauthorized access.")
+            return
+        
+        try:
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await update.message.reply_text("❌ Could not load trading statistics")
+                return
+            
+            # Get balance adjustments summary
+            adjustments_summary = stats.get_balance_adjustments_summary()
+            
+            # Get detailed adjustments
+            all_adjustments = stats.data.get('balance_adjustments', [])
+            
+            if not all_adjustments:
+                await update.message.reply_text(
+                    "💰 <b>Balance Adjustments</b>\n\n"
+                    "📭 No deposits or withdrawals detected yet.\n\n"
+                    "💡 The bot automatically monitors for deposits and withdrawals\n"
+                    "every hour to maintain accurate P&L calculations.",
+                    parse_mode='HTML'
+                )
+                return
+            
+            # Format the message
+            adjustments_text = f"""
+💰 <b>Balance Adjustments History</b>
+
+📊 <b>Summary:</b>
+• Total Deposits: ${adjustments_summary['total_deposits']:,.2f}
+• Total Withdrawals: ${adjustments_summary['total_withdrawals']:,.2f}
+• Net Adjustment: ${adjustments_summary['net_adjustment']:,.2f}
+• Total Transactions: {adjustments_summary['adjustment_count']}
+
+📅 <b>Recent Adjustments:</b>
+"""
+            
+            # Show last 10 adjustments
+            recent_adjustments = sorted(all_adjustments, key=lambda x: x['timestamp'], reverse=True)[:10]
+            
+            for adj in recent_adjustments:
+                try:
+                    # Format timestamp
+                    adj_time = datetime.fromisoformat(adj['timestamp']).strftime('%m/%d %H:%M')
+                    
+                    # Format type and amount
+                    if adj['type'] == 'deposit':
+                        emoji = "💰"
+                        amount_str = f"+${adj['amount']:,.2f}"
+                    else:  # withdrawal
+                        emoji = "💸"
+                        amount_str = f"-${abs(adj['amount']):,.2f}"
+                    
+                    adjustments_text += f"• {emoji} {adj_time}: {amount_str}\n"
+                    
+                except Exception as adj_error:
+                    logger.warning(f"Error formatting adjustment: {adj_error}")
+                    continue
+            
+            adjustments_text += f"""
+
+💡 <b>How it Works:</b>
+• Bot checks for deposits/withdrawals every hour
+• Adjustments maintain accurate P&L calculations
+• Non-trading balance changes don't affect performance metrics
+• Trading statistics remain pure and accurate
+
+⏰ <b>Last Check:</b> {adjustments_summary['last_adjustment'][:16] if adjustments_summary['last_adjustment'] else 'Never'}
+            """
+            
+            await update.message.reply_text(adjustments_text.strip(), parse_mode='HTML')
+            
+        except Exception as e:
+            error_message = f"❌ Error processing balance adjustments command: {str(e)}"
+            await update.message.reply_text(error_message)
+            logger.error(f"Error in balance_adjustments command: {e}")
+    
+    async def commands_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /commands and /c command with quick action buttons."""
+        if not self._is_authorized(update.effective_chat.id):
+            await update.message.reply_text("❌ Unauthorized access.")
+            return
+        
+        commands_text = """
+📱 <b>Quick Commands</b>
+
+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!
+        """
+        
+        from telegram import InlineKeyboardButton, InlineKeyboardMarkup
+        
+        keyboard = [
+            [
+                InlineKeyboardButton("💰 Balance", callback_data="balance"),
+                InlineKeyboardButton("📈 Positions", callback_data="positions")
+            ],
+            [
+                InlineKeyboardButton("📋 Orders", callback_data="orders"),
+                InlineKeyboardButton("📊 Stats", callback_data="stats")
+            ],
+            [
+                InlineKeyboardButton("💵 Price", callback_data="price"),
+                InlineKeyboardButton("📊 Market", callback_data="market")
+            ],
+            [
+                InlineKeyboardButton("🏆 Performance", callback_data="performance"),
+                InlineKeyboardButton("🔔 Alarms", callback_data="alarm")
+            ],
+            [
+                InlineKeyboardButton("📅 Daily", callback_data="daily"),
+                InlineKeyboardButton("📊 Weekly", callback_data="weekly")
+            ],
+            [
+                InlineKeyboardButton("📆 Monthly", callback_data="monthly"),
+                InlineKeyboardButton("🔄 Trades", callback_data="trades")
+            ],
+            [
+                InlineKeyboardButton("🔄 Monitoring", callback_data="monitoring"),
+                InlineKeyboardButton("📝 Logs", callback_data="logs")
+            ],
+            [
+                InlineKeyboardButton("⚙️ Help", callback_data="help")
+            ]
+        ]
+        reply_markup = InlineKeyboardMarkup(keyboard)
+        
+        await update.message.reply_text(commands_text, parse_mode='HTML', reply_markup=reply_markup) 

+ 58 - 1
src/commands/management_commands.py

@@ -463,4 +463,61 @@ Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
         except Exception as e:
             error_message = f"❌ Error processing version command: {str(e)}"
             await update.message.reply_text(error_message)
-            logger.error(f"Error in version command: {e}") 
+            logger.error(f"Error in version command: {e}")
+    
+    async def keyboard_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /keyboard command to enable/show custom keyboard."""
+        if not self._is_authorized(update.effective_chat.id):
+            await update.message.reply_text("❌ Unauthorized access.")
+            return
+        
+        # Check if custom keyboard is enabled in config
+        keyboard_enabled = getattr(Config, 'TELEGRAM_CUSTOM_KEYBOARD_ENABLED', False)
+        
+        if keyboard_enabled:
+            # Create a simple reply keyboard with common commands
+            from telegram import ReplyKeyboardMarkup, KeyboardButton
+            
+            keyboard = [
+                [KeyboardButton("Balance"), KeyboardButton("Positions")],
+                [KeyboardButton("Orders"), KeyboardButton("Stats")],
+                [KeyboardButton("Daily"), KeyboardButton("Performance")],
+                [KeyboardButton("Help"), KeyboardButton("Commands")]
+            ]
+            
+            reply_markup = ReplyKeyboardMarkup(
+                keyboard,
+                resize_keyboard=True,
+                one_time_keyboard=False,
+                selective=True
+            )
+            
+            await update.message.reply_text(
+                "⌨️ <b>Custom Keyboard Activated!</b>\n\n"
+                "🎯 <b>Your quick buttons are now ready:</b>\n"
+                "• Balance - Account balance\n"
+                "• Positions - Open positions\n"
+                "• Orders - Active orders\n"
+                "• Stats - Trading statistics\n"
+                "• Daily - Daily performance\n"
+                "• Performance - Performance stats\n"
+                "• Help - Help guide\n"
+                "• Commands - Command menu\n\n"
+                "💡 <b>How to use:</b>\n"
+                "Tap any button below instead of typing the command manually!\n\n"
+                "🔧 These buttons will stay at the bottom of your chat.",
+                parse_mode='HTML',
+                reply_markup=reply_markup
+            )
+        else:
+            await update.message.reply_text(
+                "❌ <b>Custom Keyboard Disabled</b>\n\n"
+                "🔧 <b>To enable:</b>\n"
+                "• Set TELEGRAM_CUSTOM_KEYBOARD_ENABLED=true in your .env file\n"
+                "• Restart the bot\n"
+                "• Run /keyboard again\n\n"
+                f"📋 <b>Current config:</b>\n"
+                f"• Enabled: {keyboard_enabled}\n"
+                f"• Layout: {getattr(Config, 'TELEGRAM_CUSTOM_KEYBOARD_LAYOUT', 'default')}",
+                parse_mode='HTML'
+            ) 

+ 5 - 0
src/commands/trading_commands.py

@@ -599,6 +599,11 @@ This action cannot be undone.
             elif callback_data == 'cancel_order':
                 await query.edit_message_text("❌ Order cancelled.")
             
+            # Handle info command button callbacks
+            elif callback_data in ['balance', 'positions', 'orders', 'stats', 'trades', 'market', 'price', 
+                                 'performance', 'daily', 'weekly', 'monthly', 'alarm', 'monitoring', 'logs', 'help']:
+                await query.edit_message_text(f"✅ Please use /{callback_data} command to get the latest data.")
+            
         except Exception as e:
             await query.edit_message_text(f"❌ Error processing order: {e}")
             logger.error(f"Error in button callback: {e}")

+ 19 - 6
src/trading/trading_engine.py

@@ -105,7 +105,7 @@ class TradingEngine:
                     return position
         return None
     
-    def get_position_direction(self, position: Dict[str, Any]) -> Tuple[str, str, str]:
+    def get_position_direction(self, position: Dict[str, Any]) -> Tuple[str, str, float]:
         """
         Get position direction info from CCXT position data.
         Returns: (position_type, exit_side, contracts_abs)
@@ -114,13 +114,13 @@ class TradingEngine:
         side_field = position.get('side', '').lower()
         
         if side_field == 'long':
-            return "LONG", "sell", contracts
+            return "LONG", "sell", abs(contracts)
         elif side_field == 'short':
-            return "SHORT", "buy", contracts
+            return "SHORT", "buy", abs(contracts)
         else:
             # Fallback to contracts sign (less reliable)
             if contracts > 0:
-                return "LONG", "sell", contracts
+                return "LONG", "sell", abs(contracts)
             else:
                 return "SHORT", "buy", abs(contracts)
     
@@ -321,7 +321,7 @@ class TradingEngine:
         try:
             symbol = f"{token}/USDC:USDC"
             position_type, exit_side, contracts = self.get_position_direction(position)
-            entry_price = float(position.get('entryPx', 0))
+            entry_price = float(position.get('entryPrice', 0))
             
             # Validate stop loss price based on position direction
             if position_type == "LONG" and stop_price >= entry_price:
@@ -363,7 +363,7 @@ class TradingEngine:
         try:
             symbol = f"{token}/USDC:USDC"
             position_type, exit_side, contracts = self.get_position_direction(position)
-            entry_price = float(position.get('entryPx', 0))
+            entry_price = float(position.get('entryPrice', 0))
             
             # Validate take profit price based on position direction
             if position_type == "LONG" and profit_price <= entry_price:
@@ -410,6 +410,19 @@ class TradingEngine:
             logger.error(f"Error cancelling orders: {e}")
             return {"success": False, "error": str(e)}
     
+    # Alias methods for consistency with command handlers
+    async def execute_sl_order(self, token: str, stop_price: float) -> Dict[str, Any]:
+        """Alias for execute_stop_loss_order."""
+        return await self.execute_stop_loss_order(token, stop_price)
+    
+    async def execute_tp_order(self, token: str, profit_price: float) -> Dict[str, Any]:
+        """Alias for execute_take_profit_order."""
+        return await self.execute_take_profit_order(token, profit_price)
+    
+    async def execute_coo_order(self, token: str) -> Dict[str, Any]:
+        """Alias for cancel_all_orders."""
+        return await self.cancel_all_orders(token)
+    
     def is_bot_trade(self, trade_id: str) -> bool:
         """Check if a trade was generated by this bot."""
         return trade_id in self.bot_trade_ids