瀏覽代碼

Refactor info command handling in TelegramTradingBot for improved modularity.

- Removed the InfoCommands class and replaced it with dedicated command classes for balance, positions, orders, stats, trades, market, performance, daily, weekly, monthly, and risk.
- Updated command handlers in TelegramTradingBot to instantiate and utilize the new command classes, enhancing clarity and maintainability.
- This change streamlines the command structure, allowing for better organization and future scalability of the bot's functionality.
Carles Sentis 1 周之前
父節點
當前提交
72234b7b32

+ 35 - 11
src/bot/core.py

@@ -17,6 +17,17 @@ from src.notifications.notification_manager import NotificationManager
 from src.commands.trading_commands import TradingCommands
 from src.commands.info_commands import InfoCommands
 from src.commands.management_commands import ManagementCommands
+from src.commands.info.balance import BalanceCommands
+from src.commands.info.positions import PositionsCommands
+from src.commands.info.orders import OrdersCommands
+from src.commands.info.stats import StatsCommands
+from src.commands.info.trades import TradesCommands
+from src.commands.info.market import MarketCommands
+from src.commands.info.performance import PerformanceCommands
+from src.commands.info.daily import DailyCommands
+from src.commands.info.weekly import WeeklyCommands
+from src.commands.info.monthly import MonthlyCommands
+from src.commands.info.risk import RiskCommands
 
 logger = logging.getLogger(__name__)
 
@@ -80,19 +91,32 @@ class TelegramTradingBot:
         self.application.add_handler(CommandHandler("tp", self.trading_commands.tp_command))
         self.application.add_handler(CommandHandler("coo", self.trading_commands.coo_command))
         
+        # Instantiate new info command classes
+        self.balance_cmds = BalanceCommands(self.trading_engine, self.notification_manager)
+        self.positions_cmds = PositionsCommands(self.trading_engine, self.notification_manager)
+        self.orders_cmds = OrdersCommands(self.trading_engine, self.notification_manager)
+        self.stats_cmds = StatsCommands(self.trading_engine, self.notification_manager)
+        self.trades_cmds = TradesCommands(self.trading_engine, self.notification_manager)
+        self.market_cmds = MarketCommands(self.trading_engine, self.notification_manager)
+        self.performance_cmds = PerformanceCommands(self.trading_engine, self.notification_manager)
+        self.daily_cmds = DailyCommands(self.trading_engine, self.notification_manager)
+        self.weekly_cmds = WeeklyCommands(self.trading_engine, self.notification_manager)
+        self.monthly_cmds = MonthlyCommands(self.trading_engine, self.notification_manager)
+        self.risk_cmds = RiskCommands(self.trading_engine, self.notification_manager)
+
         # Info commands
-        self.application.add_handler(CommandHandler("balance", self.info_commands.balance_command))
-        self.application.add_handler(CommandHandler("positions", self.info_commands.positions_command))
-        self.application.add_handler(CommandHandler("orders", self.info_commands.orders_command))
-        self.application.add_handler(CommandHandler("stats", self.info_commands.stats_command))
-        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("balance", self.balance_cmds.balance_command))
+        self.application.add_handler(CommandHandler("positions", self.positions_cmds.positions_command))
+        self.application.add_handler(CommandHandler("orders", self.orders_cmds.orders_command))
+        self.application.add_handler(CommandHandler("stats", self.stats_cmds.stats_command))
+        self.application.add_handler(CommandHandler("trades", self.trades_cmds.trades_command))
+        self.application.add_handler(CommandHandler("market", self.market_cmds.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("performance", self.performance_cmds.performance_command))
+        self.application.add_handler(CommandHandler("daily", self.daily_cmds.daily_command))
+        self.application.add_handler(CommandHandler("weekly", self.weekly_cmds.weekly_command))
+        self.application.add_handler(CommandHandler("monthly", self.monthly_cmds.monthly_command))
+        self.application.add_handler(CommandHandler("risk", self.risk_cmds.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

+ 0 - 0
src/commands/info/__init__.py


+ 73 - 0
src/commands/info/balance.py

@@ -0,0 +1,73 @@
+import logging
+from typing import Dict, Any, Optional
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+
+logger = logging.getLogger(__name__)
+
+class BalanceCommands(InfoCommandsBase):
+    """Handles all balance-related commands."""
+
+    async def balance_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /balance command."""
+        try:
+            if not self._is_authorized(update):
+                await self._reply(update, "❌ Unauthorized access.")
+                return
+
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await self._reply(update, "❌ Trading stats not available.")
+                return
+
+            # Get balance info
+            balance_info = stats.get_balance_info()
+            if not balance_info:
+                await self._reply(update, "❌ Balance information not available.")
+                return
+
+            # Format balance text
+            balance_text = "💰 <b>Account Balance</b>\n\n"
+
+            # Add total balance
+            total_balance = balance_info.get('total_balance', 0.0)
+            balance_text += f"💵 Total Balance: ${total_balance:,.2f}\n"
+
+            # Add available balance
+            available_balance = balance_info.get('available_balance', 0.0)
+            balance_text += f"💳 Available Balance: ${available_balance:,.2f}\n"
+
+            # Add margin used
+            margin_used = balance_info.get('margin_used', 0.0)
+            if margin_used > 0:
+                balance_text += f"📊 Margin Used: ${margin_used:,.2f}\n"
+                margin_ratio = (margin_used / total_balance) * 100 if total_balance > 0 else 0
+                balance_text += f"⚖️ Margin Ratio: {margin_ratio:.2f}%\n"
+
+            # Add unrealized P&L
+            unrealized_pnl = balance_info.get('unrealized_pnl', 0.0)
+            pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
+            balance_text += f"{pnl_emoji} Unrealized P&L: ${unrealized_pnl:,.2f}\n"
+
+            # Add realized P&L if available
+            realized_pnl = balance_info.get('realized_pnl', 0.0)
+            if realized_pnl != 0:
+                realized_emoji = "🟢" if realized_pnl >= 0 else "🔴"
+                balance_text += f"{realized_emoji} Realized P&L: ${realized_pnl:,.2f}\n"
+
+            # Add total P&L
+            total_pnl = unrealized_pnl + realized_pnl
+            total_pnl_emoji = "🟢" if total_pnl >= 0 else "🔴"
+            balance_text += f"{total_pnl_emoji} Total P&L: ${total_pnl:,.2f}\n"
+
+            # Add P&L percentage if margin is used
+            if margin_used > 0:
+                pnl_percentage = (total_pnl / margin_used) * 100
+                balance_text += f"📈 Return on Margin: {pnl_percentage:+.2f}%\n"
+
+            await self._reply(update, balance_text.strip())
+
+        except Exception as e:
+            logger.error(f"Error in balance command: {e}")
+            await self._reply(update, "❌ Error retrieving balance information.")

+ 62 - 0
src/commands/info/balance_adjustments.py

@@ -0,0 +1,62 @@
+import logging
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+from src.utils.token_display_formatter import get_formatter
+
+logger = logging.getLogger(__name__)
+
+class BalanceAdjustmentsCommands(InfoCommandsBase):
+    """Handles all balance adjustment-related commands."""
+
+    async def balance_adjustments_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /balance_adjustments command to show balance adjustment history."""
+        if not self._is_authorized(update):
+            return
+
+        try:
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await self._reply(update, "❌ Could not load trading statistics")
+                return
+
+            # Get balance adjustments
+            adjustments = stats.get_balance_adjustments()
+            if not adjustments:
+                await self._reply(update, "📊 <b>Balance Adjustments</b>\n\n📭 No balance adjustments found.")
+                return
+
+            formatter = get_formatter()
+            adjustments_text = ["📊 <b>Balance Adjustments History</b>"]
+
+            # Sort adjustments by timestamp (newest first)
+            sorted_adjustments = sorted(adjustments, key=lambda x: x.get('timestamp', ''), reverse=True)
+
+            for adj in sorted_adjustments:
+                amount = float(adj.get('amount', 0))
+                reason = adj.get('reason', 'Unknown')
+                timestamp = adj.get('timestamp', '')
+                
+                # Format the adjustment
+                amount_emoji = "➕" if amount > 0 else "➖"
+                amount_str = formatter.format_price_with_symbol(abs(amount))
+                
+                adjustments_text.append(
+                    f"\n{amount_emoji} <b>{amount_str}</b>"
+                    f"\n• Reason: {reason}"
+                    f"\n• Time: {timestamp}"
+                )
+
+            # Add summary
+            total_adjustments = sum(float(adj.get('amount', 0)) for adj in adjustments)
+            total_emoji = "🟢" if total_adjustments >= 0 else "🔴"
+            total_str = formatter.format_price_with_symbol(total_adjustments)
+            
+            adjustments_text.append(f"\n\n💼 <b>Total Adjustments:</b> {total_emoji} {total_str}")
+
+            await self._reply(update, "\n".join(adjustments_text))
+
+        except Exception as e:
+            error_message = f"❌ Error processing balance adjustments command: {str(e)}"
+            await self._reply(update, error_message)
+            logger.error(f"Error in balance adjustments command: {e}") 

+ 58 - 0
src/commands/info/base.py

@@ -0,0 +1,58 @@
+import logging
+from typing import Optional, Dict, Any
+from telegram import Update
+from telegram.ext import ContextTypes
+
+logger = logging.getLogger(__name__)
+
+class InfoCommandsBase:
+    """Base class for all info commands with shared functionality."""
+    
+    def __init__(self, trading_engine, notification_manager=None):
+        """Initialize the base class with required dependencies."""
+        self.trading_engine = trading_engine
+        self.notification_manager = notification_manager
+        self.logger = logging.getLogger(__name__)
+
+    def _is_authorized(self, update: Update) -> bool:
+        """Check if the user is authorized to use the command."""
+        if not update or not update.effective_user:
+            return False
+        return True  # Add your authorization logic here
+
+    async def _reply(self, update: Update, text: str, parse_mode: str = 'HTML') -> None:
+        """Common reply method for all commands."""
+        try:
+            if update.callback_query:
+                await update.callback_query.answer()
+                await update.callback_query.edit_message_text(
+                    text=text,
+                    parse_mode=parse_mode
+                )
+            else:
+                await update.message.reply_text(
+                    text=text,
+                    parse_mode=parse_mode
+                )
+        except Exception as e:
+            self.logger.error(f"Error sending reply: {e}")
+            # Try fallback reply method
+            try:
+                if update.message:
+                    await update.message.reply_text(
+                        text="❌ Error processing command. Please try again.",
+                        parse_mode=parse_mode
+                    )
+            except Exception as e2:
+                self.logger.error(f"Error sending fallback reply: {e2}")
+
+    def _get_formatter(self):
+        """Get the token display formatter."""
+        from src.utils.token_display_formatter import get_formatter
+        return get_formatter()
+
+    def _normalize_token_case(self, token: str) -> str:
+        """Normalize token case for consistent display."""
+        if not token:
+            return token
+        return token.upper()  # Add your token case normalization logic here

+ 63 - 0
src/commands/info/commands.py

@@ -0,0 +1,63 @@
+import logging
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+
+logger = logging.getLogger(__name__)
+
+class CommandsInfo(InfoCommandsBase):
+    """Handles the commands information command."""
+
+    async def commands_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /commands command to show available commands."""
+        if not self._is_authorized(update):
+            return
+
+        try:
+            commands_text = ["🤖 <b>Available Commands</b>"]
+
+            # Trading Commands
+            commands_text.append("\n📈 <b>Trading Commands:</b>")
+            commands_text.append("• /long [token] [amount] - Open a long position")
+            commands_text.append("• /short [token] [amount] - Open a short position")
+            commands_text.append("• /exit [token] - Close position")
+            commands_text.append("• /sl [token] [price] - Set stop loss")
+            commands_text.append("• /tp [token] [price] - Set take profit")
+            commands_text.append("• /coo [token] [price] - Cancel open orders")
+
+            # Information Commands
+            commands_text.append("\n📊 <b>Information Commands:</b>")
+            commands_text.append("• /balance - Show account balance")
+            commands_text.append("• /positions - Show open positions")
+            commands_text.append("• /orders - Show open orders")
+            commands_text.append("• /trades - Show recent trades")
+            commands_text.append("• /stats - Show trading statistics")
+            commands_text.append("• /market - Show market overview")
+            commands_text.append("• /price [token] - Show token price")
+            commands_text.append("• /performance [token] - Show performance stats")
+            commands_text.append("• /daily - Show daily performance")
+            commands_text.append("• /weekly - Show weekly performance")
+            commands_text.append("• /monthly - Show monthly performance")
+            commands_text.append("• /risk - Show risk management info")
+            commands_text.append("• /balance_adjustments - Show balance adjustments")
+
+            # Management Commands
+            commands_text.append("\n⚙️ <b>Management Commands:</b>")
+            commands_text.append("• /monitoring - Show monitoring status")
+            commands_text.append("• /alarm - Set price alarms")
+            commands_text.append("• /logs - Show recent logs")
+            commands_text.append("• /debug - Show debug information")
+            commands_text.append("• /version - Show bot version")
+            commands_text.append("• /keyboard - Toggle keyboard")
+
+            # Help
+            commands_text.append("\n💡 <b>Help:</b>")
+            commands_text.append("• Use /help for detailed command information")
+            commands_text.append("• Add ? after any command for help with that command")
+
+            await self._reply(update, "\n".join(commands_text))
+
+        except Exception as e:
+            error_message = f"❌ Error processing commands command: {str(e)}"
+            await self._reply(update, error_message)
+            logger.error(f"Error in commands command: {e}") 

+ 76 - 0
src/commands/info/daily.py

@@ -0,0 +1,76 @@
+import logging
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+from src.utils.token_display_formatter import get_formatter
+
+logger = logging.getLogger(__name__)
+
+class DailyCommands(InfoCommandsBase):
+    """Handles all daily performance-related commands."""
+
+    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):
+            return
+
+        try:
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await self._reply(update, "❌ Could not load trading statistics")
+                return
+
+            daily_stats_list = stats.get_daily_stats(10)  # Last 10 days
+            formatter = get_formatter()
+
+            if not daily_stats_list:
+                await self._reply(update, "📅 <b>Daily Performance</b>\n📭 No daily performance data available yet.\n💡 Daily stats are calculated from completed trades. Start trading to see them!")
+                return
+
+            daily_text_parts = [f"📅 <b>Daily Performance (Last 10 Days)</b>"]
+
+            total_pnl_all_days = 0
+            total_trades_all_days = 0
+            total_roe_all_days = 0
+            trading_days_count = 0
+
+            period_lines = []
+            for day_stats_item in daily_stats_list:
+                if day_stats_item['has_trades']:
+                    pnl_emoji = "🟢" if day_stats_item['pnl'] >= 0 else "🔴"
+                    pnl_str = formatter.format_price_with_symbol(day_stats_item['pnl'])
+                    roe = day_stats_item.get('roe', 0.0)  # Get ROE from stats
+                    roe_str = f"ROE: {roe:+.1f}%" if roe != 0 else ""
+                    period_lines.append(f"📅 <b>{day_stats_item['day_formatted']}</b>: {pnl_emoji} {pnl_str} ({day_stats_item['pnl_pct']:+.1f}%) {roe_str} | Trades: {day_stats_item['trades']}")
+                    total_pnl_all_days += day_stats_item['pnl']
+                    total_trades_all_days += day_stats_item['trades']
+                    total_roe_all_days += roe
+                    trading_days_count += 1
+                else:
+                    period_lines.append(f"📅 <b>{day_stats_item['day_formatted']}</b>: 📭 No trading activity")
+
+            if period_lines:
+                daily_text_parts.append("\n".join(period_lines))
+
+            if trading_days_count > 0:
+                avg_daily_pnl = total_pnl_all_days / trading_days_count
+                avg_daily_roe = total_roe_all_days / trading_days_count
+                avg_pnl_emoji = "🟢" if avg_daily_pnl >= 0 else "🔴"
+                total_pnl_all_days_str = formatter.format_price_with_symbol(total_pnl_all_days)
+                avg_daily_pnl_str = formatter.format_price_with_symbol(avg_daily_pnl)
+
+                daily_text_parts.append(f"\n\n📈 <b>Period Summary:</b>")
+                daily_text_parts.append(f"  Total P&L: {avg_pnl_emoji} {total_pnl_all_days_str} | Avg Daily: {avg_daily_pnl_str}")
+                daily_text_parts.append(f"  Total ROE: {total_roe_all_days:+.1f}% | Avg Daily ROE: {avg_daily_roe:+.1f}%")
+                daily_text_parts.append(f"  Trading Days: {trading_days_count}/10 | Total Trades: {total_trades_all_days}")
+            else:
+                if not period_lines:
+                    daily_text_parts = [daily_text_parts[0]]
+                daily_text_parts.append("\n\n📉 No trading activity in the last 10 days.")
+
+            await self._reply(update, "\n".join(daily_text_parts).strip())
+
+        except Exception as e:
+            error_message = f"❌ Error processing daily command: {str(e)}"
+            await self._reply(update, error_message)
+            logger.error(f"Error in daily command: {e}") 

+ 61 - 0
src/commands/info/market.py

@@ -0,0 +1,61 @@
+import logging
+from typing import Dict, Any, List, Optional
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+
+logger = logging.getLogger(__name__)
+
+class MarketCommands(InfoCommandsBase):
+    """Handles all market-related commands."""
+
+    async def market_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /market command."""
+        try:
+            if not self._is_authorized(update):
+                await self._reply(update, "❌ Unauthorized access.")
+                return
+
+            # Get market data
+            market_data = self.trading_engine.get_market_data()
+            if not market_data:
+                await self._reply(update, "❌ Market data not available.")
+                return
+
+            # Format market text
+            market_text = "📊 <b>Market Overview</b>\n\n"
+
+            for symbol, data in market_data.items():
+                try:
+                    base_asset = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
+                    price = float(data.get('price', 0))
+                    volume_24h = float(data.get('volume_24h', 0))
+                    change_24h = float(data.get('change_24h', 0))
+                    high_24h = float(data.get('high_24h', 0))
+                    low_24h = float(data.get('low_24h', 0))
+
+                    # Format market details
+                    formatter = self._get_formatter()
+                    price_str = formatter.format_price_with_symbol(price, base_asset)
+                    volume_str = formatter.format_amount(volume_24h, base_asset)
+                    high_str = formatter.format_price_with_symbol(high_24h, base_asset)
+                    low_str = formatter.format_price_with_symbol(low_24h, base_asset)
+
+                    # Market header
+                    change_emoji = "🟢" if change_24h >= 0 else "🔴"
+                    market_text += f"{change_emoji} <b>{base_asset}</b>\n"
+                    market_text += f"   💰 Price: {price_str}\n"
+                    market_text += f"   📈 24h Change: {change_24h:+.2f}%\n"
+                    market_text += f"   📊 24h Volume: {volume_str} {base_asset}\n"
+                    market_text += f"   🔺 24h High: {high_str}\n"
+                    market_text += f"   🔻 24h Low: {low_str}\n\n"
+
+                except Exception as e:
+                    logger.error(f"Error processing market data for {symbol}: {e}")
+                    continue
+
+            await self._reply(update, market_text.strip())
+
+        except Exception as e:
+            logger.error(f"Error in market command: {e}")
+            await self._reply(update, "❌ Error retrieving market data.")

+ 76 - 0
src/commands/info/monthly.py

@@ -0,0 +1,76 @@
+import logging
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+from src.utils.token_display_formatter import get_formatter
+
+logger = logging.getLogger(__name__)
+
+class MonthlyCommands(InfoCommandsBase):
+    """Handles all monthly performance-related commands."""
+
+    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):
+            return
+
+        try:
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await self._reply(update, "❌ Could not load trading statistics")
+                return
+
+            monthly_stats_list = stats.get_monthly_stats(12)  # Last 12 months
+            formatter = get_formatter()
+
+            if not monthly_stats_list:
+                await self._reply(update, "📅 <b>Monthly Performance</b>\n📭 No monthly performance data available yet.\n💡 Monthly stats are calculated from completed trades. Start trading to see them!")
+                return
+
+            monthly_text_parts = [f"📅 <b>Monthly Performance (Last 12 Months)</b>"]
+
+            total_pnl_all_months = 0
+            total_trades_all_months = 0
+            total_roe_all_months = 0
+            trading_months_count = 0
+
+            period_lines = []
+            for month_stats_item in monthly_stats_list:
+                if month_stats_item['has_trades']:
+                    pnl_emoji = "🟢" if month_stats_item['pnl'] >= 0 else "🔴"
+                    pnl_str = formatter.format_price_with_symbol(month_stats_item['pnl'])
+                    roe = month_stats_item.get('roe', 0.0)  # Get ROE from stats
+                    roe_str = f"ROE: {roe:+.1f}%" if roe != 0 else ""
+                    period_lines.append(f"📅 <b>{month_stats_item['month_formatted']}</b>: {pnl_emoji} {pnl_str} ({month_stats_item['pnl_pct']:+.1f}%) {roe_str} | Trades: {month_stats_item['trades']}")
+                    total_pnl_all_months += month_stats_item['pnl']
+                    total_trades_all_months += month_stats_item['trades']
+                    total_roe_all_months += roe
+                    trading_months_count += 1
+                else:
+                    period_lines.append(f"📅 <b>{month_stats_item['month_formatted']}</b>: 📭 No trading activity")
+
+            if period_lines:
+                monthly_text_parts.append("\n".join(period_lines))
+
+            if trading_months_count > 0:
+                avg_monthly_pnl = total_pnl_all_months / trading_months_count
+                avg_monthly_roe = total_roe_all_months / trading_months_count
+                avg_pnl_emoji = "🟢" if avg_monthly_pnl >= 0 else "🔴"
+                total_pnl_all_months_str = formatter.format_price_with_symbol(total_pnl_all_months)
+                avg_monthly_pnl_str = formatter.format_price_with_symbol(avg_monthly_pnl)
+
+                monthly_text_parts.append(f"\n\n📈 <b>Period Summary:</b>")
+                monthly_text_parts.append(f"  Total P&L: {avg_pnl_emoji} {total_pnl_all_months_str} | Avg Monthly: {avg_monthly_pnl_str}")
+                monthly_text_parts.append(f"  Total ROE: {total_roe_all_months:+.1f}% | Avg Monthly ROE: {avg_monthly_roe:+.1f}%")
+                monthly_text_parts.append(f"  Trading Months: {trading_months_count}/12 | Total Trades: {total_trades_all_months}")
+            else:
+                if not period_lines:
+                    monthly_text_parts = [monthly_text_parts[0]]
+                monthly_text_parts.append("\n\n📉 No trading activity in the last 12 months.")
+
+            await self._reply(update, "\n".join(monthly_text_parts).strip())
+
+        except Exception as e:
+            error_message = f"❌ Error processing monthly command: {str(e)}"
+            await self._reply(update, error_message)
+            logger.error(f"Error in monthly command: {e}") 

+ 74 - 0
src/commands/info/orders.py

@@ -0,0 +1,74 @@
+import logging
+from typing import Dict, Any, List, Optional
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+
+logger = logging.getLogger(__name__)
+
+class OrdersCommands(InfoCommandsBase):
+    """Handles all order-related commands."""
+
+    async def orders_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /orders command."""
+        try:
+            if not self._is_authorized(update):
+                await self._reply(update, "❌ Unauthorized access.")
+                return
+
+            # Get open orders
+            open_orders = self.trading_engine.get_orders()
+            if not open_orders:
+                await self._reply(update, "📭 No open orders")
+                return
+
+            # Format orders text
+            orders_text = "📋 <b>Open Orders</b>\n\n"
+
+            for order in open_orders:
+                try:
+                    symbol = order.get('symbol', 'unknown')
+                    base_asset = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
+                    order_type = order.get('type', 'unknown').upper()
+                    side = order.get('side', 'unknown').upper()
+                    price = float(order.get('price', 0))
+                    amount = float(order.get('amount', 0))
+                    filled = float(order.get('filled', 0))
+                    remaining = amount - filled
+                    status = order.get('status', 'unknown').upper()
+
+                    # Skip fully filled orders
+                    if remaining <= 0:
+                        continue
+
+                    # Format order details
+                    formatter = self._get_formatter()
+                    price_str = formatter.format_price_with_symbol(price, base_asset)
+                    amount_str = formatter.format_amount(remaining, base_asset)
+
+                    # Order header
+                    side_emoji = "🟢" if side == "BUY" else "🔴"
+                    orders_text += f"{side_emoji} <b>{base_asset} {side} {order_type}</b>\n"
+                    orders_text += f"   📏 Amount: {amount_str} {base_asset}\n"
+                    orders_text += f"   💰 Price: {price_str}\n"
+
+                    # Add order type specific info
+                    if order_type == "STOP_LOSS" or order_type == "TAKE_PROFIT":
+                        trigger_price = order.get('info', {}).get('triggerPrice')
+                        if trigger_price:
+                            trigger_price_str = formatter.format_price_with_symbol(float(trigger_price), base_asset)
+                            orders_text += f"   🎯 Trigger: {trigger_price_str}\n"
+
+                    # Add order ID
+                    order_id = order.get('id', 'unknown')
+                    orders_text += f"   🆔 Order ID: {order_id[:8]}\n\n"
+
+                except Exception as e:
+                    logger.error(f"Error processing order {order.get('symbol', 'unknown')}: {e}")
+                    continue
+
+            await self._reply(update, orders_text.strip())
+
+        except Exception as e:
+            logger.error(f"Error in orders command: {e}")
+            await self._reply(update, "❌ Error retrieving order information.")

+ 127 - 0
src/commands/info/performance.py

@@ -0,0 +1,127 @@
+import logging
+from datetime import datetime
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+from .utils import normalize_token_case
+from src.config.config import Config
+from src.utils.token_display_formatter import get_formatter
+
+logger = logging.getLogger(__name__)
+
+class PerformanceCommands(InfoCommandsBase):
+    """Handles all performance-related commands."""
+
+    async def performance_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /performance command to show performance information."""
+        if not self._is_authorized(update):
+            return
+
+        try:
+            # Check if a specific token was requested
+            args = context.args
+            token = args[0].upper() if args else None
+
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await self._reply(update, "❌ Could not load trading statistics")
+                return
+
+            if token:
+                await self._show_token_performance(update, token, stats)
+            else:
+                await self._show_performance_ranking(update, stats)
+
+        except Exception as e:
+            error_message = f"❌ Error processing performance command: {str(e)}"
+            await self._reply(update, error_message)
+            logger.error(f"Error in performance command: {e}")
+
+    async def _show_performance_ranking(self, update: Update, stats) -> None:
+        """Show performance ranking for all tokens."""
+        try:
+            # Get performance data for all tokens
+            token_performance = stats.get_token_performance()
+            if not token_performance:
+                await self._reply(update, "📊 No performance data available yet")
+                return
+
+            # Sort tokens by P&L
+            sorted_tokens = sorted(
+                token_performance.items(),
+                key=lambda x: x[1].get('total_pnl', 0),
+                reverse=True
+            )
+
+            formatter = get_formatter()
+            performance_text = ["📊 <b>Token Performance Ranking</b>"]
+
+            for token, data in sorted_tokens:
+                total_pnl = data.get('total_pnl', 0)
+                total_trades = data.get('total_trades', 0)
+                win_rate = data.get('win_rate', 0)
+                
+                if total_trades > 0:
+                    pnl_emoji = "🟢" if total_pnl >= 0 else "🔴"
+                    performance_text.append(
+                        f"\n<b>{token}</b>: {pnl_emoji} {formatter.format_price_with_symbol(total_pnl)} "
+                        f"({total_trades} trades, {win_rate:.1f}% win rate)"
+                    )
+
+            await self._reply(update, "\n".join(performance_text))
+
+        except Exception as e:
+            error_message = f"❌ Error showing performance ranking: {str(e)}"
+            await self._reply(update, error_message)
+            logger.error(f"Error in _show_performance_ranking: {e}")
+
+    async def _show_token_performance(self, update: Update, token: str, stats) -> None:
+        """Show detailed performance for a specific token."""
+        try:
+            # Get performance data for the token
+            token_performance = stats.get_token_performance(token)
+            if not token_performance:
+                await self._reply(update, f"📊 No performance data available for {token}")
+                return
+
+            formatter = get_formatter()
+            performance_text = [f"📊 <b>{token} Performance</b>"]
+
+            # Add summary statistics
+            total_pnl = token_performance.get('total_pnl', 0)
+            total_trades = token_performance.get('total_trades', 0)
+            win_rate = token_performance.get('win_rate', 0)
+            avg_trade = token_performance.get('avg_trade', 0)
+            largest_win = token_performance.get('largest_win', 0)
+            largest_loss = token_performance.get('largest_loss', 0)
+
+            pnl_emoji = "🟢" if total_pnl >= 0 else "🔴"
+            performance_text.extend([
+                f"\n<b>Summary:</b>",
+                f"• Total P&L: {pnl_emoji} {formatter.format_price_with_symbol(total_pnl)}",
+                f"• Total Trades: {total_trades}",
+                f"• Win Rate: {win_rate:.1f}%",
+                f"• Avg Trade: {formatter.format_price_with_symbol(avg_trade)}",
+                f"• Largest Win: {formatter.format_price_with_symbol(largest_win)}",
+                f"• Largest Loss: {formatter.format_price_with_symbol(largest_loss)}"
+            ])
+
+            # Add recent trades if available
+            recent_trades = token_performance.get('recent_trades', [])
+            if recent_trades:
+                performance_text.append("\n<b>Recent Trades:</b>")
+                for trade in recent_trades[:5]:  # Show last 5 trades
+                    side = trade.get('side', '').upper()
+                    pnl = trade.get('pnl', 0)
+                    pnl_emoji = "🟢" if pnl >= 0 else "🔴"
+                    performance_text.append(
+                        f"• {side}: {pnl_emoji} {formatter.format_price_with_symbol(pnl)} "
+                        f"({trade.get('entry_price', 0)} → {trade.get('exit_price', 0)})"
+                    )
+
+            await self._reply(update, "\n".join(performance_text))
+
+        except Exception as e:
+            error_message = f"❌ Error showing token performance: {str(e)}"
+            await self._reply(update, error_message)
+            logger.error(f"Error in _show_token_performance: {e}")

+ 224 - 0
src/commands/info/positions.py

@@ -0,0 +1,224 @@
+import logging
+from typing import Dict, Any, List, Optional
+from datetime import datetime, timezone
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+
+logger = logging.getLogger(__name__)
+
+class PositionsCommands(InfoCommandsBase):
+    """Handles all position-related commands."""
+
+    async def positions_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /positions command."""
+        try:
+            if not self._is_authorized(update):
+                await self._reply(update, "❌ Unauthorized access.")
+                return
+
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await self._reply(update, "❌ Trading stats not available.")
+                return
+
+            # Get open positions from DB
+            open_positions = stats.get_open_positions()
+            if not open_positions:
+                await self._reply(update, "📭 No open positions\n\n💡 Use /long or /short to open a position")
+                return
+
+            # Get current exchange orders for stop loss detection
+            exchange_orders = self.trading_engine.get_orders() or []
+
+            # Initialize totals
+            total_position_value = 0.0
+            total_unrealized = 0.0
+            total_margin_used = 0.0
+
+            # Build position details
+            positions_text = "📊 <b>Open Positions</b>\n\n"
+            
+            for position_trade in open_positions:
+                try:
+                    symbol = position_trade['symbol']
+                    base_asset = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
+                    position_side = position_trade.get('position_side', 'unknown')
+                    abs_current_amount = abs(float(position_trade.get('current_position_size', 0)))
+                    entry_price = float(position_trade.get('entry_price', 0))
+                    mark_price = position_trade.get('mark_price', entry_price)
+                    trade_type = position_trade.get('trade_type', 'unknown')
+
+                    # Calculate duration
+                    duration_str = "N/A"
+                    position_opened_at_str = position_trade.get('position_opened_at')
+                    if position_opened_at_str:
+                        try:
+                            position_opened_at = datetime.fromisoformat(position_opened_at_str.replace('Z', '+00:00'))
+                            duration = datetime.now(timezone.utc) - position_opened_at
+                            days = duration.days
+                            hours = duration.seconds // 3600
+                            minutes = (duration.seconds % 3600) // 60
+                            
+                            parts = []
+                            if days > 0:
+                                parts.append(f"{days}d")
+                            if hours > 0:
+                                parts.append(f"{hours}h")
+                            if minutes > 0 or (days == 0 and hours == 0):
+                                parts.append(f"{minutes}m")
+                            duration_str = " ".join(parts) if parts else "0m"
+                        except ValueError:
+                            logger.warning(f"Could not parse position_opened_at: {position_opened_at_str} for {symbol}")
+                            duration_str = "Error"
+
+                    # Calculate unrealized PnL
+                    unrealized_pnl = position_trade.get('unrealized_pnl')
+                    if unrealized_pnl is None:
+                        if position_side == 'long':
+                            unrealized_pnl = (mark_price - entry_price) * abs_current_amount
+                        else:  # Short position
+                            unrealized_pnl = (entry_price - mark_price) * abs_current_amount
+                    unrealized_pnl = unrealized_pnl or 0.0
+
+                    # ROE Percentage (from exchange, cached by heartbeat)
+                    roe_percentage = position_trade.get('unrealized_pnl_percentage') or 0.0
+
+                    # Add to totals
+                    individual_position_value = position_trade.get('position_value')
+                    if individual_position_value is None:
+                        individual_position_value = abs_current_amount * mark_price
+                    
+                    total_position_value += individual_position_value
+                    total_unrealized += unrealized_pnl
+                    
+                    margin_used = position_trade.get('margin_used')
+                    if margin_used is not None:
+                        total_margin_used += margin_used
+
+                    # Format position details
+                    formatter = self._get_formatter()
+                    entry_price_str = formatter.format_price_with_symbol(entry_price, base_asset)
+                    mark_price_str = formatter.format_price_with_symbol(mark_price, base_asset)
+                    size_str = formatter.format_amount(abs_current_amount, base_asset)
+
+                    # Position header
+                    pos_emoji = "🟢" if position_side == 'long' else "🔴"
+                    direction_text = position_side.upper()
+                    
+                    # Add leverage if available
+                    leverage = position_trade.get('leverage')
+                    if leverage is not None:
+                        try:
+                            leverage_val = float(leverage)
+                            leverage_str = f"x{leverage_val:.1f}".rstrip('0').rstrip('.') if '.' in f"{leverage_val:.1f}" else f"x{int(leverage_val)}"
+                            direction_text = f"{direction_text} {leverage_str}"
+                        except ValueError:
+                            logger.warning(f"Could not parse leverage value: {leverage} for {symbol}")
+
+                    # Add type indicator
+                    type_indicator = ""
+                    if position_trade.get('trade_lifecycle_id'):
+                        type_indicator = " 🤖"
+                    elif trade_type == 'external':
+                        type_indicator = " 🔄"
+
+                    # Build position text
+                    positions_text += f"{pos_emoji} <b>{base_asset} ({direction_text}){type_indicator}</b>\n"
+                    positions_text += f"   📏 Size: {size_str} {base_asset}\n"
+                    positions_text += f"   💰 Entry: {entry_price_str}\n"
+                    positions_text += f"   ⏳ Duration: {duration_str}\n"
+                    positions_text += f"   🏦 Value: ${individual_position_value:,.2f}\n"
+                    if mark_price != 0 and abs(mark_price - entry_price) > 1e-9:
+                        positions_text += f"   📈 Mark: {mark_price_str}\n"
+                    roe_emoji = "🟢" if roe_percentage >= 0 else "🔴"
+                    positions_text += f"   {roe_emoji} ROE: {roe_percentage:+.2f}%\n"
+
+                    # Add risk management info
+                    if margin_used is not None:
+                        positions_text += f"   💳 Margin Used: ${margin_used:,.2f}\n"
+                    if position_trade.get('liquidation_price') is not None and position_trade.get('liquidation_price') > 0:
+                        liq_price_str = formatter.format_price_with_symbol(position_trade.get('liquidation_price'), base_asset)
+                        positions_text += f"   ⚠️ Liquidation: {liq_price_str}\n"
+                    if position_trade.get('stop_loss_price'):
+                        sl_price = position_trade['stop_loss_price']
+                        positions_text += f"   🛑 Stop Loss: {formatter.format_price_with_symbol(sl_price, base_asset)}\n"
+                    if position_trade.get('take_profit_price'):
+                        tp_price = position_trade['take_profit_price']
+                        positions_text += f"   🎯 Take Profit: {formatter.format_price_with_symbol(tp_price, base_asset)}\n"
+
+                    # Add external stop losses
+                    external_sls = self._get_external_stop_losses(symbol, position_side, entry_price, abs_current_amount, exchange_orders)
+                    for ext_sl_price in external_sls:
+                        positions_text += f"   🛡️ External SL: {formatter.format_price_with_symbol(ext_sl_price, base_asset)}\n"
+
+                    positions_text += f"   🆔 Lifecycle ID: {position_trade['trade_lifecycle_id'][:8]}\n\n"
+
+                except Exception as e:
+                    logger.error(f"Error processing position {position_trade.get('symbol', 'unknown')}: {e}")
+                    continue
+
+            # Add portfolio summary
+            portfolio_emoji = "🟢" if total_unrealized >= 0 else "🔴"
+            positions_text += f"💼 <b>Total Portfolio:</b>\n"
+            positions_text += f"   🏦 Total Positions Value: ${total_position_value:,.2f}\n"
+            if total_margin_used > 0:
+                positions_text += f"   💳 Total Margin Used: ${total_margin_used:,.2f}\n"
+                leverage_ratio = total_position_value / total_margin_used if total_margin_used > 0 else 1.0
+                positions_text += f"   ⚖️ Portfolio Leverage: {leverage_ratio:.2f}x\n"
+            positions_text += f"   {portfolio_emoji} Total Unrealized P&L: ${total_unrealized:,.2f}\n"
+            if total_margin_used > 0:
+                margin_pnl_percentage = (total_unrealized / total_margin_used) * 100
+                positions_text += f"   📊 Portfolio Return: {margin_pnl_percentage:+.2f}% (on margin)\n"
+            positions_text += "\n"
+            positions_text += f"🤖 <b>Legend:</b> 🤖 Bot-created • 🔄 External/synced • 🛡️ External SL\n"
+            positions_text += f"💡 Use /sl [token] [price] or /tp [token] [price] to set risk management"
+
+            await self._reply(update, positions_text.strip())
+
+        except Exception as e:
+            logger.error(f"Error in positions command: {e}")
+            await self._reply(update, "❌ Error retrieving position information.")
+
+    def _get_external_stop_losses(self, symbol: str, position_side: str, entry_price: float, 
+                                current_amount: float, exchange_orders: List[Dict[str, Any]]) -> List[float]:
+        """Get external stop losses for a position."""
+        external_sls = []
+        for order in exchange_orders:
+            try:
+                order_symbol = order.get('symbol')
+                order_side = order.get('side', '').lower()
+                order_type = order.get('type', '').lower()
+                order_price = float(order.get('price', 0))
+                trigger_price = order.get('info', {}).get('triggerPrice')
+                is_reduce_only = order.get('reduceOnly', False) or order.get('info', {}).get('reduceOnly', False)
+                order_amount = float(order.get('amount', 0))
+                
+                if (order_symbol == symbol and is_reduce_only and 
+                    abs(order_amount - current_amount) < 0.01 * current_amount):
+                    
+                    sl_trigger_price = 0
+                    if trigger_price:
+                        try:
+                            sl_trigger_price = float(trigger_price)
+                        except (ValueError, TypeError):
+                            pass
+                    if not sl_trigger_price and order_price > 0:
+                        sl_trigger_price = order_price
+                    
+                    is_valid_sl = False
+                    if position_side == 'long' and order_side == 'sell':
+                        if sl_trigger_price > 0 and sl_trigger_price < entry_price:
+                            is_valid_sl = True
+                    elif position_side == 'short' and order_side == 'buy':
+                        if sl_trigger_price > 0 and sl_trigger_price > entry_price:
+                            is_valid_sl = True
+                    
+                    if is_valid_sl:
+                        external_sls.append(sl_trigger_price)
+                        
+            except Exception as e:
+                logger.warning(f"Error processing order for external SL: {e}")
+                continue
+                
+        return external_sls

+ 60 - 0
src/commands/info/price.py

@@ -0,0 +1,60 @@
+import logging
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+from src.utils.token_display_formatter import get_formatter
+
+logger = logging.getLogger(__name__)
+
+class PriceCommands(InfoCommandsBase):
+    """Handles all price-related commands."""
+
+    async def price_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /price command to show price information."""
+        if not self._is_authorized(update):
+            return
+
+        try:
+            # Check if a specific token was requested
+            args = context.args
+            if not args:
+                await self._reply(update, "❌ Please specify a token. Example: /price BTC")
+                return
+
+            token = args[0].upper()
+            market_data = self.trading_engine.get_market_data(token)
+            
+            if not market_data or not market_data.get('ticker'):
+                await self._reply(update, f"❌ Could not fetch price data for {token}")
+                return
+
+            ticker = market_data['ticker']
+            formatter = get_formatter()
+
+            # Format price information
+            price_text_parts = [f"💰 <b>{token} Price Information</b>"]
+
+            # Current price
+            current_price = float(ticker.get('last', 0))
+            price_text_parts.append(f"\n📊 <b>Current Price:</b> {formatter.format_price_with_symbol(current_price, token)}")
+
+            # 24h change
+            change_24h = float(ticker.get('percentage', 0))
+            change_emoji = "🟢" if change_24h >= 0 else "🔴"
+            price_text_parts.append(f"📈 <b>24h Change:</b> {change_emoji} {change_24h:+.2f}%")
+
+            # 24h high/low
+            high_24h = float(ticker.get('high', 0))
+            low_24h = float(ticker.get('low', 0))
+            price_text_parts.append(f"📊 <b>24h Range:</b> {formatter.format_price_with_symbol(low_24h, token)} - {formatter.format_price_with_symbol(high_24h, token)}")
+
+            # Volume
+            volume_24h = float(ticker.get('quoteVolume', 0))
+            price_text_parts.append(f"💎 <b>24h Volume:</b> {formatter.format_price_with_symbol(volume_24h)}")
+
+            await self._reply(update, "\n".join(price_text_parts))
+
+        except Exception as e:
+            error_message = f"❌ Error processing price command: {str(e)}"
+            await self._reply(update, error_message)
+            logger.error(f"Error in price command: {e}") 

+ 111 - 0
src/commands/info/risk.py

@@ -0,0 +1,111 @@
+import logging
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+from src.utils.token_display_formatter import get_formatter
+
+logger = logging.getLogger(__name__)
+
+class RiskCommands(InfoCommandsBase):
+    """Handles all risk management-related commands."""
+
+    async def risk_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /risk command to show risk management information."""
+        if not self._is_authorized(update):
+            return
+
+        try:
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await self._reply(update, "❌ Could not load trading statistics")
+                return
+
+            # Get current positions
+            positions = stats.get_open_positions()
+            if not positions:
+                await self._reply(update, "📭 No open positions to analyze risk")
+                return
+
+            # Get current orders for stop loss analysis
+            orders = self.trading_engine.get_orders() or []
+            
+            # Get formatter for consistent display
+            formatter = get_formatter()
+
+            # Build risk analysis message
+            risk_text_parts = ["🎯 <b>Risk Management Overview</b>"]
+
+            # Analyze each position
+            for position in positions:
+                symbol = position['symbol']
+                token = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
+                side = position['position_side']
+                size = position['current_position_size']
+                entry_price = position['entry_price']
+                current_price = position.get('current_price', entry_price)
+                
+                # Calculate unrealized P&L
+                if side == 'long':
+                    unrealized_pnl = size * (current_price - entry_price)
+                else:  # short
+                    unrealized_pnl = size * (entry_price - current_price)
+                
+                # Find stop loss orders for this position
+                stop_loss_orders = [
+                    order for order in orders 
+                    if order.get('symbol') == symbol and 
+                    order.get('type') == 'stop_loss'
+                ]
+                
+                # Get the most relevant stop loss price
+                stop_loss_price = None
+                if stop_loss_orders:
+                    # Sort by trigger price to find the most conservative stop loss
+                    stop_loss_orders.sort(key=lambda x: float(x.get('triggerPrice', 0)))
+                    if side == 'long':
+                        stop_loss_price = float(stop_loss_orders[0].get('triggerPrice', 0))
+                    else:  # short
+                        stop_loss_price = float(stop_loss_orders[-1].get('triggerPrice', 0))
+                
+                # Calculate risk metrics
+                risk_text_parts.append(f"\n📊 <b>{token}</b>")
+                risk_text_parts.append(f"• Position: {side.upper()} {formatter.format_amount(size, token)} @ {formatter.format_price_with_symbol(entry_price, token)}")
+                risk_text_parts.append(f"• Current: {formatter.format_price_with_symbol(current_price, token)}")
+                
+                # Show P&L
+                pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
+                risk_text_parts.append(f"• P&L: {pnl_emoji} {formatter.format_price_with_symbol(unrealized_pnl)}")
+                
+                # Show stop loss info
+                if stop_loss_price:
+                    risk_amount = abs(size * (stop_loss_price - entry_price))
+                    risk_text_parts.append(f"• Stop Loss: {formatter.format_price_with_symbol(stop_loss_price, token)}")
+                    risk_text_parts.append(f"• Risk Amount: {formatter.format_price_with_symbol(risk_amount)}")
+                else:
+                    risk_text_parts.append("• Stop Loss: ❌ Not set")
+                
+                # Add separator between positions
+                risk_text_parts.append("")
+
+            # Add portfolio-level risk metrics
+            total_position_value = sum(
+                abs(float(pos.get('current_position_size', 0)) * float(pos.get('current_price', 0)))
+                for pos in positions
+            )
+            
+            # Get account balance
+            balance = self.trading_engine.get_balance()
+            if balance:
+                total_balance = float(balance.get('total', 0))
+                if total_balance > 0:
+                    portfolio_risk = (total_position_value / total_balance) * 100
+                    risk_text_parts.append(f"📈 <b>Portfolio Risk</b>")
+                    risk_text_parts.append(f"• Total Position Value: {formatter.format_price_with_symbol(total_position_value)}")
+                    risk_text_parts.append(f"• Portfolio Risk: {portfolio_risk:.1f}% of balance")
+
+            await self._reply(update, "\n".join(risk_text_parts).strip())
+
+        except Exception as e:
+            error_message = f"❌ Error processing risk command: {str(e)}"
+            await self._reply(update, error_message)
+            logger.error(f"Error in risk command: {e}")

+ 115 - 0
src/commands/info/stats.py

@@ -0,0 +1,115 @@
+import logging
+from typing import Dict, Any, List, Optional
+from datetime import datetime, timedelta
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+
+logger = logging.getLogger(__name__)
+
+class StatsCommands(InfoCommandsBase):
+    """Handles all statistics-related commands."""
+
+    async def stats_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /stats command."""
+        try:
+            if not self._is_authorized(update):
+                await self._reply(update, "❌ Unauthorized access.")
+                return
+
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await self._reply(update, "❌ Trading stats not available.")
+                return
+
+            # Get trading stats
+            trading_stats = stats.get_trading_stats()
+            if not trading_stats:
+                await self._reply(update, "❌ Trading statistics not available.")
+                return
+
+            # Format stats text
+            stats_text = "📊 <b>Trading Statistics</b>\n\n"
+
+            # Add total trades
+            total_trades = trading_stats.get('total_trades', 0)
+            stats_text += f"📈 Total Trades: {total_trades}\n"
+
+            # Add winning trades
+            winning_trades = trading_stats.get('winning_trades', 0)
+            win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0
+            stats_text += f"✅ Winning Trades: {winning_trades} ({win_rate:.2f}%)\n"
+
+            # Add losing trades
+            losing_trades = trading_stats.get('losing_trades', 0)
+            loss_rate = (losing_trades / total_trades * 100) if total_trades > 0 else 0
+            stats_text += f"❌ Losing Trades: {losing_trades} ({loss_rate:.2f}%)\n"
+
+            # Add total P&L
+            total_pnl = trading_stats.get('total_pnl', 0.0)
+            pnl_emoji = "🟢" if total_pnl >= 0 else "🔴"
+            stats_text += f"{pnl_emoji} Total P&L: ${total_pnl:,.2f}\n"
+
+            # Add average P&L per trade
+            avg_pnl = total_pnl / total_trades if total_trades > 0 else 0
+            avg_pnl_emoji = "🟢" if avg_pnl >= 0 else "🔴"
+            stats_text += f"{avg_pnl_emoji} Average P&L per Trade: ${avg_pnl:,.2f}\n"
+
+            # Add ROE metrics
+            total_roe = trading_stats.get('total_roe', 0.0)
+            avg_roe = trading_stats.get('average_roe', 0.0)
+            best_roe = trading_stats.get('best_roe', 0.0)
+            worst_roe = trading_stats.get('worst_roe', 0.0)
+            best_roe_token = trading_stats.get('best_roe_token', 'N/A')
+            worst_roe_token = trading_stats.get('worst_roe_token', 'N/A')
+
+            roe_emoji = "🟢" if total_roe >= 0 else "🔴"
+            stats_text += f"{roe_emoji} Total ROE: {total_roe:+.2f}%\n"
+            stats_text += f"{roe_emoji} Average ROE per Trade: {avg_roe:+.2f}%\n"
+            stats_text += f"🏆 Best ROE: {best_roe:+.2f}% ({best_roe_token})\n"
+            stats_text += f"💔 Worst ROE: {worst_roe:+.2f}% ({worst_roe_token})\n"
+
+            # Add largest win and loss
+            largest_win = trading_stats.get('largest_win', 0.0)
+            largest_loss = trading_stats.get('largest_loss', 0.0)
+            stats_text += f"🏆 Largest Win: ${largest_win:,.2f}\n"
+            stats_text += f"💔 Largest Loss: ${largest_loss:,.2f}\n"
+
+            # Add average win and loss
+            avg_win = trading_stats.get('average_win', 0.0)
+            avg_loss = trading_stats.get('average_loss', 0.0)
+            stats_text += f"📈 Average Win: ${avg_win:,.2f}\n"
+            stats_text += f"📉 Average Loss: ${avg_loss:,.2f}\n"
+
+            # Add profit factor
+            profit_factor = trading_stats.get('profit_factor', 0.0)
+            stats_text += f"⚖️ Profit Factor: {profit_factor:.2f}\n"
+
+            # Add time-based stats
+            first_trade_time = trading_stats.get('first_trade_time')
+            if first_trade_time:
+                try:
+                    first_trade = datetime.fromisoformat(first_trade_time.replace('Z', '+00:00'))
+                    trading_duration = datetime.now(first_trade.tzinfo) - first_trade
+                    days = trading_duration.days
+                    hours = trading_duration.seconds // 3600
+                    stats_text += f"⏱️ Trading Duration: {days}d {hours}h\n"
+                except ValueError:
+                    logger.warning(f"Could not parse first_trade_time: {first_trade_time}")
+
+            # Add trades per day
+            if first_trade_time:
+                try:
+                    first_trade = datetime.fromisoformat(first_trade_time.replace('Z', '+00:00'))
+                    trading_duration = datetime.now(first_trade.tzinfo) - first_trade
+                    days = max(trading_duration.days, 1)  # Avoid division by zero
+                    trades_per_day = total_trades / days
+                    stats_text += f"📅 Trades per Day: {trades_per_day:.2f}\n"
+                except ValueError:
+                    pass
+
+            await self._reply(update, stats_text.strip())
+
+        except Exception as e:
+            logger.error(f"Error in stats command: {e}")
+            await self._reply(update, "❌ Error retrieving trading statistics.")

+ 96 - 0
src/commands/info/trades.py

@@ -0,0 +1,96 @@
+import logging
+from typing import Dict, Any, List, Optional
+from datetime import datetime, timedelta
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+
+logger = logging.getLogger(__name__)
+
+class TradesCommands(InfoCommandsBase):
+    """Handles all trade history-related commands."""
+
+    async def trades_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+        """Handle the /trades command."""
+        try:
+            if not self._is_authorized(update):
+                await self._reply(update, "❌ Unauthorized access.")
+                return
+
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await self._reply(update, "❌ Trading stats not available.")
+                return
+
+            # Get recent trades
+            recent_trades = stats.get_recent_trades()
+            if not recent_trades:
+                await self._reply(update, "📭 No recent trades")
+                return
+
+            # Format trades text
+            trades_text = "📜 <b>Recent Trades</b>\n\n"
+
+            for trade in recent_trades:
+                try:
+                    symbol = trade.get('symbol', 'unknown')
+                    base_asset = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
+                    side = trade.get('side', 'unknown').upper()
+                    price = float(trade.get('price', 0))
+                    amount = float(trade.get('amount', 0))
+                    pnl = float(trade.get('pnl', 0))
+                    pnl_percentage = float(trade.get('pnl_percentage', 0))
+                    trade_type = trade.get('trade_type', 'unknown')
+                    trade_time = trade.get('trade_time')
+
+                    # Format trade details
+                    formatter = self._get_formatter()
+                    price_str = formatter.format_price_with_symbol(price, base_asset)
+                    amount_str = formatter.format_amount(amount, base_asset)
+
+                    # Trade header
+                    side_emoji = "🟢" if side == "BUY" else "🔴"
+                    trades_text += f"{side_emoji} <b>{base_asset} {side}</b>\n"
+                    trades_text += f"   📏 Amount: {amount_str} {base_asset}\n"
+                    trades_text += f"   💰 Price: {price_str}\n"
+
+                    # Add P&L info
+                    pnl_emoji = "🟢" if pnl >= 0 else "🔴"
+                    trades_text += f"   {pnl_emoji} P&L: ${pnl:,.2f} ({pnl_percentage:+.2f}%)\n"
+
+                    # Add trade type
+                    type_indicator = ""
+                    if trade.get('trade_lifecycle_id'):
+                        type_indicator = " 🤖"
+                    elif trade_type == 'external':
+                        type_indicator = " 🔄"
+                    trades_text += f"   📝 Type: {trade_type.upper()}{type_indicator}\n"
+
+                    # Add trade time
+                    if trade_time:
+                        try:
+                            trade_datetime = datetime.fromisoformat(trade_time.replace('Z', '+00:00'))
+                            time_diff = datetime.now(trade_datetime.tzinfo) - trade_datetime
+                            if time_diff.days > 0:
+                                time_str = f"{time_diff.days}d ago"
+                            elif time_diff.seconds >= 3600:
+                                time_str = f"{time_diff.seconds // 3600}h ago"
+                            else:
+                                time_str = f"{time_diff.seconds // 60}m ago"
+                            trades_text += f"   ⏰ Time: {time_str}\n"
+                        except ValueError:
+                            logger.warning(f"Could not parse trade_time: {trade_time}")
+
+                    # Add trade ID
+                    trade_id = trade.get('id', 'unknown')
+                    trades_text += f"   🆔 Trade ID: {trade_id[:8]}\n\n"
+
+                except Exception as e:
+                    logger.error(f"Error processing trade {trade.get('symbol', 'unknown')}: {e}")
+                    continue
+
+            await self._reply(update, trades_text.strip())
+
+        except Exception as e:
+            logger.error(f"Error in trades command: {e}")
+            await self._reply(update, "❌ Error retrieving trade history.")

+ 14 - 0
src/commands/info/utils.py

@@ -0,0 +1,14 @@
+import html
+
+def normalize_token_case(token: str) -> str:
+    """
+    Normalize token case: if any characters are already uppercase, keep as-is.
+    Otherwise, convert to uppercase. This handles mixed-case tokens like kPEPE, kBONK.
+    """
+    if any(c.isupper() for c in token):
+        return token
+    else:
+        return token.upper()
+
+def escape_html(text: str) -> str:
+    return html.escape(text)

+ 76 - 0
src/commands/info/weekly.py

@@ -0,0 +1,76 @@
+import logging
+from telegram import Update
+from telegram.ext import ContextTypes
+from .base import InfoCommandsBase
+from src.utils.token_display_formatter import get_formatter
+
+logger = logging.getLogger(__name__)
+
+class WeeklyCommands(InfoCommandsBase):
+    """Handles all weekly performance-related commands."""
+
+    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):
+            return
+
+        try:
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                await self._reply(update, "❌ Could not load trading statistics")
+                return
+
+            weekly_stats_list = stats.get_weekly_stats(10)  # Last 10 weeks
+            formatter = get_formatter()
+
+            if not weekly_stats_list:
+                await self._reply(update, "📅 <b>Weekly Performance</b>\n📭 No weekly performance data available yet.\n💡 Weekly stats are calculated from completed trades. Start trading to see them!")
+                return
+
+            weekly_text_parts = [f"📅 <b>Weekly Performance (Last 10 Weeks)</b>"]
+
+            total_pnl_all_weeks = 0
+            total_trades_all_weeks = 0
+            total_roe_all_weeks = 0
+            trading_weeks_count = 0
+
+            period_lines = []
+            for week_stats_item in weekly_stats_list:
+                if week_stats_item['has_trades']:
+                    pnl_emoji = "🟢" if week_stats_item['pnl'] >= 0 else "🔴"
+                    pnl_str = formatter.format_price_with_symbol(week_stats_item['pnl'])
+                    roe = week_stats_item.get('roe', 0.0)  # Get ROE from stats
+                    roe_str = f"ROE: {roe:+.1f}%" if roe != 0 else ""
+                    period_lines.append(f"📅 <b>{week_stats_item['week_formatted']}</b>: {pnl_emoji} {pnl_str} ({week_stats_item['pnl_pct']:+.1f}%) {roe_str} | Trades: {week_stats_item['trades']}")
+                    total_pnl_all_weeks += week_stats_item['pnl']
+                    total_trades_all_weeks += week_stats_item['trades']
+                    total_roe_all_weeks += roe
+                    trading_weeks_count += 1
+                else:
+                    period_lines.append(f"📅 <b>{week_stats_item['week_formatted']}</b>: 📭 No trading activity")
+
+            if period_lines:
+                weekly_text_parts.append("\n".join(period_lines))
+
+            if trading_weeks_count > 0:
+                avg_weekly_pnl = total_pnl_all_weeks / trading_weeks_count
+                avg_weekly_roe = total_roe_all_weeks / trading_weeks_count
+                avg_pnl_emoji = "🟢" if avg_weekly_pnl >= 0 else "🔴"
+                total_pnl_all_weeks_str = formatter.format_price_with_symbol(total_pnl_all_weeks)
+                avg_weekly_pnl_str = formatter.format_price_with_symbol(avg_weekly_pnl)
+
+                weekly_text_parts.append(f"\n\n📈 <b>Period Summary:</b>")
+                weekly_text_parts.append(f"  Total P&L: {avg_pnl_emoji} {total_pnl_all_weeks_str} | Avg Weekly: {avg_weekly_pnl_str}")
+                weekly_text_parts.append(f"  Total ROE: {total_roe_all_weeks:+.1f}% | Avg Weekly ROE: {avg_weekly_roe:+.1f}%")
+                weekly_text_parts.append(f"  Trading Weeks: {trading_weeks_count}/10 | Total Trades: {total_trades_all_weeks}")
+            else:
+                if not period_lines:
+                    weekly_text_parts = [weekly_text_parts[0]]
+                weekly_text_parts.append("\n\n📉 No trading activity in the last 10 weeks.")
+
+            await self._reply(update, "\n".join(weekly_text_parts).strip())
+
+        except Exception as e:
+            error_message = f"❌ Error processing weekly command: {str(e)}"
+            await self._reply(update, error_message)
+            logger.error(f"Error in weekly command: {e}") 

+ 0 - 1609
src/commands/info_commands.py

@@ -1,1609 +0,0 @@
-#!/usr/bin/env python3
-"""
-Info Commands - Handles information-related Telegram commands.
-"""
-
-import logging
-import html # Added for escaping HTML characters
-from datetime import datetime, timezone, timedelta
-from typing import Optional, Dict, Any, List
-from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
-from telegram.ext import ContextTypes
-
-from src.config.config import Config
-from src.utils.token_display_formatter import format_price_with_symbol, get_formatter
-
-logger = logging.getLogger(__name__)
-
-def _normalize_token_case(token: str) -> str:
-    """
-    Normalize token case: if any characters are already uppercase, keep as-is.
-    Otherwise, convert to uppercase. This handles mixed-case tokens like kPEPE, kBONK.
-    """
-    # Check if any character is already uppercase
-    if any(c.isupper() for c in token):
-        return token  # Keep original case for mixed-case tokens
-    else:
-        return token.upper()  # Convert to uppercase for all-lowercase input
-
-class InfoCommands:
-    """Handles all information-related Telegram commands."""
-    
-    def __init__(self, trading_engine, notification_manager=None):
-        """Initialize with trading engine and notification manager."""
-        self.trading_engine = trading_engine
-        self.notification_manager = notification_manager
-    
-    def _is_authorized(self, update: Update) -> bool:
-        """Check if the chat ID is authorized. Handles direct commands and callbacks."""
-        chat_id = None
-        if update.effective_chat: # For direct commands
-            chat_id = update.effective_chat.id
-        elif update.callback_query and update.callback_query.message: # For callback queries
-            chat_id = update.callback_query.message.chat_id
-        
-        if not chat_id:
-            logger.warning("Could not determine chat_id for authorization in InfoCommands.")
-            return False
-            
-        authorized = str(chat_id) == str(Config.TELEGRAM_CHAT_ID)
-        if not authorized:
-            logger.warning(f"Unauthorized access attempt in InfoCommands by chat_id: {chat_id}")
-        return authorized
-    
-    async def balance_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
-        """Handle the /balance command."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            return
-        
-        reply_method = None
-        if update.callback_query:
-            reply_method = update.callback_query.message.reply_text
-        elif update.message:
-            reply_method = update.message.reply_text
-        else:
-            logger.error("balance_command: Cannot find a method to reply.")
-            await context.bot.send_message(chat_id=Config.TELEGRAM_CHAT_ID, text="Error: Could not determine how to reply.")
-            return
-
-        try:
-            balance = self.trading_engine.get_balance()
-            if balance:
-                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})")
-                
-                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()
-                initial_balance = 0.0
-                pnl = 0.0
-                pnl_percent = 0.0
-                pnl_emoji = "⚪"
-
-                if stats:
-                    basic_stats = stats.get_basic_stats()
-                    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_emoji = "🟢" if pnl >= 0 else "🔴"
-                
-                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)"
-                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')}")
-                
-                final_message = "\n".join(balance_text_parts)
-                
-                await reply_method(text=final_message.strip(), parse_mode='HTML')
-            else:
-                await reply_method(text="❌ Could not fetch balance information", parse_mode='HTML')
-        except Exception as e:
-            logger.error(f"Error in balance command: {e}", exc_info=True)
-            await reply_method(text="❌ Error retrieving balance information.", parse_mode='HTML')
-    
-    async def positions_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
-        """Handle the /positions command."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            return
-        
-        reply_method = None
-        if update.callback_query:
-            reply_method = update.callback_query.message.reply_text
-        elif update.message:
-            reply_method = update.message.reply_text
-        else:
-            logger.error("positions_command: Cannot find a method to reply.")
-            await context.bot.send_message(chat_id=Config.TELEGRAM_CHAT_ID, text="Error: Could not determine how to reply.")
-            return
-        
-        try:
-            stats = self.trading_engine.get_stats()
-            if not stats:
-                await reply_method(text="❌ Trading statistics not available.", parse_mode='HTML')
-                return
-            
-            # Get open positions from unified trades table
-            open_positions = stats.get_open_positions()
-            
-            # Add position count to header
-            position_count = len(open_positions) if open_positions else 0
-            positions_text = f"📈 <b>Open Positions ({position_count})</b>\n\n"
-            
-            if open_positions:
-                total_unrealized = 0
-                total_position_value = 0
-                total_margin_used = 0 
-                
-                # Get exchange orders to detect external stop losses
-                exchange_orders = self.trading_engine.get_orders() or []
-                
-                for position_trade in open_positions:
-                    symbol = position_trade['symbol']
-                    # base_asset is the asset being traded, quote_asset is the settlement currency (usually USDC)
-                    base_asset = symbol.split('/')[0] if '/' in symbol else symbol
-                    # quote_asset = symbol.split('/')[1] if '/' in symbol else "USDC" # Not strictly needed for display here
-
-                    position_side = position_trade['position_side']  # 'long' or 'short'
-                    entry_price = position_trade['entry_price']
-                    current_amount = position_trade['current_position_size'] # This is the size of the position
-                    abs_current_amount = abs(current_amount)
-                    trade_type = position_trade.get('trade_type', 'manual') # Default to manual if not specified
-                    
-                    # Calculate position duration
-                    position_opened_at_str = position_trade.get('position_opened_at')
-                    duration_str = "N/A"
-                    if position_opened_at_str:
-                        try:
-                            opened_at_dt = datetime.fromisoformat(position_opened_at_str)
-                            if opened_at_dt.tzinfo is None: # Ensure timezone aware
-                                opened_at_dt = opened_at_dt.replace(tzinfo=timezone.utc)
-                            now_utc = datetime.now(timezone.utc)
-                            duration = now_utc - opened_at_dt
-                            
-                            days = duration.days
-                            hours, remainder = divmod(duration.seconds, 3600)
-                            minutes, _ = divmod(remainder, 60)
-                            
-                            parts = []
-                            if days > 0:
-                                parts.append(f"{days}d")
-                            if hours > 0:
-                                parts.append(f"{hours}h")
-                            if minutes > 0 or (days == 0 and hours == 0): # Show minutes if primary unit or others are zero
-                                parts.append(f"{minutes}m")
-                            duration_str = " ".join(parts) if parts else "0m"
-                        except ValueError:
-                            logger.warning(f"Could not parse position_opened_at: {position_opened_at_str} for {symbol}")
-                            duration_str = "Error"
-                            
-                    mark_price = position_trade.get('mark_price', entry_price) # Default to entry if not available
-                    
-                    # Calculate unrealized PnL
-                    unrealized_pnl = position_trade.get('unrealized_pnl') # Prefer DB value if up-to-date
-                    if unrealized_pnl is None: # Calculate if not directly available from DB
-                        if position_side == 'long':
-                            unrealized_pnl = (mark_price - entry_price) * abs_current_amount
-                        else:  # Short position
-                            unrealized_pnl = (entry_price - mark_price) * abs_current_amount
-                    unrealized_pnl = unrealized_pnl or 0.0 # Ensure it's not None for calculations
-
-                    # P&L Percentage Calculation (use database data)
-                    pnl_percentage = 0.0
-                    db_pnl_percentage = position_trade.get('unrealized_pnl_percentage')
-                    margin_used = position_trade.get('margin_used')
-
-                    if db_pnl_percentage is not None:
-                        pnl_percentage = db_pnl_percentage 
-                    elif margin_used is not None and margin_used > 0 and unrealized_pnl != 0:
-                        pnl_percentage = (unrealized_pnl / margin_used) * 100
-                    elif entry_price != 0 and abs_current_amount != 0 and unrealized_pnl != 0:
-                        initial_value = entry_price * abs_current_amount
-                        pnl_percentage = (unrealized_pnl / initial_value) * 100
-
-                    # Add to totals
-                    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
-                    
-                    # Add margin to total
-                    if margin_used is not None:
-                        total_margin_used += margin_used
-                    
-                    # --- Position Header Formatting (Emoji, Direction, Leverage) ---
-                    pos_emoji = ""
-                    direction_text = ""
-                    if position_side == 'long':
-                        pos_emoji = "🟢"
-                        direction_text = "LONG"
-                    else:  # Short position
-                        pos_emoji = "🔴"
-                        direction_text = "SHORT"
-                    
-                    leverage = position_trade.get('leverage')
-                    if leverage is not None:
-                        try:
-                            leverage_val = float(leverage)
-                            leverage_str = f"x{leverage_val:.1f}".rstrip('0').rstrip('.') if '.' in f"{leverage_val:.1f}" else f"x{int(leverage_val)}"
-                            direction_text = f"{direction_text} {leverage_str}"
-                        except ValueError:
-                            logger.warning(f"Could not parse leverage value: {leverage} for {symbol}")
-
-                    # --- Format Output String ---
-                    # Get token info for formatting prices
-                    # Assuming get_formatter() is available and provides necessary precision
-                    formatter = get_formatter() # Keep using if it wraps these precisions
-
-                    # Get price precisions
-                    entry_price_str = formatter.format_price_with_symbol(entry_price, base_asset)
-                    mark_price_str = formatter.format_price_with_symbol(mark_price, base_asset)
-
-                    # Get amount precision for position size
-                    # base_precision = int(token_info.get('precision', {}).get('amount', 6) if token_info and token_info.get('precision') else 6) # Old way
-                    # No longer need to fetch token_info separately for base_precision
-                    # The formatter now handles amount precision directly.
-                    size_str = formatter.format_amount(abs_current_amount, base_asset)
-
-                    type_indicator = ""
-                    # Determine type_indicator based on trade_lifecycle_id or trade_type
-                    if position_trade.get('trade_lifecycle_id'): # Primary indicator for bot managed
-                        type_indicator = " 🤖"
-                    elif trade_type == 'external':
-                        type_indicator = " 🔄"
-                    
-                    positions_text += f"{pos_emoji} <b>{base_asset} ({direction_text}){type_indicator}</b>\n"
-                    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"   ⏳ Duration: {duration_str}\n" # Display position duration
-                    
-                    # 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
-                        positions_text += f"   📈 Mark: {mark_price_str}\n"
-                    
-                    pnl_line_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
-                    positions_text += f"   {pnl_line_emoji} P&L: ${unrealized_pnl:,.2f} ({pnl_percentage:+.2f}%)\n"
-                    
-                    # Show exchange-provided risk data if available
-                    if margin_used is not None:
-                        positions_text += f"   💳 Margin Used: ${margin_used:,.2f}\n"
-                    if position_trade.get('liquidation_price') is not None and position_trade.get('liquidation_price') > 0:
-                        liq_price_str = formatter.format_price_with_symbol(position_trade.get('liquidation_price'), base_asset)
-                        positions_text += f"   ⚠️ Liquidation: {liq_price_str}\n"
-                    
-                    # Show stop loss if linked in database
-                    if position_trade.get('stop_loss_price'):
-                        sl_price = position_trade['stop_loss_price']
-                        positions_text += f"   🛑 Stop Loss: {formatter.format_price_with_symbol(sl_price, base_asset)}\n"
-                    
-                    # Show take profit if linked in database
-                    if position_trade.get('take_profit_price'):
-                        tp_price = position_trade['take_profit_price']
-                        positions_text += f"   🎯 Take Profit: {formatter.format_price_with_symbol(tp_price, base_asset)}\n"
-                    
-                    # Detect external/unlinked stop losses for this position
-                    external_sls = []
-                    for order in exchange_orders:
-                        try:
-                            order_symbol = order.get('symbol')
-                            order_side = order.get('side', '').lower()
-                            order_type = order.get('type', '').lower()
-                            order_price = float(order.get('price', 0))
-                            trigger_price = order.get('info', {}).get('triggerPrice')
-                            is_reduce_only = order.get('reduceOnly', False) or order.get('info', {}).get('reduceOnly', False)
-                            order_amount = float(order.get('amount', 0))
-                            
-                            # Check if this order could be a stop loss for this position
-                            if (order_symbol == symbol and is_reduce_only and 
-                                abs(order_amount - abs_current_amount) < 0.01 * abs_current_amount):
-                                
-                                # Get trigger price (prefer triggerPrice over regular price)
-                                sl_trigger_price = 0
-                                if trigger_price:
-                                    try:
-                                        sl_trigger_price = float(trigger_price)
-                                    except (ValueError, TypeError):
-                                        pass
-                                if not sl_trigger_price and order_price > 0:
-                                    sl_trigger_price = order_price
-                                
-                                # Check if it's positioned correctly relative to entry price
-                                is_valid_sl = False
-                                if position_side == 'long' and order_side == 'sell':
-                                    # Long position: SL should be sell order below entry price
-                                    if sl_trigger_price > 0 and sl_trigger_price < entry_price:
-                                        is_valid_sl = True
-                                elif position_side == 'short' and order_side == 'buy':
-                                    # Short position: SL should be buy order above entry price
-                                    if sl_trigger_price > 0 and sl_trigger_price > entry_price:
-                                        is_valid_sl = True
-                                
-                                # Check if it's already linked in database
-                                linked_sl_order_id = position_trade.get('stop_loss_order_id')
-                                is_already_linked = (linked_sl_order_id and linked_sl_order_id == order.get('id'))
-                                
-                                if is_valid_sl and not is_already_linked:
-                                    external_sls.append(sl_trigger_price)
-                                    
-                        except Exception as order_err:
-                            # Skip problematic orders
-                            continue
-                    
-                    # Show external stop losses (unlinked)
-                    for ext_sl_price in external_sls:
-                        positions_text += f"   🛡️ External SL: {formatter.format_price_with_symbol(ext_sl_price, base_asset)}\n"
-
-                    positions_text += f"   🆔 Lifecycle ID: {position_trade['trade_lifecycle_id'][:8]}\n\n"
-                
-                # Portfolio summary
-                portfolio_emoji = "🟢" if total_unrealized >= 0 else "🔴"
-                positions_text += f"💼 <b>Total Portfolio:</b>\n"
-                positions_text += f"   🏦 Total Positions Value: ${total_position_value:,.2f}\n"
-                if total_margin_used > 0:
-                    positions_text += f"   💳 Total Margin Used: ${total_margin_used:,.2f}\n"
-                    leverage_ratio = total_position_value / total_margin_used if total_margin_used > 0 else 1.0
-                    positions_text += f"   ⚖️ Portfolio Leverage: {leverage_ratio:.2f}x\n"
-                positions_text += f"   {portfolio_emoji} Total Unrealized P&L: ${total_unrealized:,.2f}\n"
-                if total_margin_used > 0:
-                    margin_pnl_percentage = (total_unrealized / total_margin_used) * 100
-                    positions_text += f"   📊 Portfolio Return: {margin_pnl_percentage:+.2f}% (on margin)\n"
-                positions_text += "\n"
-                positions_text += f"🤖 <b>Legend:</b> 🤖 Bot-created • 🔄 External/synced • 🛡️ External SL\n"
-                positions_text += f"💡 Use /sl [token] [price] or /tp [token] [price] to set risk management"
-                
-            else:
-                positions_text += "📭 No open positions\n\n"
-                positions_text += "💡 Use /long or /short to open a position"
-            
-            await reply_method(text=positions_text.strip(), parse_mode='HTML')
-        except Exception as e:
-            logger.error(f"Error in positions command: {e}")
-            await reply_method(text="❌ Error retrieving position information.", parse_mode='HTML')
-    
-    async def orders_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
-        """Handle the /orders command."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            return
-        
-        reply_method = None
-        if update.callback_query:
-            reply_method = update.callback_query.message.reply_text
-        elif update.message:
-            reply_method = update.message.reply_text
-        else:
-            logger.error("orders_command: Cannot find a method to reply.")
-            await context.bot.send_message(chat_id=Config.TELEGRAM_CHAT_ID, text="Error: Could not determine how to reply.")
-            return
-        
-        try:
-            orders = self.trading_engine.get_orders()
-            stats = self.trading_engine.get_stats()
-            
-            if orders is not None:
-                if len(orders) > 0:
-                    orders_text = f"📋 <b>Open Orders ({len(orders)})</b>\n\n"
-                    
-                    # Group orders by symbol
-                    orders_by_symbol = {}
-                    # Keep track of parent_bot_order_ref_ids for pending SLs already displayed with an open parent order
-                    displayed_sl_parent_refs = set()
-                    
-                    for order in orders: # Iterate through orders from self.trading_engine.get_orders()
-                        symbol_display_key = order.get('symbol', '').replace('/USDC:USDC', '')
-                        if symbol_display_key not in orders_by_symbol:
-                            orders_by_symbol[symbol_display_key] = []
-                        orders_by_symbol[symbol_display_key].append(order) # Group by display key
-                    
-                    formatter = get_formatter() # Get formatter once
-
-                    for symbol, symbol_orders_list in orders_by_symbol.items():
-                        orders_text += f"📊 <b>{symbol}</b>\n"
-                        
-                        for order_data in symbol_orders_list: # This is an individual exchange order
-                            side = order_data.get('side', '').upper()
-                            amount = float(order_data.get('amount', 0))
-                            price = float(order_data.get('price', 0))
-                            
-                            # Check for trigger price (for stop loss orders)
-                            trigger_price = order_data.get('info', {}).get('triggerPrice')
-                            if trigger_price:
-                                try:
-                                    price = float(trigger_price)
-                                except (ValueError, TypeError):
-                                    pass  # Keep original price if trigger price can't be parsed
-                            
-                            order_type = order_data.get('type', 'unknown').title()
-                            exchange_order_id = order_data.get('id', 'N/A') # Renamed for clarity
-                            
-                            side_emoji = "🟢" if side == "BUY" else "🔴"
-                            
-                            orders_text += f"   {side_emoji} {side} {formatter.format_amount(amount, symbol)} @ {formatter.format_price_with_symbol(price, symbol)}\n"
-                            orders_text += f"   📋 Type: {order_type} | ID: {exchange_order_id}\n"
-                            
-                            # Check for pending SL in the trade lifecycle system
-                            if stats:
-                                # First check the old system for conceptual pending SLs
-                                order_in_db = stats.get_order_by_exchange_id(exchange_order_id)
-                                if order_in_db:
-                                    bot_ref_id = order_in_db.get('bot_order_ref_id')
-                                    if bot_ref_id:
-                                        pending_sls = stats.get_orders_by_status(
-                                            status='pending_activation', 
-                                            order_type_filter='pending_sl_activation',
-                                            parent_bot_order_ref_id=bot_ref_id
-                                        )
-                                        
-                                        if pending_sls:
-                                            sl_order = pending_sls[0]
-                                            sl_price = sl_order.get('price', 0)
-                                            sl_conceptual_side = sl_order.get('side', '').upper()
-                                            
-                                            orders_text += f"   ⏳ Pending SL Activation: {sl_conceptual_side} at {formatter.format_price_with_symbol(sl_price, symbol)}\n"
-                                            orders_text += f"      (Activates after main order fills)\n"
-                                            displayed_sl_parent_refs.add(bot_ref_id)
-                                
-                                # Also check for pending SL in trade lifecycle (new system)
-                                lifecycle_manager = stats.trade_manager
-                                if lifecycle_manager:
-                                    pending_trade = lifecycle_manager.get_lifecycle_by_entry_order_id(exchange_order_id, status='pending')
-                                    if pending_trade and pending_trade.get('stop_loss_price'):
-                                        sl_price = pending_trade['stop_loss_price']
-                                        entry_side = pending_trade.get('side', '').lower()
-                                        sl_side = 'SELL' if entry_side == 'buy' else 'BUY'
-                                        
-                                        orders_text += f"   ⏳ Pending SL: {sl_side} at {formatter.format_price_with_symbol(sl_price, symbol)}\n"
-                                        orders_text += f"      (Activates when order fills)\n"
-                            
-                            orders_text += "\n"
-                    
-                    orders_text += f"💼 <b>Total Open Exchange Orders:</b> {len(orders)}\n"
-                    
-                    # Check for orphaned pending SLs from old system
-                    if stats:
-                        all_pending_sl_activations = stats.get_orders_by_status(
-                            status='pending_activation',
-                            order_type_filter='pending_sl_activation'
-                        )
-                        
-                        orphaned_pending_sls_to_display = []
-                        for sl_order_data in all_pending_sl_activations:
-                            parent_ref = sl_order_data.get('parent_bot_order_ref_id')
-                            if parent_ref not in displayed_sl_parent_refs:
-                                orphaned_pending_sls_to_display.append(sl_order_data)
-                        
-                        if orphaned_pending_sls_to_display:
-                            orders_text += "\n" 
-                            orders_text += "⏳ <b>Pending SL Activations (Entry Order Assumed Filled/Closed)</b>\n\n"
-                            
-                            orphaned_sls_by_symbol_group = {}
-                            for sl_data in orphaned_pending_sls_to_display:
-                                sl_symbol_raw = sl_data.get('symbol', '')
-                                sl_symbol_display_key = sl_symbol_raw.replace('/USDC:USDC', '')
-                                if sl_symbol_display_key not in orphaned_sls_by_symbol_group:
-                                    orphaned_sls_by_symbol_group[sl_symbol_display_key] = []
-                                orphaned_sls_by_symbol_group[sl_symbol_display_key].append(sl_data)
-                            
-                            for sl_sym_key, sl_list_items in orphaned_sls_by_symbol_group.items():
-                                orders_text += f"📊 <b>{sl_sym_key}</b>\n"
-                                for sl_item in sl_list_items:
-                                    sl_price_val = sl_item.get('price', 0)
-                                    sl_side_val = sl_item.get('side', '').upper()
-                                    orders_text += f"   ⏳ Pending SL: {sl_side_val} at {formatter.format_price_with_symbol(sl_price_val, sl_sym_key)}\n"
-                                    orders_text += f"      (Awaiting activation by bot)\n\n"
-                                    
-                            orders_text += f"📦 <b>Total Pending Activations (Entry Filled):</b> {len(orphaned_pending_sls_to_display)}\n"
-
-                    orders_text += f"💡 Use /coo [token] to cancel orders"
-                    
-                else:
-                    orders_text = "📋 <b>Open Orders (0)</b>\n\n"
-                    orders_text += "📭 No open orders\n\n"
-                    
-                    # Check for pending SLs from trade lifecycle even if no exchange orders
-                    if stats and stats.trade_manager:
-                        pending_sl_trades = stats.trade_manager.get_pending_stop_loss_activations()
-                        
-                        if pending_sl_trades:
-                            orders_text += "\n⏳ <b>Pending Stop Loss Activations</b>\n\n"
-                            formatter_for_empty = get_formatter()
-                            
-                            for trade in pending_sl_trades:
-                                symbol_raw = trade.get('symbol', '')
-                                symbol_display = symbol_raw.replace('/USDC:USDC', '')
-                                sl_price = trade.get('stop_loss_price', 0)
-                                entry_side = trade.get('side', '').lower()
-                                sl_side = 'SELL' if entry_side == 'buy' else 'BUY'
-                                lifecycle_id = trade.get('trade_lifecycle_id', '')[:8]
-                                
-                                orders_text += f"📊 <b>{symbol_display}</b>\n"
-                                orders_text += f"   ⏳ Pending SL: {sl_side} at {formatter_for_empty.format_price_with_symbol(sl_price, symbol_display)}\n"
-                                orders_text += f"      (Position opened, awaiting SL activation)\n"
-                                orders_text += f"      Lifecycle: {lifecycle_id}\n\n"
-                            
-                            orders_text += f"📦 <b>Total Pending SL Activations:</b> {len(pending_sl_trades)}\n"
-                        else:
-                            # Check for purely conceptual pending SLs from old system
-                            all_pending_sl_activations_empty = stats.get_orders_by_status(
-                                status='pending_activation',
-                                order_type_filter='pending_sl_activation'
-                            )
-                            if all_pending_sl_activations_empty:
-                                orders_text += "\n⏳ <b>Pending SL Activations (Entry Order Assumed Filled/Closed)</b>\n\n"
-                                formatter_for_empty = get_formatter()
-
-                                orphaned_sls_by_symbol_group_empty = {}
-                                for sl_data_empty in all_pending_sl_activations_empty:
-                                    sl_symbol_raw_empty = sl_data_empty.get('symbol', '')
-                                    sl_symbol_display_key_empty = sl_symbol_raw_empty.replace('/USDC:USDC', '')
-                                    if sl_symbol_display_key_empty not in orphaned_sls_by_symbol_group_empty:
-                                        orphaned_sls_by_symbol_group_empty[sl_symbol_display_key_empty] = []
-                                    orphaned_sls_by_symbol_group_empty[sl_symbol_display_key_empty].append(sl_data_empty)
-                                
-                                for sl_sym_key_empty, sl_list_items_empty in orphaned_sls_by_symbol_group_empty.items():
-                                    orders_text += f"📊 <b>{sl_sym_key_empty}</b>\n"
-                                    for sl_item_empty in sl_list_items_empty:
-                                        sl_price_val_empty = sl_item_empty.get('price', 0)
-                                        sl_side_val_empty = sl_item_empty.get('side', '').upper()
-                                        orders_text += f"   ⏳ Pending SL: {sl_side_val_empty} at {formatter_for_empty.format_price_with_symbol(sl_price_val_empty, sl_sym_key_empty)}\n"
-                                        orders_text += f"      (Awaiting activation by bot)\n\n"
-                                orders_text += f"📦 <b>Total Pending Activations (Entry Filled):</b> {len(all_pending_sl_activations_empty)}\n"
-                            else:
-                                orders_text += "💡 Use /long, /short, /sl, or /tp to create orders"
-                    else:
-                        orders_text += "💡 Use /long, /short, /sl, or /tp to create orders"
-                
-                await reply_method(text=orders_text, parse_mode='HTML')
-            else:
-                await reply_method(text="❌ Could not fetch orders", parse_mode='HTML')
-        except Exception as e:
-            logger.error(f"Error in orders command: {e}")
-            await reply_method(text="❌ Error retrieving open orders.", parse_mode='HTML')
-    
-    async def _format_token_specific_stats_message(self, token_stats_data: Dict[str, Any], token_name: str) -> str:
-        """Format detailed statistics for a specific token."""
-        formatter = get_formatter()
-        
-        if not token_stats_data or token_stats_data.get('summary_total_trades', 0) == 0: # Check summary_total_trades
-            return (
-                f"📊 <b>{token_name} Statistics</b>\\n\\n"
-                f"📭 No trading data found for {token_name}.\\n\\n"
-                f"💡 To trade this token, try commands like:\\n"
-                f"   <code>/long {token_name} 100</code>\\n"
-                f"   <code>/short {token_name} 100</code>"
-            )
-
-        perf_summary = token_stats_data.get('performance_summary', {})
-        open_positions = token_stats_data.get('open_positions', [])
-        
-        parts = [f"📊 <b>{token_name.upper()} Detailed Statistics</b>\\n"]
-
-        # Completed Trades Summary (from token_stats table)
-        parts.append("📈 <b>Completed Trades Summary:</b>")
-        if perf_summary.get('completed_trades', 0) > 0:
-            pnl_emoji = "🟢" if perf_summary.get('total_pnl', 0) >= 0 else "🔴"
-            # Calculate PnL percentage from entry volume
-            entry_vol = perf_summary.get('completed_entry_volume', 0.0)
-            pnl_pct = (perf_summary.get('total_pnl', 0.0) / entry_vol * 100) if entry_vol > 0 else 0.0
-            
-            parts.append(f"• Total Completed: {perf_summary.get('completed_trades', 0)}")
-            parts.append(f"• {pnl_emoji} Realized P&L: {formatter.format_price_with_symbol(perf_summary.get('total_pnl', 0.0))} ({pnl_pct:+.2f}%)")
-            parts.append(f"• Win Rate: {perf_summary.get('win_rate', 0.0):.1f}% ({perf_summary.get('total_wins', 0)}W / {perf_summary.get('total_losses', 0)}L)")
-            parts.append(f"• Profit Factor: {perf_summary.get('profit_factor', 0.0):.2f}")
-            parts.append(f"• Expectancy: {formatter.format_price_with_symbol(perf_summary.get('expectancy', 0.0))}")
-            parts.append(f"• Avg Win: {formatter.format_price_with_symbol(perf_summary.get('avg_win', 0.0))} | Avg Loss: {formatter.format_price_with_symbol(perf_summary.get('avg_loss', 0.0))}")
-            parts.append(f"• Largest Win: {formatter.format_price_with_symbol(perf_summary.get('largest_win', 0.0))} | Largest Loss: {formatter.format_price_with_symbol(perf_summary.get('largest_loss', 0.0))}")
-            parts.append(f"• Entry Volume: {formatter.format_price_with_symbol(perf_summary.get('completed_entry_volume', 0.0))}")
-            parts.append(f"• Exit Volume: {formatter.format_price_with_symbol(perf_summary.get('completed_exit_volume', 0.0))}")
-            parts.append(f"• Average Trade Duration: {perf_summary.get('avg_trade_duration', 'N/A')}")
-            parts.append(f"• Cancelled Cycles: {perf_summary.get('total_cancelled', 0)}")
-        else:
-            parts.append("• No completed trades for this token yet.")
-        parts.append("") # Newline
-
-        # Open Positions for this token
-        parts.append("📉 <b>Current Open Positions:</b>")
-        if open_positions:
-            total_open_unrealized_pnl = token_stats_data.get('summary_total_unrealized_pnl', 0.0)
-            open_pnl_emoji = "🟢" if total_open_unrealized_pnl >= 0 else "🔴"
-            
-            for pos in open_positions:
-                pos_side_emoji = "🟢" if pos.get('side') == 'long' else "🔴"
-                pos_pnl_emoji = "🟢" if pos.get('unrealized_pnl', 0) >= 0 else "🔴"
-                opened_at_str = "N/A"
-                if pos.get('opened_at'):
-                    try:
-                        opened_at_dt = datetime.fromisoformat(pos['opened_at'])
-                        opened_at_str = opened_at_dt.strftime('%Y-%m-%d %H:%M')
-                    except:
-                        pass # Keep N/A
-                
-                parts.append(f"• {pos_side_emoji} {pos.get('side', '').upper()} {formatter.format_amount(abs(pos.get('amount',0)), token_name)} {token_name}")
-                parts.append(f"    Entry: {formatter.format_price_with_symbol(pos.get('entry_price',0), token_name)} | Mark: {formatter.format_price_with_symbol(pos.get('mark_price',0), token_name)}")
-                parts.append(f"    {pos_pnl_emoji} Unrealized P&L: {formatter.format_price_with_symbol(pos.get('unrealized_pnl',0))}")
-                parts.append(f"    Opened: {opened_at_str} | ID: ...{pos.get('lifecycle_id', '')[-6:]}")
-            parts.append(f"  {open_pnl_emoji} <b>Total Open P&L: {formatter.format_price_with_symbol(total_open_unrealized_pnl)}</b>")
-        else:
-            parts.append("• No open positions for this token.")
-        parts.append("") # Newline
-
-        parts.append(f"📋 Open Orders (Exchange): {token_stats_data.get('current_open_orders_count', 0)}")
-        parts.append(f"💡 Use <code>/performance {token_name}</code> for another view including recent trades.")
-        
-        return "\n".join(parts)
-
-    async def stats_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
-        """Handle the /stats command. Shows overall stats or stats for a specific token."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            return
-        
-        reply_method = None
-        if update.callback_query:
-            reply_method = update.callback_query.message.reply_text
-        elif update.message:
-            reply_method = update.message.reply_text
-        else:
-            logger.error("stats_command: Cannot find a method to reply.")
-            # Attempt to send to context.bot.send_message if no direct reply method.
-            # It's possible this could be from a button click where original message is gone.
-            target_chat_id = chat_id if chat_id else Config.TELEGRAM_CHAT_ID
-            await context.bot.send_message(chat_id=target_chat_id, text="Error: Could not determine how to reply for /stats command.")
-            return
-
-        try:
-            stats_manager = self.trading_engine.get_stats()
-            if not stats_manager:
-                await reply_method(text="❌ Could not load trading statistics manager.", parse_mode='HTML')
-                return
-
-            if context.args and len(context.args) > 0:
-                # Token-specific stats
-                token_name_arg = _normalize_token_case(context.args[0])
-                
-                # Use the centralized formatting method from TradingStats
-                stats_message = stats_manager.format_token_stats_message(token_name_arg)
-                await reply_method(text=stats_message, parse_mode='HTML')
-            else:
-                # Overall stats
-                balance = self.trading_engine.get_balance()
-                current_balance = 0
-                if balance and balance.get('total') and 'USDC' in balance['total']:
-                    current_balance = float(balance['total']['USDC'])
-                
-                stats_message = stats_manager.format_stats_message(current_balance)
-                await reply_method(text=stats_message, parse_mode='HTML')
-                
-        except Exception as e:
-            logger.error(f"Error in stats command: {e}", exc_info=True)
-            await reply_method(text="❌ Error retrieving statistics.", parse_mode='HTML')
-    
-    async def trades_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
-        """Handle the /trades command - Show recent trade history."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            return
-            
-        reply_method = None
-        if update.callback_query:
-            reply_method = update.callback_query.message.reply_text
-        elif update.message:
-            reply_method = update.message.reply_text
-        else:
-            logger.error("trades_command: Cannot find a method to reply.")
-            # If it's a button click, the user won't see this if it's sent to Config.TELEGRAM_CHAT_ID
-            # unless that's the same chat.
-            # Consider editing the original message for callbacks if a reply_method can't be found.
-            # For now, let's try to send to the original chat if possible.
-            target_chat_id_for_error = chat_id if chat_id else Config.TELEGRAM_CHAT_ID
-            await context.bot.send_message(chat_id=target_chat_id_for_error, text="Error: Could not determine how to reply for /trades command.")
-            return
-            
-        try:
-            stats = self.trading_engine.get_stats()
-            if not stats:
-                await reply_method("❌ Trading statistics not available.", parse_mode='HTML')
-                return
-            
-            # Get recent trades (limit to last 20)
-            recent_trades = stats.get_recent_trades(limit=20)
-            
-            if not recent_trades:
-                await reply_method("📊 <b>No trades found.</b>", parse_mode='HTML')
-                return
-            
-            message = "📈 <b>Recent Trades (Last 20)</b>\n\n"
-            
-            for trade in recent_trades:
-                symbol = trade['symbol']
-                token = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0] if ':' in symbol else symbol
-                side = trade['side'].upper()
-                amount = trade['amount']
-                price = trade['price']
-                timestamp = trade['timestamp']
-                pnl = trade.get('realized_pnl', 0)
-                
-                # Format timestamp
-                try:
-                    from datetime import datetime
-                    dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
-                    time_str = dt.strftime('%m/%d %H:%M')
-                except:
-                    time_str = "Unknown"
-                
-                # PnL emoji and formatting
-                if pnl > 0:
-                    pnl_emoji = "🟢"
-                    pnl_str = f"+${pnl:.2f}"
-                elif pnl < 0:
-                    pnl_emoji = "🔴"
-                    pnl_str = f"${pnl:.2f}"
-                else:
-                    pnl_emoji = "⚪"
-                    pnl_str = "$0.00"
-                
-                side_emoji = "🟢" if side == 'BUY' else "🔴"
-                
-                message += f"{side_emoji} <b>{side}</b> {amount} {token} @ ${price:,.2f}\n"
-                message += f"   {pnl_emoji} P&L: {pnl_str} | {time_str}\n\n"
-            
-            await reply_method(message, parse_mode='HTML')
-            
-        except Exception as e:
-            logger.error(f"Error in trades command: {e}")
-            await reply_method("❌ Error retrieving trade history.", parse_mode='HTML')
-    
-    async def market_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
-        """Handle the /market command."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            return
-        
-        # Get token from arguments or use default
-        if context.args and len(context.args) > 0:
-            token = _normalize_token_case(context.args[0])
-        else:
-            token = Config.DEFAULT_TRADING_TOKEN
-        
-        symbol = f"{token}/USDC:USDC"
-        market_data = self.trading_engine.get_market_data(symbol)
-        
-        if market_data:
-            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)
-            bid_price = float(ticker.get('bid', 0.0) or 0.0)
-            ask_price = float(ticker.get('ask', 0.0) or 0.0)
-            raw_base_volume = ticker.get('baseVolume')
-            volume_24h = float(raw_base_volume if raw_base_volume is not None else 0.0)
-            raw_change_24h = ticker.get('change')
-            change_24h = float(raw_change_24h if raw_change_24h is not None else 0.0)
-            raw_percentage = ticker.get('percentage')
-            change_percent = float(raw_percentage if raw_percentage is not None else 0.0)
-            high_24h = float(ticker.get('high', 0.0) or 0.0)
-            low_24h = float(ticker.get('low', 0.0) or 0.0)
-            
-            # Market direction emoji
-            trend_emoji = "🟢" if change_24h >= 0 else "🔴"
-            
-            # Format prices with proper precision for this token
-            formatter = get_formatter()
-            current_price_str = formatter.format_price_with_symbol(current_price, token)
-            bid_price_str = formatter.format_price_with_symbol(bid_price, token)
-            ask_price_str = formatter.format_price_with_symbol(ask_price, token)
-            spread_str = formatter.format_price_with_symbol(ask_price - bid_price, token)
-            high_24h_str = formatter.format_price_with_symbol(high_24h, token)
-            low_24h_str = formatter.format_price_with_symbol(low_24h, token)
-            change_24h_str = formatter.format_price_with_symbol(change_24h, token)
-            
-            market_text = f"""
-📊 <b>{token} Market Data</b>
-
-💰 <b>Price Information:</b>
-   💵 Current: {current_price_str}
-   🟢 Bid: {bid_price_str}
-   🔴 Ask: {ask_price_str}
-   📊 Spread: {spread_str}
-
-📈 <b>24h Statistics:</b>
-   {trend_emoji} Change: {change_24h_str} ({change_percent:+.2f}%)
-   🔝 High: {high_24h_str}
-   🔻 Low: {low_24h_str}
-   📊 Volume: {volume_24h:,.2f} {token}
-
-⏰ <b>Last Updated:</b> {datetime.now().strftime('%H:%M:%S')}
-            """
-            
-            await context.bot.send_message(chat_id=chat_id, text=market_text.strip(), parse_mode='HTML')
-        else:
-            await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch market data for {token}")
-    
-    async def price_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
-        """Handle the /price command."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            return
-        
-        # Get token from arguments or use default
-        if context.args and len(context.args) > 0:
-            token = _normalize_token_case(context.args[0])
-        else:
-            token = Config.DEFAULT_TRADING_TOKEN
-        
-        symbol = f"{token}/USDC:USDC"
-        market_data = self.trading_engine.get_market_data(symbol)
-        
-        if market_data:
-            ticker = market_data.get('ticker', {})
-            current_price = float(ticker.get('last', 0.0) or 0.0)
-            raw_change_24h = ticker.get('change')
-            change_24h = float(raw_change_24h if raw_change_24h is not None else 0.0)
-            raw_percentage = ticker.get('percentage')
-            change_percent = float(raw_percentage if raw_percentage is not None else 0.0)
-            
-            # Price direction emoji
-            trend_emoji = "🟢" if change_24h >= 0 else "🔴"
-            
-            # Format prices with proper precision for this token
-            formatter = get_formatter()
-            current_price_str = formatter.format_price_with_symbol(current_price, token)
-            change_24h_str = formatter.format_price_with_symbol(change_24h, token)
-            
-            price_text = f"""
-💵 <b>{token} Price</b>
-
-💰 {current_price_str}
-{trend_emoji} {change_percent:+.2f}% ({change_24h_str})
-
-⏰ {datetime.now().strftime('%H:%M:%S')}
-            """
-            
-            await context.bot.send_message(chat_id=chat_id, text=price_text.strip(), parse_mode='HTML')
-        else:
-            await context.bot.send_message(chat_id=chat_id, 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."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            return
-        
-        try:
-            # Check if specific token is requested
-            if context.args and len(context.args) >= 1:
-                # Detailed performance for specific token
-                token = _normalize_token_case(context.args[0])
-                await self._show_token_performance(chat_id, token, context)
-            else:
-                # Show token performance ranking
-                await self._show_performance_ranking(chat_id, context)
-                
-        except Exception as e:
-            error_message = f"❌ Error processing performance command: {str(e)}"
-            await context.bot.send_message(chat_id=chat_id, text=error_message)
-            logger.error(f"Error in performance command: {e}")
-
-    async def _show_performance_ranking(self, chat_id: str, context: ContextTypes.DEFAULT_TYPE):
-        """Show token performance ranking (compressed view)."""
-        stats = self.trading_engine.get_stats()
-        if not stats:
-            await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
-            return
-            
-        token_performance = stats.get_token_performance()
-        
-        if not token_performance:
-            await context.bot.send_message(chat_id=chat_id, 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
-        
-        # Tokens are already sorted by ROE from the performance calculator
-        sorted_tokens = token_performance
-        
-        performance_text = "🏆 <b>Token Performance Ranking</b>\n"
-        performance_text += "<i>Ordered by Total P&L (Dollar Amount)</i>\n\n"
-        
-        # Add ranking with emojis
-        for i, 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}"
-            
-            total_pnl = stats_data.get('total_realized_pnl', 0)
-            roe_percentage = stats_data.get('roe_percentage', 0)
-            
-            # ROE emoji (shows performance efficiency)
-            roe_emoji = "🟢" if roe_percentage >= 0 else "🔴"
-            
-            # P&L emoji (primary ranking 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 - show both P&L (primary) and ROE (efficiency)
-            performance_text += f"{rank_emoji} <b>{token_name}</b>\n"
-            performance_text += f"   {pnl_emoji} P&L: ${total_pnl:,.2f} | {roe_emoji} ROE: {roe_percentage:+.2f}%\n"
-            performance_text += f"   📊 Trades: {completed_trades}"
-            
-            # Add win rate if there are completed trades
-            if completed_trades > 0:
-                win_rate = stats_data.get('win_rate', 0)
-                performance_text += f" | Win: {win_rate:.0f}%"
-            
-            performance_text += "\n\n"
-        
-        # Add summary
-        total_pnl = sum(stats_data.get('total_realized_pnl', 0) for stats_data in token_performance)
-        total_trades = sum(stats_data.get('total_completed_cycles', 0) for stats_data in token_performance)
-        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 context.bot.send_message(chat_id=chat_id, text=performance_text.strip(), parse_mode='HTML')
-
-    async def _show_token_performance(self, chat_id: str, token: str, context: ContextTypes.DEFAULT_TYPE):
-        """Show detailed performance for a specific token."""
-        stats = self.trading_engine.get_stats()
-        if not stats:
-            await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
-            return
-            
-        token_stats = stats.get_token_detailed_stats(token)
-        formatter = get_formatter() # Get the formatter instance
-        
-        # Check if token has any data
-        if token_stats.get('summary_total_trades', 0) == 0:
-            await context.bot.send_message(chat_id=chat_id, 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)
-        perf_summary = token_stats.get('performance_summary', {})
-        if 'message' in token_stats and perf_summary.get('completed_trades', 0) == 0:
-            total_volume_str = formatter.format_price_with_symbol(token_stats.get('total_volume', 0), quote_asset=Config.QUOTE_CURRENCY) # Assuming total volume is in quote currency
-            await context.bot.send_message(chat_id=chat_id, 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.get('summary_total_trades', 0)}\n"
-                f"• Buy Orders: {token_stats.get('buy_trades', 0)}\n"
-                f"• Sell Orders: {token_stats.get('sell_trades', 0)}\n"
-                f"• Volume: {total_volume_str}\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 - use performance_summary data
-        perf_summary = token_stats.get('performance_summary', {})
-        pnl_emoji = "🟢" if perf_summary.get('total_pnl', 0) >= 0 else "🔴"
-        
-        total_pnl_str = formatter.format_price_with_symbol(perf_summary.get('total_pnl', 0))
-        completed_volume_str = formatter.format_price_with_symbol(perf_summary.get('completed_entry_volume', 0))
-        expectancy_str = formatter.format_price_with_symbol(perf_summary.get('expectancy', 0))
-        largest_win_str = formatter.format_price_with_symbol(perf_summary.get('largest_win', 0))
-        largest_loss_str = formatter.format_price_with_symbol(perf_summary.get('largest_loss', 0))
-        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 ROE (Return on Equity)
-        entry_vol = perf_summary.get('completed_entry_volume', 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>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}
-
-📊 <b>Trading Activity:</b>
-• Total Trades: {token_stats.get('summary_total_trades', 0)}
-• Completed: {perf_summary.get('completed_trades', 0)}
-• Buy Orders: {token_stats.get('buy_trades', 0)}
-• Sell Orders: {token_stats.get('sell_trades', 0)}
-
-🏆 <b>Performance Metrics:</b>
-• Win Rate: {perf_summary.get('win_rate', 0):.1f}%
-• Profit Factor: {perf_summary.get('profit_factor', 0):.2f}
-• Wins: {perf_summary.get('total_wins', 0)} | Losses: {perf_summary.get('total_losses', 0)}
-
-💡 <b>Best/Worst:</b>
-• Largest Win: {largest_win_str}
-• Largest Loss: {largest_loss_str}
-• Avg Win: {avg_win_str}
-• Avg Loss: {avg_loss_str}
-        """
-        
-        # 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 "🔴"
-                
-                # trade_symbol is required for format_price and format_amount
-                trade_symbol = trade.get('symbol', token) # Fallback to token if symbol not in trade dict
-                trade_base_asset = trade_symbol.split('/')[0] if '/' in trade_symbol else trade_symbol
-                
-                # Formatting trade value. Assuming 'value' is in quote currency.
-                trade_value_str = formatter.format_price_with_symbol(trade.get('value', 0))
-                
-                pnl_display_str = ""
-                if trade.get('pnl', 0) != 0:
-                    trade_pnl_str = formatter.format_price_with_symbol(trade.get('pnl', 0))
-                    pnl_display_str = f" | P&L: {trade_pnl_str}"
-                
-                performance_text += f"• {side_emoji} {trade['side'].upper()} {trade_value_str} @ {trade_time}{pnl_display_str}\n"
-        
-        performance_text += f"\n🔄 Use <code>/performance</code> to see all token rankings"
-        
-        await context.bot.send_message(chat_id=chat_id, 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."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            return
-        
-        try:
-            stats = self.trading_engine.get_stats()
-            if not stats:
-                await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
-                return
-                
-            daily_stats = stats.get_daily_stats(10)
-            formatter = get_formatter() # Get formatter
-            
-            if not daily_stats:
-                await context.bot.send_message(chat_id=chat_id, text=
-                    "📅 <b>Daily Performance</b>\n"
-                    "📭 No daily performance data available yet.\n"
-                    "💡 Daily stats are calculated from completed trades. Start trading to see them!",
-                    parse_mode='HTML'
-                )
-                return
-            
-            daily_text_parts = [f"📅 <b>Daily Performance (Last 10 Days)</b>"]
-            
-            total_pnl_all_days = 0
-            total_trades_all_days = 0
-            trading_days_count = 0
-            
-            period_lines = []
-            for day_stats_item in daily_stats:
-                if day_stats_item['has_trades']:
-                    pnl_emoji = "🟢" if day_stats_item['pnl'] >= 0 else "🔴"
-                    pnl_str = formatter.format_price_with_symbol(day_stats_item['pnl'])
-                    period_lines.append(f"📊 <b>{day_stats_item['date_formatted']}</b>: {pnl_emoji} {pnl_str} ({day_stats_item['pnl_pct']:+.1f}%) | Trades: {day_stats_item['trades']}")
-                    total_pnl_all_days += day_stats_item['pnl']
-                    total_trades_all_days += day_stats_item['trades']
-                    trading_days_count += 1
-                else:
-                    period_lines.append(f"📊 <b>{day_stats_item['date_formatted']}</b>: 📭 No trading activity")
-            
-            if period_lines: # Add collected period lines if any
-                daily_text_parts.append("\n".join(period_lines))
-
-            if trading_days_count > 0:
-                avg_daily_pnl = total_pnl_all_days / trading_days_count
-                avg_pnl_emoji = "🟢" if avg_daily_pnl >= 0 else "🔴"
-                total_pnl_all_days_str = formatter.format_price_with_symbol(total_pnl_all_days)
-                avg_daily_pnl_str = formatter.format_price_with_symbol(avg_daily_pnl)
-                
-                daily_text_parts.append(f"\n\n📈 <b>Period Summary:</b>")
-                daily_text_parts.append(f"  Total P&L: {avg_pnl_emoji} {total_pnl_all_days_str} | Avg Daily: {avg_daily_pnl_str}")
-                daily_text_parts.append(f"  Trading Days: {trading_days_count}/10 | Total Trades: {total_trades_all_days}")
-            else:
-                if not period_lines: # If there were no stat items at all, the header is the only part
-                    daily_text_parts = [daily_text_parts[0]] # Keep only the title
-                daily_text_parts.append("\n\n📉 No trading activity in the last 10 days.")
-
-            await context.bot.send_message(chat_id=chat_id, text="\n".join(daily_text_parts).strip(), parse_mode='HTML')
-                
-        except Exception as e:
-            error_message = f"❌ Error processing daily command: {str(e)}"
-            await context.bot.send_message(chat_id=chat_id, 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."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            return
-        
-        try:
-            stats = self.trading_engine.get_stats()
-            if not stats:
-                await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
-                return
-                
-            weekly_stats_list = stats.get_weekly_stats(10) # Renamed variable
-            formatter = get_formatter() # Get formatter
-            
-            if not weekly_stats_list:
-                await context.bot.send_message(chat_id=chat_id, text=
-                    "📊 <b>Weekly Performance</b>\n"
-                    "📭 No weekly performance data available yet.\n"
-                    "💡 Weekly stats are calculated from completed trades. Start trading to see them!",
-                    parse_mode='HTML'
-                )
-                return
-            
-            weekly_text_parts = [f"📊 <b>Weekly Performance (Last 10 Weeks)</b>"]
-            
-            total_pnl_all_weeks = 0
-            total_trades_all_weeks = 0
-            trading_weeks_count = 0
-            
-            period_lines = []
-            for week_stats_item in weekly_stats_list:
-                if week_stats_item['has_trades']:
-                    pnl_emoji = "🟢" if week_stats_item['pnl'] >= 0 else "🔴"
-                    pnl_str = formatter.format_price_with_symbol(week_stats_item['pnl'])
-                    period_lines.append(f"📈 <b>{week_stats_item['week_formatted']}</b>: {pnl_emoji} {pnl_str} ({week_stats_item['pnl_pct']:+.1f}%) | Trades: {week_stats_item['trades']}")
-                    total_pnl_all_weeks += week_stats_item['pnl']
-                    total_trades_all_weeks += week_stats_item['trades']
-                    trading_weeks_count += 1
-                else:
-                    period_lines.append(f"📈 <b>{week_stats_item['week_formatted']}</b>: 📭 No trading activity")
-
-            if period_lines:
-                weekly_text_parts.append("\n".join(period_lines))
-            
-            if trading_weeks_count > 0:
-                avg_weekly_pnl = total_pnl_all_weeks / trading_weeks_count
-                avg_pnl_emoji = "🟢" if avg_weekly_pnl >= 0 else "🔴"
-                total_pnl_all_weeks_str = formatter.format_price_with_symbol(total_pnl_all_weeks)
-                avg_weekly_pnl_str = formatter.format_price_with_symbol(avg_weekly_pnl)
-
-                weekly_text_parts.append(f"\n\n📅 <b>Period Summary:</b>")
-                weekly_text_parts.append(f"  Total P&L: {avg_pnl_emoji} {total_pnl_all_weeks_str} | Avg Weekly: {avg_weekly_pnl_str}")
-                weekly_text_parts.append(f"  Trading Weeks: {trading_weeks_count}/10 | Total Trades: {total_trades_all_weeks}")
-            else:
-                if not period_lines:
-                    weekly_text_parts = [weekly_text_parts[0]]
-                weekly_text_parts.append("\n\n📉 No trading activity in the last 10 weeks.")
-            
-            await context.bot.send_message(chat_id=chat_id, text="\n".join(weekly_text_parts).strip(), parse_mode='HTML')
-                
-        except Exception as e:
-            error_message = f"❌ Error processing weekly command: {str(e)}"
-            await context.bot.send_message(chat_id=chat_id, 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."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            return
-        
-        try:
-            stats = self.trading_engine.get_stats()
-            if not stats:
-                await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
-                return
-                
-            monthly_stats_list = stats.get_monthly_stats(12) # Renamed variable, 12 months
-            formatter = get_formatter() # Get formatter
-            
-            if not monthly_stats_list:
-                await context.bot.send_message(chat_id=chat_id, text=
-                    "🗓️ <b>Monthly Performance</b>\n"
-                    "📭 No monthly performance data available yet.\n"
-                    "💡 Monthly stats are calculated from completed trades. Start trading to see them!",
-                    parse_mode='HTML'
-                )
-                return
-            
-            monthly_text_parts = [f"🗓️ <b>Monthly Performance (Last 12 Months)</b>"]
-            
-            total_pnl_all_months = 0
-            total_trades_all_months = 0
-            trading_months_count = 0
-            
-            period_lines = []
-            for month_stats_item in monthly_stats_list:
-                if month_stats_item['has_trades']:
-                    pnl_emoji = "🟢" if month_stats_item['pnl'] >= 0 else "🔴"
-                    pnl_str = formatter.format_price_with_symbol(month_stats_item['pnl'])
-                    period_lines.append(f"📅 <b>{month_stats_item['month_formatted']}</b>: {pnl_emoji} {pnl_str} ({month_stats_item['pnl_pct']:+.1f}%) | Trades: {month_stats_item['trades']}")
-                    total_pnl_all_months += month_stats_item['pnl']
-                    total_trades_all_months += month_stats_item['trades']
-                    trading_months_count += 1
-                else:
-                    period_lines.append(f"📅 <b>{month_stats_item['month_formatted']}</b>: 📭 No trading activity")
-
-            if period_lines:
-                monthly_text_parts.append("\n".join(period_lines))
-            
-            if trading_months_count > 0:
-                avg_monthly_pnl = total_pnl_all_months / trading_months_count
-                avg_pnl_emoji = "🟢" if avg_monthly_pnl >= 0 else "🔴"
-                total_pnl_all_months_str = formatter.format_price_with_symbol(total_pnl_all_months)
-                avg_monthly_pnl_str = formatter.format_price_with_symbol(avg_monthly_pnl)
-                
-                monthly_text_parts.append(f"\n\n📈 <b>Period Summary:</b>")
-                monthly_text_parts.append(f"  Total P&L: {avg_pnl_emoji} {total_pnl_all_months_str} | Avg Monthly: {avg_monthly_pnl_str}")
-                monthly_text_parts.append(f"  Trading Months: {trading_months_count}/12 | Total Trades: {total_trades_all_months}")
-            else:
-                if not period_lines:
-                    monthly_text_parts = [monthly_text_parts[0]]
-                monthly_text_parts.append("\n\n📉 No trading activity in the last 12 months.")
-            
-            await context.bot.send_message(chat_id=chat_id, text="\n".join(monthly_text_parts).strip(), parse_mode='HTML')
-                
-        except Exception as e:
-            error_message = f"❌ Error processing monthly command: {str(e)}"
-            await context.bot.send_message(chat_id=chat_id, 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."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            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 context.bot.send_message(chat_id=chat_id, 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 context.bot.send_message(chat_id=chat_id, text=
-                    "📊 <b>Risk Analysis</b>\\n\\n"
-                    "📭 <b>Insufficient Data</b>\\n\\n"
-                    f"• Current completed trades: {html.escape(str(basic_stats['completed_trades']))}\\n"
-                    f"• Required for risk analysis: 2+ trades\\n"
-                    f"• Daily balance snapshots: {html.escape(str(stats.get_daily_balance_record_count()))}\\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
-            
-            # Get risk metric values with safe defaults
-            sharpe_ratio = risk_metrics.get('sharpe_ratio')
-            max_drawdown_pct = risk_metrics.get('max_drawdown_live_percentage', 0.0)
-            
-            # Format values safely
-            sharpe_str = f"{sharpe_ratio:.3f}" if sharpe_ratio is not None else "N/A"
-            
-            # Format the risk analysis message
-            risk_text = f"""
-📊 <b>Risk Analysis & Advanced Metrics</b>
-
-🎯 <b>Risk-Adjusted Performance:</b>
-• Sharpe Ratio: {sharpe_str}
-• Profit Factor: {risk_metrics.get('profit_factor', 0):.2f}
-• Win Rate: {risk_metrics.get('win_rate', 0):.1f}%
-
-📉 <b>Drawdown Analysis:</b>
-• Maximum Drawdown: {max_drawdown_pct:.2f}%
-• Max Consecutive Losses: {risk_metrics.get('max_consecutive_losses', 0)}
-
-💰 <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: {html.escape(str(basic_stats['days_active']))}
-
-📊 <b>Risk Interpretation:</b>
-"""
-            
-            # Add interpretive guidance
-            if sharpe_ratio is not None:
-                if sharpe_ratio > 2.0:
-                    risk_text += "• 🟢 <b>Excellent</b> risk-adjusted returns (Sharpe &gt; 2.0)\n"
-                elif sharpe_ratio > 1.0:
-                    risk_text += "• 🟡 <b>Good</b> risk-adjusted returns (Sharpe &gt; 1.0)\n"
-                elif sharpe_ratio > 0.5:
-                    risk_text += "• 🟠 <b>Moderate</b> risk-adjusted returns (Sharpe &gt; 0.5)\n"
-                elif sharpe_ratio > 0:
-                    risk_text += "• 🔴 <b>Poor</b> risk-adjusted returns (Sharpe &gt; 0)\n"
-                else:
-                    risk_text += "• ⚫ <b>Negative</b> risk-adjusted returns (Sharpe &lt; 0)\n"
-            else:
-                risk_text += "• ⚪ <b>Insufficient data</b> for Sharpe ratio calculation\n"
-            
-            if max_drawdown_pct < 5:
-                risk_text += "• 🟢 <b>Low</b> maximum drawdown (&lt; 5%)\n"
-            elif max_drawdown_pct < 15:
-                risk_text += "• 🟡 <b>Moderate</b> maximum drawdown (&lt; 15%)\n"
-            elif max_drawdown_pct < 30:
-                risk_text += "• 🟠 <b>High</b> maximum drawdown (&lt; 30%)\n"
-            else:
-                risk_text += "• 🔴 <b>Very High</b> maximum drawdown (&gt; 30%)\n"
-            
-            # Add profit factor interpretation
-            profit_factor = risk_metrics.get('profit_factor', 0)
-            if profit_factor > 2.0:
-                risk_text += "• 🟢 <b>Excellent</b> profit factor (&gt; 2.0)\n"
-            elif profit_factor > 1.5:
-                risk_text += "• 🟡 <b>Good</b> profit factor (&gt; 1.5)\n"
-            elif profit_factor > 1.0:
-                risk_text += "• 🟠 <b>Profitable</b> but low profit factor (&gt; 1.0)\n"
-            else:
-                risk_text += "• 🔴 <b>Unprofitable</b> trading strategy (&lt; 1.0)\n"
-            
-            risk_text += f"""
-
-💡 <b>Risk Definitions:</b>
-• <b>Sharpe Ratio:</b> Risk-adjusted return (excess return / volatility)
-• <b>Profit Factor:</b> Total winning trades / Total losing trades
-• <b>Win Rate:</b> Percentage of profitable trades
-• <b>Max Drawdown:</b> Largest peak-to-trough decline
-• <b>Max Consecutive Losses:</b> Longest streak of losing trades
-
-📈 <b>Data Based On:</b>
-• Completed Trades: {html.escape(str(basic_stats['completed_trades']))}
-• Trading Period: {html.escape(str(basic_stats['days_active']))} days
-
-🔄 Use /stats for trading performance metrics
-            """
-            
-            await context.bot.send_message(chat_id=chat_id, text=risk_text.strip(), parse_mode='HTML')
-            
-        except Exception as e:
-            error_message = f"❌ Error processing risk command: {str(e)}"
-            await context.bot.send_message(chat_id=chat_id, 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."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            return
-        
-        try:
-            stats = self.trading_engine.get_stats()
-            if not stats:
-                await context.bot.send_message(chat_id=chat_id, 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 context.bot.send_message(chat_id=chat_id, 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 context.bot.send_message(chat_id=chat_id, text=adjustments_text.strip(), parse_mode='HTML')
-            
-        except Exception as e:
-            error_message = f"❌ Error processing balance adjustments command: {str(e)}"
-            await context.bot.send_message(chat_id=chat_id, 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."""
-        chat_id = update.effective_chat.id
-        if not self._is_authorized(update):
-            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!
-        """
-        
-        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("🎲 Risk", callback_data="/risk"),
-                InlineKeyboardButton("⚙️ Help", callback_data="/help")
-            ]
-        ]
-        reply_markup = InlineKeyboardMarkup(keyboard)
-        
-        await context.bot.send_message(chat_id=chat_id, text=commands_text, parse_mode='HTML', reply_markup=reply_markup)
-
-    async def _estimate_entry_price_for_orphaned_position(self, symbol: str, contracts: float) -> float:
-        """Estimate entry price for an orphaned position by checking recent fills and market data."""
-        try:
-            # Method 1: Check recent fills from the exchange
-            recent_fills = self.trading_engine.get_recent_fills()
-            if recent_fills:
-                # Look for recent fills for this symbol
-                symbol_fills = [fill for fill in recent_fills if fill.get('symbol') == symbol]
-                
-                if symbol_fills:
-                    # Get the most recent fill as entry price estimate
-                    latest_fill = symbol_fills[0]  # Assuming sorted by newest first
-                    fill_price = float(latest_fill.get('price', 0))
-                    
-                    if fill_price > 0:
-                        logger.info(f"💡 Found recent fill price for {symbol}: ${fill_price:.4f}")
-                        return fill_price
-            
-            # Method 2: Use current market price as fallback
-            market_data = self.trading_engine.get_market_data(symbol)
-            if market_data and market_data.get('ticker'):
-                current_price = float(market_data['ticker'].get('last', 0))
-                
-                if current_price > 0:
-                    logger.warning(f"⚠️ Using current market price as entry estimate for {symbol}: ${current_price:.4f}")
-                    return current_price
-            
-            # Method 3: Last resort - try bid/ask average
-            if market_data and market_data.get('ticker'):
-                bid = float(market_data['ticker'].get('bid', 0))
-                ask = float(market_data['ticker'].get('ask', 0))
-                
-                if bid > 0 and ask > 0:
-                    avg_price = (bid + ask) / 2
-                    logger.warning(f"⚠️ Using bid/ask average as entry estimate for {symbol}: ${avg_price:.4f}")
-                    return avg_price
-            
-            # Method 4: Absolute fallback - return a small positive value to avoid 0
-            logger.error(f"❌ Could not estimate entry price for {symbol}, using fallback value of $1.00")
-            return 1.0
-            
-        except Exception as e:
-            logger.error(f"❌ Error estimating entry price for {symbol}: {e}")
-            return 1.0  # Safe fallback 

+ 1 - 1
trading_bot.py

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