Browse Source

Refactor price formatting across the application to utilize TokenDisplayFormatter

- Replaced instances of PriceFormatter with TokenDisplayFormatter for consistent price and amount formatting.
- Updated various command classes (InfoCommands, ManagementCommands, TradingCommands) to improve clarity in order details and notifications.
- Enhanced logging messages to include formatted prices and amounts, ensuring better readability and understanding of trade actions.
- Removed the deprecated price_formatter module, centralizing formatting logic in the new token_display_formatter module.
Carles Sentis 2 days ago
parent
commit
9f1b9d7140

+ 3 - 1
docs/project-structure.md

@@ -7,7 +7,7 @@
 ```
 trader_hyperliquid/
 โ”œโ”€โ”€ ๐Ÿ“‚ src/                     # Source code (modular architecture)
-โ”‚   โ”œโ”€โ”€ ๐Ÿ“ backup/              # Archive
+โ”‚   โ”œโ”€โ”€ ๐Ÿ“‚ backup/              # Archive
 โ”‚   โ”‚   โ””โ”€โ”€ ๐Ÿ“„ telegram_bot.py  # Original 4,627-line monolithic file
 โ”‚   โ”œโ”€โ”€ ๐Ÿ“ bot/                 # Core bot functionality
 โ”‚   โ”‚   โ””โ”€โ”€ ๐Ÿค– core.py          # Core bot setup & authentication (264 lines)
@@ -33,6 +33,8 @@ trader_hyperliquid/
 โ”‚   โ”œโ”€โ”€ ๐Ÿ“ trading/             # Trading logic
 โ”‚   โ”‚   โ”œโ”€โ”€ ๐Ÿš€ trading_engine.py # Order execution (419 lines)
 โ”‚   โ”‚   โ””โ”€โ”€ ๐Ÿ“Š trading_stats.py # Statistics tracking (1,161 lines)
+โ”‚   โ”œโ”€โ”€ ๐Ÿ“ utils/               # Utility scripts
+โ”‚   โ”‚   โ””โ”€โ”€ ๐Ÿ“„ token_display_formatter.py # Utility for formatting token prices and amounts
 โ”‚   โ””โ”€โ”€ ๐Ÿ“„ __init__.py          # Root module init
 โ”œโ”€โ”€ ๐Ÿ“‚ data/                    # ๐Ÿ†• Persistent data storage
 โ”‚   โ”œโ”€โ”€ ๐Ÿ“Š trading_stats.sqlite # SQLite database with all trading data

+ 143 - 118
src/commands/info_commands.py

@@ -10,7 +10,7 @@ from telegram import Update
 from telegram.ext import ContextTypes
 
 from src.config.config import Config
-from src.utils.price_formatter import format_price_with_symbol, get_formatter
+from src.utils.token_display_formatter import format_price_with_symbol, get_formatter
 
 logger = logging.getLogger(__name__)
 
@@ -204,14 +204,18 @@ class InfoCommands:
                 # --- Format Output String ---
                 # Get token info for formatting prices
                 # Assuming get_formatter() is available and provides necessary precision
-                # For direct use, we can fetch token_info if get_formatter() isn't what we expect
-                token_info = self.trading_engine.get_token_info(base_asset) # Ensure this method exists and works
-                base_precision = token_info.get('precision', {}).get('amount', 6) if token_info and token_info.get('precision') else 6 # Default amount 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
@@ -220,7 +224,7 @@ class InfoCommands:
                     type_indicator = " ๐Ÿ”„"
                 
                 positions_text += f"{pos_emoji} <b>{base_asset} ({direction_text}){type_indicator}</b>\n"
-                positions_text += f"   ๐Ÿ“ Size: {abs_current_amount:.{base_precision}f} {base_asset}\n"
+                positions_text += f"   ๐Ÿ“ Size: {size_str} {base_asset}\n" # Use the formatted size_str
                 positions_text += f"   ๐Ÿ’ฐ Entry: {entry_price_str}\n"
                 
                 if mark_price != 0 and abs(mark_price - entry_price) > 1e-9: # Only show mark if significantly different
@@ -713,6 +717,7 @@ class InfoCommands:
             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('total_trades', 0) == 0:
@@ -729,6 +734,7 @@ class InfoCommands:
         
         # Check if there's a message (no completed trades)
         if 'message' in token_stats and token_stats.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"
@@ -736,7 +742,7 @@ class InfoCommands:
                 f"โ€ข Total Trades: {token_stats['total_trades']}\n"
                 f"โ€ข Buy Orders: {token_stats.get('buy_trades', 0)}\n"
                 f"โ€ข Sell Orders: {token_stats.get('sell_trades', 0)}\n"
-                f"โ€ข Volume: ${token_stats.get('total_volume', 0):,.2f}\n\n"
+                f"โ€ข 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'
@@ -746,13 +752,22 @@ class InfoCommands:
         # Detailed stats display
         pnl_emoji = "๐ŸŸข" if token_stats['total_pnl'] >= 0 else "๐Ÿ”ด"
         
+        total_pnl_str = formatter.format_price_with_symbol(token_stats['total_pnl'], quote_asset=Config.QUOTE_CURRENCY)
+        completed_volume_str = formatter.format_price_with_symbol(token_stats['completed_volume'], quote_asset=Config.QUOTE_CURRENCY)
+        expectancy_str = formatter.format_price_with_symbol(token_stats['expectancy'], quote_asset=Config.QUOTE_CURRENCY)
+        largest_win_str = formatter.format_price_with_symbol(token_stats['largest_win'], quote_asset=Config.QUOTE_CURRENCY)
+        largest_loss_str = formatter.format_price_with_symbol(token_stats['largest_loss'], quote_asset=Config.QUOTE_CURRENCY) # Assuming loss is positive number
+        avg_win_str = formatter.format_price_with_symbol(token_stats['avg_win'], quote_asset=Config.QUOTE_CURRENCY)
+        avg_loss_str = formatter.format_price_with_symbol(token_stats['avg_loss'], quote_asset=Config.QUOTE_CURRENCY) # Assuming loss is positive number
+
+
         performance_text = f"""
 ๐Ÿ“Š <b>{token} Detailed Performance</b>
 
 ๐Ÿ’ฐ <b>P&L Summary:</b>
-โ€ข {pnl_emoji} Total P&L: ${token_stats['total_pnl']:,.2f} ({token_stats['pnl_percentage']:+.2f}%)
-โ€ข ๐Ÿ’ต Total Volume: ${token_stats['completed_volume']:,.2f}
-โ€ข ๐Ÿ“ˆ Expectancy: ${token_stats['expectancy']:,.2f}
+โ€ข {pnl_emoji} Total P&L: {total_pnl_str} ({token_stats['pnl_percentage']:+.2f}%)
+โ€ข ๐Ÿ’ต Total Volume: {completed_volume_str}
+โ€ข ๐Ÿ“ˆ Expectancy: {expectancy_str}
 
 ๐Ÿ“Š <b>Trading Activity:</b>
 โ€ข Total Trades: {token_stats['total_trades']}
@@ -766,10 +781,10 @@ class InfoCommands:
 โ€ข Wins: {token_stats['total_wins']} | Losses: {token_stats['total_losses']}
 
 ๐Ÿ’ก <b>Best/Worst:</b>
-โ€ข Largest Win: ${token_stats['largest_win']:,.2f}
-โ€ข Largest Loss: ${token_stats['largest_loss']:,.2f}
-โ€ข Avg Win: ${token_stats['avg_win']:,.2f}
-โ€ข Avg Loss: ${token_stats['avg_loss']:,.2f}
+โ€ข 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
@@ -778,9 +793,20 @@ class InfoCommands:
             for trade in token_stats['recent_trades'][-3:]:  # Last 3 trades
                 trade_time = datetime.fromisoformat(trade['timestamp']).strftime('%m/%d %H:%M')
                 side_emoji = "๐ŸŸข" if trade['side'] == 'buy' else "๐Ÿ”ด"
-                pnl_display = f" | P&L: ${trade.get('pnl', 0):.2f}" if trade.get('pnl', 0) != 0 else ""
                 
-                performance_text += f"โ€ข {side_emoji} {trade['side'].upper()} ${trade['value']:,.0f} @ {trade_time}{pnl_display}\n"
+                # 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), quote_asset=Config.QUOTE_CURRENCY)
+                
+                pnl_display_str = ""
+                if trade.get('pnl', 0) != 0:
+                    trade_pnl_str = formatter.format_price_with_symbol(trade.get('pnl', 0), quote_asset=Config.QUOTE_CURRENCY)
+                    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"
         
@@ -800,49 +826,50 @@ class InfoCommands:
                 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\n"
-                    "๐Ÿ“ญ No daily performance data available yet.\n\n"
-                    "๐Ÿ’ก Daily stats are calculated from completed trades.\n"
+                    "๐Ÿ“… <b>Daily Performance</b>\\n\\n"
+                    "๐Ÿ“ญ No daily performance data available yet.\\n\\n"
+                    "๐Ÿ’ก Daily stats are calculated from completed trades.\\n"
                     "Start trading to see daily performance!",
                     parse_mode='HTML'
                 )
                 return
             
-            daily_text = "๐Ÿ“… <b>Daily Performance (Last 10 Days)</b>\n\n"
+            daily_text = "๐Ÿ“… <b>Daily Performance (Last 10 Days)</b>\\n\\n"
             
-            total_pnl = 0
-            total_trades = 0
-            trading_days = 0
+            total_pnl_all_days = 0 # Renamed to avoid conflict
+            total_trades_all_days = 0 # Renamed
+            trading_days_count = 0 # Renamed
             
-            for day_stats in daily_stats:
-                if day_stats['has_trades']:
-                    # Day with completed trades
-                    pnl_emoji = "๐ŸŸข" if day_stats['pnl'] >= 0 else "๐Ÿ”ด"
-                    daily_text += f"๐Ÿ“Š <b>{day_stats['date_formatted']}</b>\n"
-                    daily_text += f"   {pnl_emoji} P&L: ${day_stats['pnl']:,.2f} ({day_stats['pnl_pct']:+.1f}%)\n"
-                    daily_text += f"   ๐Ÿ”„ Trades: {day_stats['trades']}\n\n"
+            for day_stats_item in daily_stats: # Renamed to avoid conflict
+                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'], quote_asset=Config.QUOTE_CURRENCY)
+                    daily_text += f"๐Ÿ“Š <b>{day_stats_item['date_formatted']}</b>\\n"
+                    daily_text += f"   {pnl_emoji} P&L: {pnl_str} ({day_stats_item['pnl_pct']:+.1f}%)\\n"
+                    daily_text += f"   ๐Ÿ”„ Trades: {day_stats_item['trades']}\\n\\n"
                     
-                    total_pnl += day_stats['pnl']
-                    total_trades += day_stats['trades']
-                    trading_days += 1
+                    total_pnl_all_days += day_stats_item['pnl']
+                    total_trades_all_days += day_stats_item['trades']
+                    trading_days_count += 1
                 else:
-                    # Day with no trades
-                    daily_text += f"๐Ÿ“Š <b>{day_stats['date_formatted']}</b>\n"
-                    daily_text += f"   ๐Ÿ“ญ No trading activity\n\n"
+                    daily_text += f"๐Ÿ“Š <b>{day_stats_item['date_formatted']}</b>\\n"
+                    daily_text += f"   ๐Ÿ“ญ No trading activity\\n\\n"
             
-            # Add summary
-            if trading_days > 0:
-                avg_daily_pnl = total_pnl / trading_days
+            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, quote_asset=Config.QUOTE_CURRENCY)
+                avg_daily_pnl_str = formatter.format_price_with_symbol(avg_daily_pnl, quote_asset=Config.QUOTE_CURRENCY)
                 
-                daily_text += f"๐Ÿ“ˆ <b>Period Summary:</b>\n"
-                daily_text += f"   {avg_pnl_emoji} Total P&L: ${total_pnl:,.2f}\n"
-                daily_text += f"   ๐Ÿ“Š Trading Days: {trading_days}/10\n"
-                daily_text += f"   ๐Ÿ“ˆ Avg Daily P&L: ${avg_daily_pnl:,.2f}\n"
-                daily_text += f"   ๐Ÿ”„ Total Trades: {total_trades}\n"
+                daily_text += f"๐Ÿ“ˆ <b>Period Summary:</b>\\n"
+                daily_text += f"   {avg_pnl_emoji} Total P&L: {total_pnl_all_days_str}\\n"
+                daily_text += f"   ๐Ÿ“Š Trading Days: {trading_days_count}/10\\n"
+                daily_text += f"   ๐Ÿ“… Avg Daily P&L: {avg_daily_pnl_str}\\n"
+                daily_text += f"   ๐Ÿ”„ Total Trades: {total_trades_all_days}\\n"
             
             await context.bot.send_message(chat_id=chat_id, text=daily_text.strip(), parse_mode='HTML')
                 
@@ -864,60 +891,59 @@ class InfoCommands:
                 await context.bot.send_message(chat_id=chat_id, text="โŒ Could not load trading statistics")
                 return
                 
-            weekly_stats = stats.get_weekly_stats(10)
+            weekly_stats_list = stats.get_weekly_stats(10) # Renamed variable
+            formatter = get_formatter() # Get formatter
             
-            if not weekly_stats:
+            if not weekly_stats_list:
                 await context.bot.send_message(chat_id=chat_id, text=
-                    "๐Ÿ“Š <b>Weekly Performance</b>\n\n"
-                    "๐Ÿ“ญ No weekly performance data available yet.\n\n"
-                    "๐Ÿ’ก Weekly stats are calculated from completed trades.\n"
+                    "๐Ÿ“Š <b>Weekly Performance</b>\\n\\n"
+                    "๐Ÿ“ญ No weekly performance data available yet.\\n\\n"
+                    "๐Ÿ’ก Weekly stats are calculated from completed trades.\\n"
                     "Start trading to see weekly performance!",
                     parse_mode='HTML'
                 )
                 return
             
-            weekly_text = "๐Ÿ“Š <b>Weekly Performance (Last 10 Weeks)</b>\n\n"
+            weekly_text = "๐Ÿ“Š <b>Weekly Performance (Last 10 Weeks)</b>\\n\\n"
             
-            total_pnl = 0
-            total_trades = 0
-            trading_weeks = 0
+            total_pnl_all_weeks = 0 # Renamed
+            total_trades_all_weeks = 0 # Renamed
+            trading_weeks_count = 0 # Renamed
             
-            for week_stats in weekly_stats:
-                if week_stats['has_trades']:
-                    # Week with completed trades
-                    pnl_emoji = "๐ŸŸข" if week_stats['pnl'] >= 0 else "๐Ÿ”ด"
-                    weekly_text += f"๐Ÿ“ˆ <b>{week_stats['week_formatted']}</b>\n"
-                    weekly_text += f"   {pnl_emoji} P&L: ${week_stats['pnl']:,.2f} ({week_stats['pnl_pct']:+.1f}%)\n"
-                    weekly_text += f"   ๐Ÿ”„ Trades: {week_stats['trades']}\n\n"
+            for week_stats_item in weekly_stats_list: # Renamed
+                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'], quote_asset=Config.QUOTE_CURRENCY)
+                    weekly_text += f"๐Ÿ“ˆ <b>{week_stats_item['week_formatted']}</b>\\n"
+                    weekly_text += f"   {pnl_emoji} P&L: {pnl_str} ({week_stats_item['pnl_pct']:+.1f}%)\\n"
+                    weekly_text += f"   ๐Ÿ”„ Trades: {week_stats_item['trades']}\\n\\n"
                     
-                    total_pnl += week_stats['pnl']
-                    total_trades += week_stats['trades']
-                    trading_weeks += 1
+                    total_pnl_all_weeks += week_stats_item['pnl']
+                    total_trades_all_weeks += week_stats_item['trades']
+                    trading_weeks_count += 1
                 else:
-                    # Week with no trades
-                    weekly_text += f"๐Ÿ“ˆ <b>{week_stats['week_formatted']}</b>\n"
-                    weekly_text += f"   ๐Ÿ“ญ No completed trades\n\n"
-            
-            # Add summary
-            if trading_weeks > 0:
-                total_pnl_emoji = "๐ŸŸข" if total_pnl >= 0 else "๐Ÿ”ด"
-                weekly_text += f"๐Ÿ’ผ <b>10-Week Summary:</b>\n"
-                weekly_text += f"   {total_pnl_emoji} Total P&L: ${total_pnl:,.2f}\n"
-                weekly_text += f"   ๐Ÿ”„ Total Trades: {total_trades}\n"
-                weekly_text += f"   ๐Ÿ“ˆ Trading Weeks: {trading_weeks}/10\n"
-                weekly_text += f"   ๐Ÿ“Š Avg per Trading Week: ${total_pnl/trading_weeks:,.2f}"
-            else:
-                weekly_text += f"๐Ÿ’ผ <b>10-Week Summary:</b>\n"
-                weekly_text += f"   ๐Ÿ“ญ No completed trades in the last 10 weeks\n"
-                weekly_text += f"   ๐Ÿ’ก Start trading to see weekly performance!"
+                    weekly_text += f"๐Ÿ“ˆ <b>{week_stats_item['week_formatted']}</b>\\n"
+                    weekly_text += f"   ๐Ÿ“ญ No trading activity\\n\\n"
+            
+            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, quote_asset=Config.QUOTE_CURRENCY)
+                avg_weekly_pnl_str = formatter.format_price_with_symbol(avg_weekly_pnl, quote_asset=Config.QUOTE_CURRENCY)
+
+                weekly_text += f"๐Ÿ“… <b>Period Summary:</b>\\n"
+                weekly_text += f"   {avg_pnl_emoji} Total P&L: {total_pnl_all_weeks_str}\\n"
+                weekly_text += f"   ๐Ÿ“Š Trading Weeks: {trading_weeks_count}/10\\n"
+                weekly_text += f"   ๐Ÿ“… Avg Weekly P&L: {avg_weekly_pnl_str}\\n"
+                weekly_text += f"   ๐Ÿ”„ Total Trades: {total_trades_all_weeks}\\n"
             
             await context.bot.send_message(chat_id=chat_id, text=weekly_text.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
@@ -931,55 +957,54 @@ class InfoCommands:
                 await context.bot.send_message(chat_id=chat_id, text="โŒ Could not load trading statistics")
                 return
                 
-            monthly_stats = stats.get_monthly_stats(10)
+            monthly_stats_list = stats.get_monthly_stats(12) # Renamed variable, 12 months
+            formatter = get_formatter() # Get formatter
             
-            if not monthly_stats:
+            if not monthly_stats_list:
                 await context.bot.send_message(chat_id=chat_id, text=
-                    "๐Ÿ“† <b>Monthly Performance</b>\n\n"
-                    "๐Ÿ“ญ No monthly performance data available yet.\n\n"
-                    "๐Ÿ’ก Monthly stats are calculated from completed trades.\n"
+                    "๐Ÿ—“๏ธ <b>Monthly Performance</b>\\n\\n"
+                    "๐Ÿ“ญ No monthly performance data available yet.\\n\\n"
+                    "๐Ÿ’ก Monthly stats are calculated from completed trades.\\n"
                     "Start trading to see monthly performance!",
                     parse_mode='HTML'
                 )
                 return
             
-            monthly_text = "๐Ÿ“† <b>Monthly Performance (Last 10 Months)</b>\n\n"
+            monthly_text = "๐Ÿ—“๏ธ <b>Monthly Performance (Last 12 Months)</b>\\n\\n"
             
-            total_pnl = 0
-            total_trades = 0
-            trading_months = 0
+            total_pnl_all_months = 0 # Renamed
+            total_trades_all_months = 0 # Renamed
+            trading_months_count = 0 # Renamed
             
-            for month_stats in monthly_stats:
-                if month_stats['has_trades']:
-                    # Month with completed trades
-                    pnl_emoji = "๐ŸŸข" if month_stats['pnl'] >= 0 else "๐Ÿ”ด"
-                    monthly_text += f"๐Ÿ“… <b>{month_stats['month_formatted']}</b>\n"
-                    monthly_text += f"   {pnl_emoji} P&L: ${month_stats['pnl']:,.2f} ({month_stats['pnl_pct']:+.1f}%)\n"
-                    monthly_text += f"   ๐Ÿ”„ Trades: {month_stats['trades']}\n\n"
+            for month_stats_item in monthly_stats_list: # Renamed
+                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'], quote_asset=Config.QUOTE_CURRENCY)
+                    monthly_text += f"๐Ÿ“… <b>{month_stats_item['month_formatted']}</b>\\n"
+                    monthly_text += f"   {pnl_emoji} P&L: {pnl_str} ({month_stats_item['pnl_pct']:+.1f}%)\\n"
+                    monthly_text += f"   ๐Ÿ”„ Trades: {month_stats_item['trades']}\\n\\n"
                     
-                    total_pnl += month_stats['pnl']
-                    total_trades += month_stats['trades']
-                    trading_months += 1
+                    total_pnl_all_months += month_stats_item['pnl']
+                    total_trades_all_months += month_stats_item['trades']
+                    trading_months_count += 1
                 else:
-                    # Month with no trades
-                    monthly_text += f"๐Ÿ“… <b>{month_stats['month_formatted']}</b>\n"
-                    monthly_text += f"   ๐Ÿ“ญ No completed trades\n\n"
-            
-            # Add summary
-            if trading_months > 0:
-                total_pnl_emoji = "๐ŸŸข" if total_pnl >= 0 else "๐Ÿ”ด"
-                monthly_text += f"๐Ÿ’ผ <b>10-Month Summary:</b>\n"
-                monthly_text += f"   {total_pnl_emoji} Total P&L: ${total_pnl:,.2f}\n"
-                monthly_text += f"   ๐Ÿ”„ Total Trades: {total_trades}\n"
-                monthly_text += f"   ๐Ÿ“ˆ Trading Months: {trading_months}/10\n"
-                monthly_text += f"   ๐Ÿ“Š Avg per Trading Month: ${total_pnl/trading_months:,.2f}"
-            else:
-                monthly_text += f"๐Ÿ’ผ <b>10-Month Summary:</b>\n"
-                monthly_text += f"   ๐Ÿ“ญ No completed trades in the last 10 months\n"
-                monthly_text += f"   ๐Ÿ’ก Start trading to see monthly performance!"
+                    monthly_text += f"๐Ÿ“… <b>{month_stats_item['month_formatted']}</b>\\n"
+                    monthly_text += f"   ๐Ÿ“ญ No trading activity\\n\\n"
+            
+            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, quote_asset=Config.QUOTE_CURRENCY)
+                avg_monthly_pnl_str = formatter.format_price_with_symbol(avg_monthly_pnl, quote_asset=Config.QUOTE_CURRENCY)
+                
+                monthly_text += f"๐Ÿ“ˆ <b>Period Summary:</b>\\n"
+                monthly_text += f"   {avg_pnl_emoji} Total P&L: {total_pnl_all_months_str}\\n"
+                monthly_text += f"   ๐Ÿ“Š Trading Months: {trading_months_count}/12\\n"
+                monthly_text += f"   ๐Ÿ—“๏ธ Avg Monthly P&L: {avg_monthly_pnl_str}\\n"
+                monthly_text += f"   ๐Ÿ”„ Total Trades: {total_trades_all_months}\\n"
             
             await context.bot.send_message(chat_id=chat_id, text=monthly_text.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)

+ 15 - 6
src/commands/management_commands.py

@@ -14,6 +14,9 @@ import json
 
 from src.config.config import Config
 from src.monitoring.alarm_manager import AlarmManager
+from src.utils.token_display_formatter import get_formatter
+from src.trading.trading_stats import TradingStats
+from src.config.logging_config import LoggingManager
 
 logger = logging.getLogger(__name__)
 
@@ -56,6 +59,7 @@ class ManagementCommands:
         adjustments_summary = stats.get_balance_adjustments_summary() if stats else {
             'total_deposits': 0, 'total_withdrawals': 0, 'net_adjustment': 0, 'adjustment_count': 0
         }
+        formatter = get_formatter()
         
         # Safety checks for monitoring attributes
         monitoring_active = self.market_monitor.is_running
@@ -70,7 +74,7 @@ class ManagementCommands:
 
 ๐Ÿ’ฐ <b>Balance Tracking:</b>
 โ€ข Total Adjustments: {adjustments_summary['adjustment_count']}
-โ€ข Net Adjustment: ${adjustments_summary['net_adjustment']:,.2f}
+โ€ข Net Adjustment: {formatter.format_price_with_symbol(adjustments_summary['net_adjustment'])}
 
 ๐Ÿ”” <b>Price Alarms:</b>
 โ€ข Active Alarms: {alarm_stats['total_active']}
@@ -174,27 +178,32 @@ class ManagementCommands:
                 
                 # Create the alarm
                 alarm = self.alarm_manager.create_alarm(token, target_price, current_price)
+                formatter = get_formatter()
                 
                 # Format confirmation message
                 direction_emoji = "๐Ÿ“ˆ" if alarm['direction'] == 'above' else "๐Ÿ“‰"
                 price_diff = abs(target_price - current_price)
-                price_diff_percent = (price_diff / current_price) * 100
+                price_diff_percent = (price_diff / current_price) * 100 if current_price != 0 else 0
                 
+                target_price_str = formatter.format_price_with_symbol(target_price, token)
+                current_price_str = formatter.format_price_with_symbol(current_price, token)
+                price_diff_str = formatter.format_price_with_symbol(price_diff, token)
+
                 message = f"""
 โœ… <b>Price Alarm Created</b>
 
 ๐Ÿ“Š <b>Alarm Details:</b>
 โ€ข Alarm ID: {alarm['id']}
 โ€ข Token: {token}
-โ€ข Target Price: ${target_price:,.2f}
-โ€ข Current Price: ${current_price:,.2f}
+โ€ข Target Price: {target_price_str}
+โ€ข Current Price: {current_price_str}
 โ€ข Direction: {alarm['direction'].upper()}
 
 {direction_emoji} <b>Alert Condition:</b>
-Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
+Will trigger when {token} price moves {alarm['direction']} {target_price_str}
 
 ๐Ÿ’ฐ <b>Price Difference:</b>
-โ€ข Distance: ${price_diff:,.2f} ({price_diff_percent:.2f}%)
+โ€ข Distance: {price_diff_str} ({price_diff_percent:.2f}%)
 โ€ข Status: ACTIVE โœ…
 
 โฐ <b>Created:</b> {datetime.now().strftime('%H:%M:%S')}

+ 11 - 11
src/commands/trading_commands.py

@@ -9,7 +9,7 @@ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
 from telegram.ext import ContextTypes
 
 from src.config.config import Config
-from src.utils.price_formatter import get_formatter
+from src.utils.token_display_formatter import get_formatter
 
 logger = logging.getLogger(__name__)
 
@@ -122,7 +122,7 @@ class TradingCommands:
 ๐Ÿ“Š <b>Order Details:</b>
 โ€ข Token: {token}
 โ€ข USDC Amount: {formatter.format_price_with_symbol(usdc_amount)}
-โ€ข Token Amount: {token_amount:.6f} {token}
+โ€ข Token Amount: {formatter.format_amount(token_amount, token)} {token}
 โ€ข Order Type: {order_type}
 โ€ข Price: {formatter.format_price_with_symbol(price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_price, token)}
@@ -243,7 +243,7 @@ This will {"place a limit buy order" if limit_price else "execute a market buy o
 ๐Ÿ“Š <b>Order Details:</b>
 โ€ข Token: {token}
 โ€ข USDC Amount: {formatter.format_price_with_symbol(usdc_amount)}
-โ€ข Token Amount: {token_amount:.6f} {token}
+โ€ข Token Amount: {formatter.format_amount(token_amount, token)} {token}
 โ€ข Order Type: {order_type}
 โ€ข Price: {formatter.format_price_with_symbol(price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_price, token)}
@@ -332,14 +332,14 @@ This will {"place a limit sell order" if limit_price else "execute a market sell
 ๐Ÿ“Š <b>Position Details:</b>
 โ€ข Token: {token}
 โ€ข Position: {position_type}
-โ€ข Size: {contracts:.6f} contracts
+โ€ข Size: {formatter.format_amount(contracts, token)} contracts
 โ€ข Entry Price: {formatter.format_price_with_symbol(entry_price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_price, token)}
 โ€ข {pnl_emoji} Unrealized P&L: {formatter.format_price_with_symbol(unrealized_pnl)}
 
 ๐ŸŽฏ <b>Exit Order:</b>
 โ€ข Action: {exit_side.upper()} (Close {position_type})
-โ€ข Amount: {contracts:.6f} {token}
+โ€ข Amount: {formatter.format_amount(contracts, token)} {token}
 โ€ข Est. Value: ~{formatter.format_price_with_symbol(exit_value)}
 โ€ข Order Type: Market Order
 
@@ -432,14 +432,14 @@ This will {"place a limit sell order" if limit_price else "execute a market sell
 ๐Ÿ“Š <b>Position Details:</b>
 โ€ข Token: {token}
 โ€ข Position: {position_type}
-โ€ข Size: {contracts:.6f} contracts
+โ€ข Size: {formatter.format_amount(contracts, token)} contracts
 โ€ข Entry Price: {formatter.format_price_with_symbol(entry_price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_price, token)}
 
 ๐ŸŽฏ <b>Stop Loss Order:</b>
 โ€ข Stop Price: {formatter.format_price_with_symbol(stop_price, token)}
 โ€ข Action: {exit_side.upper()} (Close {position_type})
-โ€ข Amount: {contracts:.6f} {token}
+โ€ข Amount: {formatter.format_amount(contracts, token)} {token}
 โ€ข Order Type: Limit Order
 โ€ข {pnl_emoji} Est. P&L: {formatter.format_price_with_symbol(pnl_at_stop)}
 
@@ -536,20 +536,20 @@ This will place a limit {exit_side} order at {formatter.format_price_with_symbol
 ๐Ÿ“Š <b>Position Details:</b>
 โ€ข Token: {token}
 โ€ข Position: {position_type}
-โ€ข Size: {contracts:.6f} contracts
+โ€ข Size: {formatter.format_amount(contracts, token)} contracts
 โ€ข Entry Price: {formatter.format_price_with_symbol(entry_price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_price, token)}
 
 ๐ŸŽฏ <b>Take Profit Order:</b>
-โ€ข Take Profit Price: {formatter.format_price_with_symbol(tp_price, token)}
+โ€ข Target Price: {formatter.format_price_with_symbol(tp_price, token)}
 โ€ข Action: {exit_side.upper()} (Close {position_type})
-โ€ข Amount: {contracts:.6f} {token}
+โ€ข Amount: {formatter.format_amount(contracts, token)} {token}
 โ€ข Order Type: Limit Order
 โ€ข {pnl_emoji} Est. P&L: {formatter.format_price_with_symbol(pnl_at_tp)}
 
 โš ๏ธ <b>Are you sure you want to set this take profit?</b>
 
-This will place a limit {exit_side} order at {formatter.format_price_with_symbol(tp_price, token)} to secure profits from your {position_type} position.
+This will place a limit {exit_side} order at {formatter.format_price_with_symbol(tp_price, token)} to secure profit on your {position_type} position.
             """
             
             keyboard = [

+ 105 - 120
src/migrations/migrate_db.py

@@ -6,144 +6,129 @@ import logging
 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 logger = logging.getLogger(__name__)
 
-# Determine the absolute path to the project root directory
-# Adjusted for the script being in src/migrations/
-PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-DB_PATH = os.path.join(PROJECT_ROOT, "data", "trading_stats.sqlite")
-
-# -----------------------------------------------------------------------------
-# MIGRATION DEFINITIONS
-# -----------------------------------------------------------------------------
-# Define the latest schema version the application expects.
-# Migrations will run until the DB schema_version matches this.
-LATEST_SCHEMA_VERSION = 2
-
-# Define columns for each migration version set
-# Version 1: Initial set of risk/P&L columns
-MIGRATION_SET_VERSION_1_COLUMNS = {
+# Define the database path consistently. Assuming it's the same as in TradingStats.
+# If TradingStats uses a different relative path, adjust this.
+DB_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'trading_stats.sqlite') # This resolves to <project_root>/data/trading_stats.sqlite
+
+# Define the expected schema for the 'trades' table
+# This should be the single source of truth for the trades table columns
+TRADES_TABLE_SCHEMA = {
+    "id": "INTEGER PRIMARY KEY AUTOINCREMENT",
+    "exchange_fill_id": "TEXT UNIQUE",
+    "timestamp": "TEXT NOT NULL",
+    "symbol": "TEXT NOT NULL",
+    "side": "TEXT NOT NULL",
+    "amount": "REAL NOT NULL",
+    "price": "REAL NOT NULL",
+    "value": "REAL NOT NULL",
+    "trade_type": "TEXT NOT NULL",
+    "pnl": "REAL DEFAULT 0.0", # This seems like an old PNL field, might be realized_pnl or needs review
+    "linked_order_table_id": "INTEGER",
+    "status": "TEXT DEFAULT 'executed'",
+    "trade_lifecycle_id": "TEXT",
+    "position_side": "TEXT",
+    "entry_price": "REAL",
+    "current_position_size": "REAL DEFAULT 0",
+    "entry_order_id": "TEXT",
+    "stop_loss_order_id": "TEXT",
+    "take_profit_order_id": "TEXT",
+    "stop_loss_price": "REAL",
+    "take_profit_price": "REAL",
+    "realized_pnl": "REAL DEFAULT 0",
+    "unrealized_pnl": "REAL DEFAULT 0",
+    "mark_price": "REAL DEFAULT 0",
+    "position_value": "REAL DEFAULT NULL",
+    "unrealized_pnl_percentage": "REAL DEFAULT NULL",
     "liquidation_price": "REAL DEFAULT NULL",
     "margin_used": "REAL DEFAULT NULL",
     "leverage": "REAL DEFAULT NULL",
-    "position_value": "REAL DEFAULT NULL"
-}
-
-# Version 2: Added unrealized P&L percentage
-MIGRATION_SET_VERSION_2_COLUMNS = {
-    "unrealized_pnl_percentage": "REAL DEFAULT NULL"
+    "position_opened_at": "TEXT",
+    "position_closed_at": "TEXT",
+    "updated_at": "TEXT DEFAULT CURRENT_TIMESTAMP", # Ensure this default is correctly applied by SQLite
+    "notes": "TEXT"
 }
-# -----------------------------------------------------------------------------
 
-def _get_db_connection(db_path):
-    """Gets a database connection."""
-    return sqlite3.connect(db_path)
-
-def _get_current_schema_version(conn):
-    """Retrieves the current schema version from the metadata table."""
-    try:
-        cursor = conn.cursor()
-        # Ensure metadata table exists (idempotent)
-        cursor.execute("CREATE TABLE IF NOT EXISTS metadata (key TEXT PRIMARY KEY, value TEXT)")
-        conn.commit()
-        
-        cursor.execute("SELECT value FROM metadata WHERE key = 'schema_version'")
-        row = cursor.fetchone()
-        return int(row[0]) if row else 0  # Default to 0 if no version is set
-    except sqlite3.Error as e:
-        logger.error(f"Error getting schema version: {e}. Assuming version 0.")
-        return 0
-
-def _set_schema_version(conn, version):
-    """Sets the schema version in the metadata table."""
-    try:
-        cursor = conn.cursor()
-        cursor.execute("INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', ?)", (str(version),))
-        conn.commit()
-        logger.info(f"Database schema version successfully set to {version}.")
-    except sqlite3.Error as e:
-        logger.error(f"Error setting schema version to {version}: {e}")
-
-def _column_exists(conn, table_name, column_name):
-    """Checks if a column exists in a table using PRAGMA table_info."""
+def get_existing_columns(conn: sqlite3.Connection, table_name: str) -> list[str]:
+    """Fetches the list of existing column names for a given table."""
     cursor = conn.cursor()
-    cursor.execute(f"PRAGMA table_info({table_name})")
-    columns = [row[1] for row in cursor.fetchall()]
-    return column_name in columns
+    cursor.execute(f"PRAGMA table_info({table_name});")
+    return [row[1] for row in cursor.fetchall()]
 
-def _add_column_if_not_exists(conn, table_name, column_name, column_definition):
-    """Adds a column to a table if it doesn't already exist."""
-    if not _column_exists(conn, table_name, column_name):
-        try:
-            cursor = conn.cursor()
-            query = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_definition}"
-            cursor.execute(query)
-            conn.commit()
-            logger.info(f"Successfully added column '{column_name}' to table '{table_name}'.")
-        except sqlite3.OperationalError as e:
-            # This specific error means the column might already exist (though _column_exists should prevent this)
-            # or some other operational issue.
-            if f"duplicate column name: {column_name}" in str(e).lower():
-                 logger.info(f"Column '{column_name}' effectively already exists in '{table_name}'. No action needed.")
-            else:
-                logger.error(f"Error adding column '{column_name}' to table '{table_name}': {e}")
-                raise # Re-raise to signal migration step failure
+def add_missing_columns(conn: sqlite3.Connection, table_name: str, expected_schema: dict[str, str]):
+    """Adds missing columns to the table based on the expected schema."""
+    existing_columns = get_existing_columns(conn, table_name)
+    cursor = conn.cursor()
+    added_columns = False
+
+    for column_name, column_definition in expected_schema.items():
+        if column_name not in existing_columns:
+            try:
+                # Basic ALTER TABLE statement. For defaults, SQLite adds them correctly for new rows.
+                # Existing rows will have NULL for columns added this way unless a specific default is part of ADD COLUMN.
+                # For simple REAL DEFAULT NULL or TEXT DEFAULT NULL, this is fine.
+                # For REAL DEFAULT 0, SQLite handles this correctly.
+                # For TEXT DEFAULT CURRENT_TIMESTAMP, this default applies on INSERT/UPDATE if specified, not on ALTER TABLE for existing rows.
+                # The schema in TradingStats._create_tables() handles the CURRENT_TIMESTAMP default for new inserts.
+                alter_query = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_definition};"
+                cursor.execute(alter_query)
+                logger.info(f"Added missing column '{column_name}' to table '{table_name}'.")
+                added_columns = True
+            except sqlite3.OperationalError as e:
+                # This can happen if the column definition is complex or has issues.
+                # For example, adding a NOT NULL column without a default to an existing table with data.
+                # Our current schema defaults to NULL or 0, which should be safe.
+                logger.error(f"Failed to add column '{column_name}' to '{table_name}': {e}")
+    
+    if added_columns:
+        logger.info(f"Schema migration: Finished adding missing columns to '{table_name}'.")
     else:
-        logger.info(f"Column '{column_name}' already exists in table '{table_name}'. No action taken.")
+        logger.info(f"Schema migration: Table '{table_name}' is already up to date.")
 
 def run_migrations():
-    """Runs the database migrations sequentially."""
-    logger.info(f"Attempting to migrate database at: {DB_PATH}")
-    if not os.path.exists(DB_PATH):
-        logger.info(f"Database file not found at {DB_PATH}. Nothing to migrate. "
-                    f"The application will create it with the latest schema on its next start.")
-        return
+    """
+    Runs all database migrations.
+    Currently, it only checks and adds missing columns to the 'trades' table.
+    """
+    logger.info(f"Connecting to database at {DB_PATH} for migration check...")
+    
+    # Ensure the data directory exists before trying to connect
+    data_dir = os.path.dirname(DB_PATH)
+    if data_dir and not os.path.exists(data_dir):
+        try:
+            os.makedirs(data_dir)
+            logger.info(f"Created data directory for migrations: {data_dir}")
+        except OSError as e:
+            logger.error(f"Failed to create data directory {data_dir}: {e}")
+            # If directory creation fails, we likely can't proceed with DB operations.
+            return
 
     conn = None
     try:
-        conn = _get_db_connection(DB_PATH)
-        current_db_version = _get_current_schema_version(conn)
-        logger.info(f"Current reported database schema version: {current_db_version}")
-
-        if current_db_version < LATEST_SCHEMA_VERSION:
-            logger.info(f"Schema version {current_db_version} is older than target latest schema version {LATEST_SCHEMA_VERSION}. Starting migration process...")
-
-            # --- Migration for Version 1 ---
-            if current_db_version < 1:
-                logger.info("Applying migration set for version 1...")
-                for col_name, col_def in MIGRATION_SET_VERSION_1_COLUMNS.items():
-                    _add_column_if_not_exists(conn, "trades", col_name, col_def)
-                _set_schema_version(conn, 1)
-                current_db_version = 1 # Update current_db_version after successful migration
-                logger.info("Successfully migrated database to schema version 1.")
-            
-            # --- Migration for Version 2 ---
-            if current_db_version < 2:
-                logger.info("Applying migration set for version 2...")
-                for col_name, col_def in MIGRATION_SET_VERSION_2_COLUMNS.items():
-                    _add_column_if_not_exists(conn, "trades", col_name, col_def)
-                _set_schema_version(conn, 2)
-                current_db_version = 2 # Update current_db_version
-                logger.info("Successfully migrated database to schema version 2.")
-            
-            # Add more migration blocks here for future versions (e.g., if current_db_version < 3:)
-
-            if current_db_version == LATEST_SCHEMA_VERSION:
-                 logger.info(f"All migrations applied. Database is now at schema version {current_db_version}.")
-            else:
-                 logger.warning(f"Migration process completed, but DB schema version is {current_db_version}, expected {LATEST_SCHEMA_VERSION}. Check migration logic.")
-
+        conn = sqlite3.connect(DB_PATH)
+        # Check and update 'trades' table
+        # First, ensure the table exists (it should be created by TradingStats._create_tables if new)
+        cursor = conn.cursor()
+        cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='trades';")
+        if cursor.fetchone():
+            logger.info("Table 'trades' exists. Checking for missing columns...")
+            add_missing_columns(conn, "trades", TRADES_TABLE_SCHEMA)
         else:
-            logger.info(f"Database schema version {current_db_version} is already at or newer than latest target schema {LATEST_SCHEMA_VERSION}. No migration needed.")
+            logger.info("Table 'trades' does not exist. It will be created by TradingStats class. Skipping column check for now.")
+
+        # Add checks for other tables here if needed in the future
+        # e.g., add_missing_columns(conn, "orders", ORDERS_TABLE_SCHEMA)
 
+        conn.commit()
+        logger.info("Database migration check completed successfully.")
     except sqlite3.Error as e:
-        logger.error(f"A database error occurred during migration: {e}")
-    except Exception as e:
-        logger.error(f"An unexpected error occurred during migration: {e}", exc_info=True)
+        logger.error(f"Database migration error: {e}")
     finally:
         if conn:
             conn.close()
-            logger.info("Database connection closed.")
 
-if __name__ == "__main__":
-    logger.info("Starting database migration script...")
+if __name__ == '__main__':
+    # Configure logging for direct script execution (optional)
+    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
+    logger.info("Running migrations directly...")
     run_migrations()
-    logger.info("Database migration script finished.") 
+    logger.info("Migrations script finished.") 

+ 17 - 4
src/monitoring/alarm_manager.py

@@ -10,6 +10,10 @@ import os
 import logging
 from datetime import datetime, timezone
 from typing import Dict, List, Any, Optional
+from telegram.ext import ContextTypes
+
+from src.config.config import Config
+from src.utils.token_display_formatter import get_formatter
 
 logger = logging.getLogger(__name__)
 
@@ -197,14 +201,18 @@ class AlarmManager:
             return f"๐Ÿ”” <b>{title}</b>\n\n๐Ÿ“ญ No active alarms.\nUse <code>/alarm TOKEN PRICE</code> to set one."
         
         message = f"๐Ÿ”” <b>{title}</b>\n\n"
+        formatter = get_formatter()
         for alarm in alarms:
             direction_emoji = "๐Ÿ“ˆ" if alarm['direction'] == 'above' else "๐Ÿ“‰"
             created_price = alarm.get('current_price_at_creation', alarm['target_price'])
             price_diff = abs(alarm['target_price'] - created_price)
             price_diff_percent = (price_diff / created_price * 100) if created_price > 0 else 0
             
-            message += f"โ€ข ID {alarm['id']}: {alarm['token']} {direction_emoji} ${alarm['target_price']:,.2f}\n"
-            message += f"  (Created at: ${created_price:,.2f}, Diff: {price_diff_percent:.1f}%)\n"
+            target_price_str = formatter.format_price_with_symbol(alarm['target_price'], alarm['token'])
+            created_price_str = formatter.format_price_with_symbol(created_price, alarm['token'])
+
+            message += f"โ€ข ID {alarm['id']}: {alarm['token']} {direction_emoji} {target_price_str}\n"
+            message += f"  (Created at: {created_price_str}, Diff: {price_diff_percent:.1f}%)\n"
         
         message += "\n๐Ÿ’ก Use <code>/alarm ID</code> to remove an alarm."
         return message.strip()
@@ -212,6 +220,7 @@ class AlarmManager:
     def format_triggered_alarm(self, alarm: Dict[str, Any]) -> str:
         """Format a triggered alarm notification."""
         direction_emoji = "๐Ÿ“ˆ" if alarm['direction'] == 'above' else "๐Ÿ“‰"
+        formatter = get_formatter()
         triggered_at_str = "Unknown time"
         if alarm.get('triggered_at'):
             try:
@@ -219,13 +228,17 @@ class AlarmManager:
             except ValueError:
                 pass # Keep as Unknown time
 
+        target_price_str = formatter.format_price_with_symbol(alarm['target_price'], alarm['token'])
+        triggered_price_val = alarm.get('triggered_price', 0.0)
+        triggered_price_str = formatter.format_price_with_symbol(triggered_price_val, alarm['token'])
+
         message = f"""
 ๐Ÿšจ <b>Price Alarm Triggered!</b>
 
 ๐Ÿ”” <b>Alarm ID: {alarm['id']}</b>
 โ€ข Token: {alarm['token']}
-โ€ข Condition: Price {alarm['direction']} ${alarm['target_price']:,.2f}
-โ€ข Triggered Price: ${alarm.get('triggered_price', 0.0):,.2f} {direction_emoji}
+โ€ข Condition: Price {alarm['direction']} {target_price_str}
+โ€ข Triggered Price: {triggered_price_str} {direction_emoji}
 
 โฐ <b>Time:</b> {triggered_at_str}
 ๐Ÿ’ก This alarm is now inactive.

+ 72 - 48
src/monitoring/market_monitor.py

@@ -10,8 +10,11 @@ from typing import Optional, Dict, Any, List
 import os
 import json
 
+from telegram.ext import CallbackContext
+
 from src.config.config import Config
 from src.monitoring.alarm_manager import AlarmManager
+from src.utils.token_display_formatter import get_formatter
 
 logger = logging.getLogger(__name__)
 
@@ -387,6 +390,7 @@ class MarketMonitor:
         """Update position tracking and calculate P&L changes."""
         try:
             new_position_map = {}
+            formatter = get_formatter() # Get formatter
             
             for position in current_positions:
                 symbol = position.get('symbol')
@@ -402,14 +406,18 @@ class MarketMonitor:
             # Compare with previous positions to detect changes
             for symbol, new_data in new_position_map.items():
                 old_data = self.last_known_positions.get(symbol)
+                token = symbol.split('/')[0] if '/' in symbol else symbol # Extract token
                 
                 if not old_data:
                     # New position opened
-                    logger.info(f"๐Ÿ“ˆ New position detected (observed by MarketMonitor): {symbol} {new_data['contracts']} @ ${new_data['entry_price']:.4f}. TradingStats is the definitive source.")
+                    amount_str = formatter.format_amount(new_data['contracts'], token)
+                    price_str = formatter.format_price_with_symbol(new_data['entry_price'], token)
+                    logger.info(f"๐Ÿ“ˆ New position detected (observed by MarketMonitor): {symbol} {amount_str} @ {price_str}. TradingStats is the definitive source.")
                 elif abs(new_data['contracts'] - old_data['contracts']) > 0.000001:
                     # Position size changed
                     change = new_data['contracts'] - old_data['contracts']
-                    logger.info(f"๐Ÿ“Š Position change detected (observed by MarketMonitor): {symbol} {change:+.6f} contracts. TradingStats is the definitive source.")
+                    change_str = formatter.format_amount(change, token)
+                    logger.info(f"๐Ÿ“Š Position change detected (observed by MarketMonitor): {symbol} {change_str} contracts. TradingStats is the definitive source.")
             
             # Check for closed positions
             for symbol in self.last_known_positions:
@@ -760,7 +768,9 @@ class MarketMonitor:
                                 )
                                 if success:
                                     pnl_emoji = "๐ŸŸข" if realized_pnl >= 0 else "๐Ÿ”ด"
-                                    logger.info(f"{pnl_emoji} Lifecycle CLOSED: {lc_id} ({action_type}). PNL for fill: ${realized_pnl:.2f}")
+                                    # Get formatter for this log line
+                                    formatter = get_formatter()
+                                    logger.info(f"{pnl_emoji} Lifecycle CLOSED: {lc_id} ({action_type}). PNL for fill: {formatter.format_price_with_symbol(realized_pnl)}")
                                     symbols_with_fills.add(token)
                                     if self.notification_manager:
                                         await self.notification_manager.send_external_trade_notification(
@@ -777,7 +787,8 @@ class MarketMonitor:
                     if not fill_processed_this_iteration:
                         if exchange_order_id_from_fill and exchange_order_id_from_fill in self.external_stop_losses:
                             stop_loss_info = self.external_stop_losses[exchange_order_id_from_fill]
-                            logger.info(f"๐Ÿ›‘ External SL (MM Tracking): {token} Order {exchange_order_id_from_fill} filled @ ${price_from_fill:.2f}")
+                            formatter = get_formatter()
+                            logger.info(f"๐Ÿ›‘ External SL (MM Tracking): {token} Order {exchange_order_id_from_fill} filled @ {formatter.format_price_with_symbol(price_from_fill, token)}")
                             
                             sl_active_lc = stats.get_trade_by_symbol_and_status(full_symbol, 'position_opened')
                             if sl_active_lc:
@@ -789,7 +800,7 @@ class MarketMonitor:
                                 success = stats.update_trade_position_closed(lc_id, price_from_fill, realized_pnl, trade_id)
                                 if success:
                                     pnl_emoji = "๐ŸŸข" if realized_pnl >= 0 else "๐Ÿ”ด"
-                                    logger.info(f"{pnl_emoji} Lifecycle CLOSED by External SL (MM): {lc_id}. PNL: ${realized_pnl:.2f}")
+                                    logger.info(f"{pnl_emoji} Lifecycle CLOSED by External SL (MM): {lc_id}. PNL: {formatter.format_price_with_symbol(realized_pnl)}")
                                     if self.notification_manager:
                                         await self.notification_manager.send_stop_loss_execution_notification(
                                             stop_loss_info, full_symbol, side_from_fill, amount_from_fill, price_from_fill, 
@@ -1163,7 +1174,7 @@ class MarketMonitor:
             if not stats:
                 return
             
-            # Get open positions that need stop loss activation
+            formatter = get_formatter() # Get formatter
             trades_needing_sl = stats.get_pending_stop_loss_activations()
             
             if not trades_needing_sl:
@@ -1196,12 +1207,14 @@ class MarketMonitor:
                     trigger_reason = ""
                     
                     if current_price and current_price > 0 and stop_loss_price and stop_loss_price > 0:
+                        current_price_str = formatter.format_price_with_symbol(current_price, token)
+                        stop_loss_price_str = formatter.format_price_with_symbol(stop_loss_price, token)
                         if sl_side == 'sell' and current_price <= stop_loss_price:
                             trigger_already_hit = True
-                            trigger_reason = f"LONG SL: Current ${current_price:.4f} โ‰ค Stop ${stop_loss_price:.4f}"
+                            trigger_reason = f"LONG SL: Current {current_price_str} โ‰ค Stop {stop_loss_price_str}"
                         elif sl_side == 'buy' and current_price >= stop_loss_price:
                             trigger_already_hit = True
-                            trigger_reason = f"SHORT SL: Current ${current_price:.4f} โ‰ฅ Stop ${stop_loss_price:.4f}"
+                            trigger_reason = f"SHORT SL: Current {current_price_str} โ‰ฅ Stop {stop_loss_price_str}"
                     
                     if trigger_already_hit:
                         logger.warning(f"๐Ÿšจ IMMEDIATE SL EXECUTION (Trades Table): {token} (Lifecycle: {lifecycle_id[:8]}) - {trigger_reason}. Executing market exit.")
@@ -1219,14 +1232,17 @@ class MarketMonitor:
 
 
                                 if self.notification_manager:
+                                    # Re-fetch formatted prices for notification if not already strings
+                                    current_price_str_notify = formatter.format_price_with_symbol(current_price, token) if current_price else "N/A"
+                                    stop_loss_price_str_notify = formatter.format_price_with_symbol(stop_loss_price, token) if stop_loss_price else "N/A"
                                     await self.notification_manager.send_generic_notification(
                                         f"๐Ÿšจ <b>Immediate Stop Loss Execution</b>\n\n"
                                         f"๐Ÿ†• <b>Source: Unified Trades Table</b>\n"
                                         f"Token: {token}\n"
                                         f"Lifecycle ID: {lifecycle_id[:8]}...\n"
                                         f"Position Type: {position_side.upper()}\n"
-                                        f"SL Trigger Price: ${stop_loss_price:.4f}\n"
-                                        f"Current Market Price: ${current_price:.4f}\n"
+                                        f"SL Trigger Price: {stop_loss_price_str_notify}\n"
+                                        f"Current Market Price: {current_price_str_notify}\n"
                                         f"Trigger Logic: {trigger_reason}\n"
                                         f"Action: Market close order placed immediately\n"
                                         f"Exit Order ID: {exit_order_id}\n"
@@ -1284,16 +1300,19 @@ class MarketMonitor:
 
                                 if sl_exchange_order_id: # If an actual exchange order ID was returned for the SL
                                     stats.link_stop_loss_to_trade(lifecycle_id, sl_exchange_order_id, stop_loss_price)
-                                    logger.info(f"โœ… Activated {position_side.upper()} stop loss for {token} (Lifecycle: {lifecycle_id[:8]}): SL Price ${stop_loss_price:.4f}, Exchange SL Order ID: {sl_exchange_order_id}")
+                                    stop_loss_price_str_log = formatter.format_price_with_symbol(stop_loss_price, token)
+                                    logger.info(f"โœ… Activated {position_side.upper()} stop loss for {token} (Lifecycle: {lifecycle_id[:8]}): SL Price {stop_loss_price_str_log}, Exchange SL Order ID: {sl_exchange_order_id}")
                                     if self.notification_manager:
+                                        current_price_str_notify = formatter.format_price_with_symbol(current_price, token) if current_price else 'Unknown'
+                                        stop_loss_price_str_notify = formatter.format_price_with_symbol(stop_loss_price, token)
                                         await self.notification_manager.send_generic_notification(
                                             f"๐Ÿ›‘ <b>Stop Loss Activated</b>\n\n"
                                             f"๐Ÿ†• <b>Source: Unified Trades Table</b>\n"
                                             f"Token: {token}\n"
                                             f"Lifecycle ID: {lifecycle_id[:8]}...\n"
                                             f"Position Type: {position_side.upper()}\n"
-                                            f"Stop Loss Price: ${stop_loss_price:.4f}\n"
-                                            f"Current Price: ${current_price:.4f if current_price else 'Unknown'}\n"
+                                            f"Stop Loss Price: {stop_loss_price_str_notify}\n"
+                                            f"Current Price: {current_price_str_notify}\n"
                                             f"Exchange SL Order ID: {sl_exchange_order_id}\n" # Actual exchange order
                                             f"Time: {datetime.now().strftime('%H:%M:%S')}"
                                         )
@@ -1642,35 +1661,35 @@ class MarketMonitor:
         """Estimate entry price for an orphaned position by checking recent fills and market data."""
         try:
             entry_fill_side = 'buy' if side == 'long' else 'sell'
+            formatter = get_formatter()
+            token = symbol.split('/')[0] if '/' in symbol else symbol
             recent_fills = self.trading_engine.get_recent_fills(symbol=symbol, limit=20)
             if recent_fills:
                 symbol_side_fills = [
                     fill for fill in recent_fills 
                     if fill.get('symbol') == symbol and fill.get('side') == entry_fill_side and float(fill.get('amount',0)) > 0
                 ]
-                # Try to find a fill that closely matches quantity, prioritizing most recent of those
-                # This is a heuristic. A perfect match is unlikely for externally opened positions.
                 if symbol_side_fills:
-                    # Sort by timestamp (newest first) then by how close amount is to position size
                     symbol_side_fills.sort(key=lambda f: (
                         datetime.fromtimestamp(f.get('timestamp') / 1000, tz=timezone.utc) if f.get('timestamp') else datetime.min.replace(tzinfo=timezone.utc),
                         abs(float(f.get('amount',0)) - contracts)
-                        ), reverse=True) # Newest first
+                        ), reverse=True)
                     
-                    best_fill = symbol_side_fills[0] # Take the newest fill that matches side
+                    best_fill = symbol_side_fills[0]
                     fill_price = float(best_fill.get('price', 0))
+                    fill_amount = float(best_fill.get('amount', 0))
                     if fill_price > 0:
-                        logger.info(f"๐Ÿ’ก AUTO-SYNC: Estimated entry for {side} {symbol} via recent {entry_fill_side} fill: ${fill_price:.4f} (Amount: {best_fill.get('amount')})")
+                        logger.info(f"๐Ÿ’ก AUTO-SYNC: Estimated entry for {side} {symbol} via recent {entry_fill_side} fill: {formatter.format_price_with_symbol(fill_price, token)} (Amount: {formatter.format_amount(fill_amount, token)})")
                         return fill_price
             
             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"โš ๏ธ AUTO-SYNC: Using current market price as entry estimate for {side} {symbol}: ${current_price:.4f}")
+                    logger.warning(f"โš ๏ธ AUTO-SYNC: Using current market price as entry estimate for {side} {symbol}: {formatter.format_price_with_symbol(current_price, token)}")
                     return current_price
             
-            if market_data and market_data.get('ticker'): # Bid/Ask as last resort
+            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: return (bid + ask) / 2
@@ -1690,6 +1709,7 @@ class MarketMonitor:
                 logger.warning("โš ๏ธ STARTUP: TradingStats not available for auto-sync.")
                 return
 
+            formatter = get_formatter() # Ensure formatter is available
             exchange_positions = self.trading_engine.get_positions() or []
             if not exchange_positions:
                 logger.info("โœ… STARTUP: No positions found on exchange.")
@@ -1699,18 +1719,18 @@ class MarketMonitor:
             for exchange_pos in exchange_positions:
                 symbol = exchange_pos.get('symbol')
                 contracts_abs = abs(float(exchange_pos.get('contracts', 0)))
+                token_for_log = symbol.split('/')[0] if symbol and '/' in symbol else symbol # Prepare token for logging
                 
                 if not (symbol and contracts_abs > 1e-9): continue
 
                 existing_trade_lc = stats.get_trade_by_symbol_and_status(symbol, 'position_opened')
                 if not existing_trade_lc:
-                    # Determine position side and entry order side
                     position_side, order_side = '', ''
                     ccxt_side = exchange_pos.get('side', '').lower()
                     if ccxt_side == 'long': position_side, order_side = 'long', 'buy'
                     elif ccxt_side == 'short': position_side, order_side = 'short', 'sell'
                     
-                    if not position_side: # Fallback to raw info
+                    if not position_side:
                         raw_info = exchange_pos.get('info', {}).get('position', {})
                         if isinstance(raw_info, dict):
                             szi_str = raw_info.get('szi')
@@ -1720,10 +1740,10 @@ class MarketMonitor:
                                 if szi_val > 1e-9: position_side, order_side = 'long', 'buy'
                                 elif szi_val < -1e-9: position_side, order_side = 'short', 'sell'
                     
-                    if not position_side: # Final fallback
+                    if not position_side:
                         contracts_val = float(exchange_pos.get('contracts',0))
                         if contracts_val > 1e-9: position_side, order_side = 'long', 'buy'
-                        elif contracts_val < -1e-9: position_side, order_side = 'short', 'sell' # Assumes negative for short
+                        elif contracts_val < -1e-9: position_side, order_side = 'short', 'sell'
                         else:
                             logger.warning(f"AUTO-SYNC: Position size is effectively 0 for {symbol} after side checks, skipping sync. Data: {exchange_pos}")
                             continue
@@ -1743,7 +1763,7 @@ class MarketMonitor:
                             logger.error(f"AUTO-SYNC: Could not determine/estimate entry price for {symbol}. Skipping sync.")
                             continue
                     
-                    logger.info(f"๐Ÿ”„ STARTUP: Auto-syncing orphaned position: {symbol} {position_side.upper()} {contracts_abs} @ ${entry_price:.4f} {price_source_log}")
+                    logger.info(f"๐Ÿ”„ STARTUP: Auto-syncing orphaned position: {symbol} {position_side.upper()} {formatter.format_amount(contracts_abs, token_for_log)} @ {formatter.format_price_with_symbol(entry_price, token_for_log)} {price_source_log}")
                     
                     lifecycle_id = stats.create_trade_lifecycle(
                         symbol=symbol, side=order_side,
@@ -1763,11 +1783,10 @@ class MarketMonitor:
                         else: logger.error(f"โŒ STARTUP: Failed to update lifecycle for {symbol} (Lifecycle: {lifecycle_id[:8]}).")
                     else: logger.error(f"โŒ STARTUP: Failed to create lifecycle for {symbol}.")
             
-            if synced_count == 0 and exchange_positions: # Positions existed but all were tracked
+            if synced_count == 0 and exchange_positions:
                  logger.info("โœ… STARTUP: All existing exchange positions are already tracked.")
             elif synced_count > 0:
                  logger.info(f"๐ŸŽ‰ STARTUP: Auto-synced {synced_count} orphaned position(s).")
-            # If no positions and no synced, it's already logged.
                 
         except Exception as e:
             logger.error(f"โŒ Error in startup auto-sync: {e}", exc_info=True)
@@ -1777,34 +1796,39 @@ class MarketMonitor:
         try:
             if not self.notification_manager: return
 
+            formatter = get_formatter()
             token = symbol.split('/')[0] if '/' in symbol else symbol
             unrealized_pnl = float(exchange_pos.get('unrealizedPnl', 0))
-            pnl_percentage = float(exchange_pos.get('percentage', 0)) # CCXT standard field for PNL %
             pnl_emoji = "๐ŸŸข" if unrealized_pnl >= 0 else "๐Ÿ”ด"
             
-            notification_text = (
-                f"๐Ÿšจ <b>Bot Startup: Position Auto-Synced</b>\n\n"
-                f"Token: {token}\n"
-                f"Lifecycle ID: {lifecycle_id[:8]}...\n"
-                f"Direction: {position_side.upper()}\n"
-                f"Size: {contracts:.6f} {token}\n"
-                f"Entry Price: ${entry_price:,.4f} {price_source_log}\n"
-                f"{pnl_emoji} P&L (Unrealized): ${unrealized_pnl:,.2f}\n"
-                f"Reason: Position found on exchange without bot record.\n"
-                f"Time: {datetime.now().strftime('%H:%M:%S')}\n\n"
-                f"โœ… Position now tracked. Use /sl or /tp if needed."
-            )
+            size_str = formatter.format_amount(contracts, token)
+            entry_price_str = formatter.format_price_with_symbol(entry_price, token)
+            pnl_str = formatter.format_price_with_symbol(unrealized_pnl)
+
+            notification_text_parts = [
+                f"๐Ÿšจ <b>Bot Startup: Position Auto-Synced</b>\n",
+                f"Token: {token}",
+                f"Lifecycle ID: {lifecycle_id[:8]}...",
+                f"Direction: {position_side.upper()}",
+                f"Size: {size_str} {token}",
+                f"Entry Price: {entry_price_str} {price_source_log}",
+                f"{pnl_emoji} P&L (Unrealized): {pnl_str}",
+                f"Reason: Position found on exchange without bot record.",
+                # f"Time: {datetime.now().strftime('%H:%M:%S')}", # Time is in the main header of notification usually
+                "\nโœ… Position now tracked. Use /sl or /tp if needed."
+            ]
             
             liq_price = float(exchange_pos.get('liquidationPrice', 0))
-            if liq_price > 0: notification_text += f"โš ๏ธ Liquidation: ${liq_price:,.2f}\n"
+            if liq_price > 0: 
+                liq_price_str = formatter.format_price_with_symbol(liq_price, token)
+                notification_text_parts.append(f"โš ๏ธ Liquidation: {liq_price_str}")
             
-            notification_text += (
-                f"\n๐Ÿ“ <b>Discovered on bot startup</b>\n"
-                f"โฐ Time: {datetime.now().strftime('%H:%M:%S')}\n\n"
-                f"โœ… Position now tracked. Use /sl or /tp if needed."
-            )
+            # Combined details into the main block
+            # notification_text_parts.append("\n๐Ÿ“ <b>Discovered on bot startup</b>")
+            # notification_text_parts.append(f"โฐ Time: {datetime.now().strftime('%H:%M:%S')}")
+            # notification_text_parts.append("\nโœ… Position now tracked. Use /sl or /tp if needed.")
             
-            await self.notification_manager.send_generic_notification(notification_text)
+            await self.notification_manager.send_generic_notification("\n".join(notification_text_parts))
             logger.info(f"๐Ÿ“ค STARTUP: Sent auto-sync notification for {symbol} (Lifecycle: {lifecycle_id[:8]}).")
             
         except Exception as e:

+ 102 - 19
src/notifications/notification_manager.py

@@ -6,7 +6,8 @@ Notification Manager - Handles all bot notifications and messages.
 import logging
 from typing import Optional, Dict, Any, List
 from datetime import datetime
-from src.utils.price_formatter import get_formatter
+from src.config.config import Config
+from src.utils.token_display_formatter import get_formatter # Import the global formatter
 
 logger = logging.getLogger(__name__)
 
@@ -24,16 +25,16 @@ class NotificationManager:
     async def send_long_success_notification(self, query, token, amount, price, order_details, stop_loss_price=None, trade_lifecycle_id=None):
         """Send notification for successful long order."""
         try:
-            # Use PriceFormatter for consistent formatting
+            # Use TokenDisplayFormatter for consistent formatting
             formatter = get_formatter() # Get formatter
             
             price_str = formatter.format_price_with_symbol(price, token)
-            amount_str = f"{amount:.6f} {token}"
+            amount_str = formatter.format_amount(amount, token)
             value_str = formatter.format_price_with_symbol(amount * price, token)
             order_id_str = order_details.get('id', 'N/A')
             
             message = (
-                f"โœ… Successfully opened <b>LONG</b> position for {amount_str} at ~{price_str}\n\n"
+                f"โœ… Successfully opened <b>LONG</b> position for {amount_str} {token} at ~{price_str}\n\n"
                 f"๐Ÿ’ฐ Value: {value_str}\n"
                 f"๐Ÿ†” Order ID: <code>{order_id_str}</code>"
             )
@@ -54,12 +55,12 @@ class NotificationManager:
             formatter = get_formatter() # Get formatter
             
             price_str = formatter.format_price_with_symbol(price, token)
-            amount_str = f"{amount:.6f} {token}"
+            amount_str = formatter.format_amount(amount, token)
             value_str = formatter.format_price_with_symbol(amount * price, token)
             order_id_str = order_details.get('id', 'N/A')
             
             message = (
-                f"โœ… Successfully opened <b>SHORT</b> position for {amount_str} at ~{price_str}\n\n"
+                f"โœ… Successfully opened <b>SHORT</b> position for {amount_str} {token} at ~{price_str}\n\n"
                 f"๐Ÿ’ฐ Value: {value_str}\n"
                 f"๐Ÿ†” Order ID: <code>{order_id_str}</code>"
             )
@@ -82,14 +83,14 @@ class NotificationManager:
             # Price is the execution price, PnL is calculated based on it
             # For market orders, price might be approximate or from fill later
             price_str = formatter.format_price_with_symbol(price, token) if price > 0 else "Market Price"
-            amount_str = f"{amount:.6f} {token}"
+            amount_str = formatter.format_amount(amount, token)
             pnl_str = formatter.format_price_with_symbol(pnl)
             pnl_emoji = "๐ŸŸข" if pnl >= 0 else "๐Ÿ”ด"
             order_id_str = order_details.get('id', 'N/A')
             cancelled_sl_count = order_details.get('cancelled_stop_losses', 0)
             
             message = (
-                f"โœ… Successfully closed <b>{position_type}</b> position for {amount_str}\n\n"
+                f"โœ… Successfully closed <b>{position_type}</b> position for {amount_str} {token}\n\n"
                 f"๐Ÿ†” Exit Order ID: <code>{order_id_str}</code>\n"
                 # P&L and price are more reliably determined when MarketMonitor processes the fill.
                 # This notification confirms the exit order was PLACED.
@@ -111,11 +112,11 @@ class NotificationManager:
             formatter = get_formatter() # Get formatter
             
             stop_price_str = formatter.format_price_with_symbol(stop_price, token)
-            amount_str = f"{amount:.6f} {token}"
+            amount_str = formatter.format_amount(amount, token)
             order_id_str = order_details.get('exchange_order_id', 'N/A') # From order_placed_details
             
             message = (
-                f"๐Ÿ›‘ Successfully set <b>STOP LOSS</b> for {position_type} {amount_str}\n\n"
+                f"๐Ÿ›‘ Successfully set <b>STOP LOSS</b> for {position_type} {amount_str} {token}\n\n"
                 f"๐ŸŽฏ Trigger Price: {stop_price_str}\n"
                 f"๐Ÿ†” SL Order ID: <code>{order_id_str}</code>"
             )
@@ -132,11 +133,11 @@ class NotificationManager:
             formatter = get_formatter() # Get formatter
             
             tp_price_str = formatter.format_price_with_symbol(tp_price, token)
-            amount_str = f"{amount:.6f} {token}"
+            amount_str = formatter.format_amount(amount, token)
             order_id_str = order_details.get('exchange_order_id', 'N/A') # From order_placed_details
 
             message = (
-                f"๐ŸŽฏ Successfully set <b>TAKE PROFIT</b> for {position_type} {amount_str}\n\n"
+                f"๐ŸŽฏ Successfully set <b>TAKE PROFIT</b> for {position_type} {amount_str} {token}\n\n"
                 f"๐Ÿ’ฐ Target Price: {tp_price_str}\n"
                 f"๐Ÿ†” TP Order ID: <code>{order_id_str}</code>"
             )
@@ -170,13 +171,21 @@ class NotificationManager:
             success_message += f"""
 
 ๐Ÿ—‘๏ธ <b>Successfully Cancelled:</b>"""
+            formatter = get_formatter() # Get formatter for loop
             for order in cancelled_orders:
                 side = order.get('side', 'Unknown')
                 amount = order.get('amount', 0)
                 price = order.get('price', 0)
+                # Assuming 'token' is the common symbol for all these orders
+                # If individual orders can have different tokens, order dict should contain 'symbol' or 'token' key
+                order_token_symbol = order.get('symbol', token) # Fallback to main token if not in order dict
+
+                amount_str = formatter.format_amount(amount, order_token_symbol)
+                price_str = formatter.format_price_with_symbol(price, order_token_symbol) if price > 0 else "N/A"
+                
                 side_emoji = "๐ŸŸข" if side.lower() == 'buy' else "๐Ÿ”ด"
                 success_message += f"""
-{side_emoji} {side.upper()} {amount} @ ${price:,.2f}"""
+{side_emoji} {side.upper()} {amount_str} {order_token_symbol} @ {price_str}"""
         
         # Overall status
         if cancelled_count == (cancelled_count + failed_count) and failed_count == 0:
@@ -211,13 +220,18 @@ class NotificationManager:
         
         direction_emoji = "๐Ÿ“ˆ" if direction == 'above' else "๐Ÿ“‰"
         
+        formatter = get_formatter() # Get formatter
+        
+        target_price_str = formatter.format_price_with_symbol(target_price, token)
+        current_price_str = formatter.format_price_with_symbol(current_price, token)
+
         alarm_message = f"""
 ๐Ÿ”” <b>Price Alarm Triggered!</b>
 
 {direction_emoji} <b>Alert Details:</b>
 โ€ข Token: {token}
-โ€ข Target Price: ${target_price:,.2f}
-โ€ข Current Price: ${current_price:,.2f}
+โ€ข Target Price: {target_price_str}
+โ€ข Current Price: {current_price_str}
 โ€ข Direction: {direction.upper()}
 
 โฐ <b>Trigger Time:</b> {datetime.now().strftime('%H:%M:%S')}
@@ -230,7 +244,6 @@ class NotificationManager:
         """
         
         try:
-            from src.config.config import Config
             if Config.TELEGRAM_CHAT_ID:
                 await self.bot_application.bot.send_message(
                     chat_id=Config.TELEGRAM_CHAT_ID,
@@ -328,7 +341,6 @@ class NotificationManager:
             """
         
         try:
-            from src.config.config import Config
             if Config.TELEGRAM_CHAT_ID:
                 await self.bot_application.bot.send_message(
                     chat_id=Config.TELEGRAM_CHAT_ID,
@@ -346,7 +358,6 @@ class NotificationManager:
             return
         
         try:
-            from src.config.config import Config
             if Config.TELEGRAM_CHAT_ID:
                 await self.bot_application.bot.send_message(
                     chat_id=Config.TELEGRAM_CHAT_ID,
@@ -444,4 +455,76 @@ class NotificationManager:
             logger.info(f"๐Ÿ›‘ Stop loss execution notification sent: {token} {position_side} @ ${price:.2f}")
             
         except Exception as e:
-            logger.error(f"โŒ Error sending stop loss execution notification: {e}") 
+            logger.error(f"โŒ Error sending stop loss execution notification: {e}")
+
+    async def send_take_profit_execution_notification(self, tp_info: Dict, symbol: str, side: str, amount: float, price: float, action_type: str, timestamp: str):
+        """Send notification for external take profit execution."""
+        try:
+            token = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
+            formatter = get_formatter()
+
+            trigger_price = tp_info.get('trigger_price', price) # Actual TP price
+            position_side = tp_info.get('position_side', 'unknown')
+            entry_price = tp_info.get('entry_price', 0)
+
+            try:
+                time_obj = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
+                time_str = time_obj.strftime('%H:%M:%S')
+            except:
+                time_str = "Unknown"
+
+            pnl_info = ""
+            if entry_price > 0:
+                if position_side == 'long':
+                    pnl = amount * (price - entry_price)
+                    pnl_percent = ((price - entry_price) / entry_price) * 100 if entry_price != 0 else 0
+                else: # short
+                    pnl = amount * (entry_price - price)
+                    pnl_percent = ((entry_price - price) / entry_price) * 100 if entry_price != 0 else 0
+                
+                pnl_emoji = "๐ŸŸข" if pnl >= 0 else "๐Ÿ”ด"
+                pnl_info = f"""
+{pnl_emoji} <b>Take Profit P&L:</b>
+โ€ข Entry Price: {formatter.format_price_with_symbol(entry_price, token)}
+โ€ข Exit Price: {formatter.format_price_with_symbol(price, token)}
+โ€ข Realized P&L: {formatter.format_price_with_symbol(pnl)} ({pnl_percent:+.2f}%)
+โ€ข Result: {"PROFIT SECURED" if pnl >=0 else "MINIMIZED LOSS"}"""
+            
+            trade_value = amount * price
+            position_emoji = "๐Ÿ“ˆ" if position_side == 'long' else "๐Ÿ“‰"
+
+            message = f"""
+๐ŸŽฏ <b>TAKE PROFIT EXECUTED</b>
+
+{position_emoji} <b>{position_side.upper()} Position Profit Secured:</b>
+โ€ข Token: {token}
+โ€ข Position Type: {position_side.upper()}
+โ€ข Take Profit Size: {amount} {token}
+โ€ข Target Price: {formatter.format_price_with_symbol(trigger_price, token)}
+โ€ข Execution Price: {formatter.format_price_with_symbol(price, token)}
+โ€ข Exit Value: {formatter.format_price_with_symbol(trade_value, token)}
+
+โœ… <b>Take Profit Details:</b>
+โ€ข Status: EXECUTED
+โ€ข Order Side: {side.upper()}
+โ€ข Action Type: {action_type.replace('_', ' ').title()}
+
+{pnl_info}
+
+โฐ <b>Execution Time:</b> {time_str}
+๐Ÿค– <b>Source:</b> External Hyperliquid Order
+๐Ÿ“Š <b>Risk Management:</b> Profit successfully secured
+
+๐Ÿ’ก Your take profit order worked as intended!
+            """
+
+            if self.bot_application:
+                if Config.TELEGRAM_CHAT_ID:
+                    await self.bot_application.bot.send_message(
+                        chat_id=Config.TELEGRAM_CHAT_ID,
+                        text=message,
+                        parse_mode='HTML'
+                    )
+                    logger.info(f"Take Profit execution notification sent for {token}")
+        except Exception as e:
+            logger.error(f"Error sending Take Profit execution notification: {e}", exc_info=True) 

+ 27 - 15
src/trading/trading_engine.py

@@ -13,7 +13,8 @@ import uuid # For generating unique bot_order_ref_ids
 from src.config.config import Config
 from src.clients.hyperliquid_client import HyperliquidClient
 from src.trading.trading_stats import TradingStats
-from src.utils.price_formatter import set_global_trading_engine
+from src.utils.token_display_formatter import set_global_trading_engine, get_formatter
+from telegram.ext import CallbackContext
 
 logger = logging.getLogger(__name__)
 
@@ -237,6 +238,7 @@ class TradingEngine:
                                 limit_price_arg: Optional[float] = None,
                                 stop_loss_price: Optional[float] = None) -> Dict[str, Any]:
         symbol = f"{token}/USDC:USDC"
+        formatter = get_formatter() # Get formatter
         
         try:
             # Validate inputs
@@ -284,10 +286,11 @@ class TradingEngine:
 
             # 2. Place the order with the exchange
             if order_type_for_stats == 'limit':
-                logger.info(f"Placing LIMIT BUY order ({bot_order_ref_id}) for {token_amount:.6f} {symbol} at ${order_placement_price:,.2f}")
+                # Use token for formatting, symbol for logging context
+                logger.info(f"Placing LIMIT BUY order ({bot_order_ref_id}) for {formatter.format_amount(token_amount, token)} {symbol} at {formatter.format_price_with_symbol(order_placement_price, token)}")
                 exchange_order_data, error_msg = self.client.place_limit_order(symbol, 'buy', token_amount, order_placement_price)
             else: # Market order
-                logger.info(f"Placing MARKET BUY order ({bot_order_ref_id}) for {token_amount:.6f} {symbol} (approx. price ${order_placement_price:,.2f})")
+                logger.info(f"Placing MARKET BUY order ({bot_order_ref_id}) for {formatter.format_amount(token_amount, token)} {symbol} (approx. price {formatter.format_price_with_symbol(order_placement_price, token)})")
                 exchange_order_data, error_msg = self.client.place_market_order(symbol, 'buy', token_amount)
             
             if error_msg:
@@ -395,6 +398,7 @@ class TradingEngine:
                                  limit_price_arg: Optional[float] = None,
                                  stop_loss_price: Optional[float] = None) -> Dict[str, Any]:
         symbol = f"{token}/USDC:USDC"
+        formatter = get_formatter() # Get formatter
         
         try:
             if usdc_amount <= 0:
@@ -438,10 +442,10 @@ class TradingEngine:
 
             # 2. Place the order with the exchange
             if order_type_for_stats == 'limit':
-                logger.info(f"Placing LIMIT SELL order ({bot_order_ref_id}) for {token_amount:.6f} {symbol} at ${order_placement_price:,.2f}")
+                logger.info(f"Placing LIMIT SELL order ({bot_order_ref_id}) for {formatter.format_amount(token_amount, token)} {symbol} at {formatter.format_price_with_symbol(order_placement_price, token)}")
                 exchange_order_data, error_msg = self.client.place_limit_order(symbol, 'sell', token_amount, order_placement_price)
             else: # Market order
-                logger.info(f"Placing MARKET SELL order ({bot_order_ref_id}) for {token_amount:.6f} {symbol} (approx. price ${order_placement_price:,.2f})")
+                logger.info(f"Placing MARKET SELL order ({bot_order_ref_id}) for {formatter.format_amount(token_amount, token)} {symbol} (approx. price {formatter.format_price_with_symbol(order_placement_price, token)})")
                 exchange_order_data, error_msg = self.client.place_market_order(symbol, 'sell', token_amount)
             
             if error_msg:
@@ -542,6 +546,7 @@ class TradingEngine:
         if not position:
             return {"success": False, "error": f"No open position found for {token}"}
         
+        formatter = get_formatter() # Get formatter
         try:
             symbol = f"{token}/USDC:USDC"
             position_type, exit_side, contracts_to_close = self.get_position_direction(position)
@@ -561,7 +566,7 @@ class TradingEngine:
                 return {"success": False, "error": "Failed to record exit order intent in database."}
 
             # 2. Execute market order to close position
-            logger.info(f"Placing MARKET {exit_side.upper()} order ({bot_order_ref_id}) to close {contracts_to_close:.6f} {symbol}")
+            logger.info(f"Placing MARKET {exit_side.upper()} order ({bot_order_ref_id}) to close {formatter.format_amount(contracts_to_close, token)} {symbol}")
             exchange_order_data, error_msg = self.client.place_market_order(symbol, exit_side, contracts_to_close)
             
             if error_msg:
@@ -640,6 +645,7 @@ class TradingEngine:
         if not position:
             return {"success": False, "error": f"No open position found for {token}"}
         
+        formatter = get_formatter() # Get formatter
         try:
             symbol = f"{token}/USDC:USDC"
             position_type, exit_side, contracts = self.get_position_direction(position)
@@ -666,7 +672,7 @@ class TradingEngine:
                 return {"success": False, "error": "Failed to record SL order intent in database."}
 
             # 2. Place limit order at stop loss price
-            logger.info(f"Placing STOP LOSS (LIMIT {exit_side.upper()}) order ({bot_order_ref_id}) for {contracts:.6f} {symbol} at ${stop_price:,.2f}")
+            logger.info(f"Placing STOP LOSS (LIMIT {exit_side.upper()}) order ({bot_order_ref_id}) for {formatter.format_amount(contracts, token)} {symbol} at {formatter.format_price_with_symbol(stop_price, token)}")
             exchange_order_data, error_msg = self.client.place_limit_order(symbol, exit_side, contracts, stop_price)
             
             if error_msg:
@@ -730,6 +736,7 @@ class TradingEngine:
         if not position:
             return {"success": False, "error": f"No open position found for {token}"}
         
+        formatter = get_formatter() # Get formatter
         try:
             symbol = f"{token}/USDC:USDC"
             position_type, exit_side, contracts = self.get_position_direction(position)
@@ -756,7 +763,7 @@ class TradingEngine:
                 return {"success": False, "error": "Failed to record TP order intent in database."}
 
             # 2. Place limit order at take profit price
-            logger.info(f"Placing TAKE PROFIT (LIMIT {exit_side.upper()}) order ({bot_order_ref_id}) for {contracts:.6f} {symbol} at ${profit_price:,.2f}")
+            logger.info(f"Placing TAKE PROFIT (LIMIT {exit_side.upper()}) order ({bot_order_ref_id}) for {formatter.format_amount(contracts, token)} {symbol} at {formatter.format_price_with_symbol(profit_price, token)}")
             exchange_order_data, error_msg = self.client.place_limit_order(symbol, exit_side, contracts, profit_price)
             
             if error_msg:
@@ -958,6 +965,7 @@ class TradingEngine:
         if not self.stats:
             return {"success": False, "error": "TradingStats not available."}
 
+        formatter = get_formatter() # Get formatter
         trigger_order_details = self.stats.get_order_by_db_id(original_trigger_order_db_id)
         if not trigger_order_details:
             return {"success": False, "error": f"Original trigger order DB ID {original_trigger_order_db_id} not found."}
@@ -992,22 +1000,26 @@ class TradingEngine:
         order_price = stop_price  # Default to stop price
 
         if current_price and current_price > 0:
+            token = symbol.split('/')[0] if symbol else "TOKEN" # Extract token for formatter
+            current_price_str = formatter.format_price_with_symbol(current_price, token)
+            stop_price_str = formatter.format_price_with_symbol(stop_price, token)
+
             if sl_order_side.lower() == 'buy':
                 # SHORT position stop loss (BUY to close)
                 # If current price > stop price, use market order (price moved beyond stop)
                 if current_price > stop_price:
                     use_market_order = True
-                    logger.warning(f"๐Ÿšจ SHORT SL: Price ${current_price:.4f} > Stop ${stop_price:.4f} - Using MARKET order for immediate execution")
+                    logger.warning(f"๐Ÿšจ SHORT SL: Price {current_price_str} > Stop {stop_price_str} - Using MARKET order for immediate execution")
                 else:
-                    logger.info(f"๐Ÿ“Š SHORT SL: Price ${current_price:.4f} โ‰ค Stop ${stop_price:.4f} - Using LIMIT order at stop price")
+                    logger.info(f"๐Ÿ“Š SHORT SL: Price {current_price_str} โ‰ค Stop {stop_price_str} - Using LIMIT order at stop price")
             elif sl_order_side.lower() == 'sell':
                 # LONG position stop loss (SELL to close)
                 # If current price < stop price, use market order (price moved beyond stop)
                 if current_price < stop_price:
                     use_market_order = True
-                    logger.warning(f"๐Ÿšจ LONG SL: Price ${current_price:.4f} < Stop ${stop_price:.4f} - Using MARKET order for immediate execution")
+                    logger.warning(f"๐Ÿšจ LONG SL: Price {current_price_str} < Stop {stop_price_str} - Using MARKET order for immediate execution")
                 else:
-                    logger.info(f"๐Ÿ“Š LONG SL: Price ${current_price:.4f} โ‰ฅ Stop ${stop_price:.4f} - Using LIMIT order at stop price")
+                    logger.info(f"๐Ÿ“Š LONG SL: Price {current_price_str} โ‰ฅ Stop {stop_price_str} - Using LIMIT order at stop price")
 
         if use_market_order:
             order_type_for_actual_sl = 'market'
@@ -1034,10 +1046,10 @@ class TradingEngine:
 
         # 2. Place the actual SL order on the exchange
         if use_market_order:
-            logger.info(f"๐Ÿšจ Placing ACTUAL SL ORDER (MARKET {sl_order_side.upper()}) from trigger {original_trigger_order_db_id}. New BotRef: {actual_sl_bot_order_ref_id}, Amount: {amount}, Trigger was: ${stop_price}")
+            logger.info(f"๐Ÿšจ Placing ACTUAL SL ORDER (MARKET {sl_order_side.upper()}) from trigger {original_trigger_order_db_id}. New BotRef: {actual_sl_bot_order_ref_id}, Amount: {formatter.format_amount(amount, token)}, Trigger was: {formatter.format_price_with_symbol(stop_price, token)}")
             exchange_order_data, error_msg = self.client.place_market_order(symbol, sl_order_side, amount)
         else:
-            logger.info(f"๐Ÿ“Š Placing ACTUAL SL ORDER (LIMIT {sl_order_side.upper()}) from trigger {original_trigger_order_db_id}. New BotRef: {actual_sl_bot_order_ref_id}, Amount: {amount}, Price: ${stop_price}")
+            logger.info(f"๐Ÿ“Š Placing ACTUAL SL ORDER (LIMIT {sl_order_side.upper()}) from trigger {original_trigger_order_db_id}. New BotRef: {actual_sl_bot_order_ref_id}, Amount: {formatter.format_amount(amount, token)}, Price: {formatter.format_price_with_symbol(stop_price, token)}")
             exchange_order_data, error_msg = self.client.place_limit_order(symbol, sl_order_side, amount, stop_price)
 
         if error_msg:
@@ -1070,7 +1082,7 @@ class TradingEngine:
         if use_market_order:
             success_message += " for immediate execution (price moved beyond stop level)."
         else:
-            success_message += f" at ${stop_price}."
+            success_message += f" at {formatter.format_price_with_symbol(stop_price, token)}."
 
         return {
             "success": True,

+ 88 - 58
src/trading/trading_stats.py

@@ -17,6 +17,7 @@ import numpy as np # Ensure numpy is imported as np
 
 # ๐Ÿ†• Import the migration runner
 from src.migrations.migrate_db import run_migrations as run_db_migrations
+from src.utils.token_display_formatter import get_formatter # Added import
 
 logger = logging.getLogger(__name__)
 
@@ -247,9 +248,11 @@ class TradingStats:
             # Also set start_date if it's the first time setting balance
             if self._get_metadata('start_date') is None or float(current_initial_balance_str if current_initial_balance_str else '0.0') == 0.0:
                  self._set_metadata('start_date', datetime.now(timezone.utc).isoformat())
-            logger.info(f"Initial balance set to: ${balance:.2f}")
+            formatter = get_formatter()
+            logger.info(f"Initial balance set to: {formatter.format_price_with_symbol(balance)}")
         else:
-            logger.info(f"Initial balance already set to ${current_initial_balance:.2f}. Not changing.")
+            formatter = get_formatter()
+            logger.info(f"Initial balance already set to {formatter.format_price_with_symbol(current_initial_balance)}. Not changing.")
 
 
     def record_balance(self, balance: float):
@@ -279,11 +282,29 @@ class TradingStats:
             "INSERT OR IGNORE INTO trades (symbol, side, amount, price, value, trade_type, timestamp, exchange_fill_id, pnl, linked_order_table_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
             (symbol, side, amount, price, value, trade_type, timestamp, exchange_fill_id, pnl or 0.0, linked_order_table_id_to_link)
         )
-        logger.info(f"๐Ÿ“ˆ Trade recorded: {side.upper()} {amount:.6f} {symbol} @ ${price:.2f} (${value:.2f}) [{trade_type}]")
+        formatter = get_formatter()
+        # Assuming symbol's base asset for amount formatting. If symbol is like BTC/USDT, base is BTC.
+        base_asset_for_amount = symbol.split('/')[0] if '/' in symbol else symbol 
+        logger.info(f"๐Ÿ“ˆ Trade recorded: {side.upper()} {formatter.format_amount(amount, base_asset_for_amount)} {symbol} @ {formatter.format_price(price, symbol)} ({formatter.format_price(value, symbol)}) [{trade_type}]")
 
     def get_all_trades(self) -> List[Dict[str, Any]]:
         """Fetch all trades from the database, ordered by timestamp."""
         return self._fetch_query("SELECT * FROM trades ORDER BY timestamp ASC")
+
+    def get_trade_by_symbol_and_status(self, symbol: str, status: str) -> Optional[Dict[str, Any]]:
+        """
+        Fetches a single trade record for a given symbol and status.
+        Typically used to find an open position master record.
+        Assumes that for a given symbol, there's at most one trade record with a specific
+        active status like 'position_opened'. If multiple could exist, this fetches the most recent.
+        """
+        query = "SELECT * FROM trades WHERE symbol = ? AND status = ? ORDER BY id DESC LIMIT 1"
+        trade = self._fetchone_query(query, (symbol, status))
+        if trade:
+            logger.debug(f"Found trade for {symbol} with status {status}: ID {trade.get('id')}")
+        # else: # Can be noisy if not finding a trade is a common occurrence
+            # logger.debug(f"No trade found for {symbol} with status {status}")
+        return trade
         
     def calculate_completed_trade_cycles(self) -> List[Dict[str, Any]]:
         """
@@ -620,73 +641,65 @@ class TradingStats:
         """Format stats for Telegram display using data from DB."""
         try:
             stats = self.get_comprehensive_stats(current_balance)
+            formatter = get_formatter() # Get formatter
             
             basic = stats['basic']
             perf = stats['performance']
-            # risk = stats['risk'] # Risk metrics not directly used in this message format previously
-
-            # Use current_balance passed or derived in get_comprehensive_stats
+            
             effective_current_balance = stats['current_balance']
             initial_bal = basic['initial_balance']
 
-            # Total P&L should reflect current worth vs initial, including open positions if current_balance is live
-            total_pnl_val = effective_current_balance - initial_bal if initial_bal > 0 else basic['total_pnl'] # Fallback to closed PNL
+            total_pnl_val = effective_current_balance - initial_bal if initial_bal > 0 else basic['total_pnl']
             total_return_pct = (total_pnl_val / initial_bal * 100) if initial_bal > 0 else 0.0
             
             pnl_emoji = "๐ŸŸข" if total_pnl_val >= 0 else "๐Ÿ”ด"
             
             open_positions_count = self._get_open_positions_count_from_db()
 
-            # Calculate trade volume and average trade size from 'trades' table for sell orders
             sell_trades_data = self._fetch_query("SELECT value FROM trades WHERE side = 'sell'")
             total_sell_volume = sum(t['value'] for t in sell_trades_data)
             avg_trade_size_sell = (total_sell_volume / len(sell_trades_data)) if sell_trades_data else 0.0
             
             adjustments_summary = self.get_balance_adjustments_summary()
 
-            stats_text = f"""๐Ÿ“Š <b>Trading Statistics</b>
-
-๐Ÿ’ฐ <b>Account Overview:</b>
-โ€ข Current Balance: ${effective_current_balance:,.2f}
-โ€ข Initial Balance: ${initial_bal:,.2f}
-โ€ข {pnl_emoji} Total P&L: ${total_pnl_val:,.2f} ({total_return_pct:+.2f}%)
-
-๐Ÿ“ˆ <b>Trading Activity:</b>
-โ€ข Total Orders: {basic['total_trades']}
-โ€ข Completed Trades (Cycles): {basic['completed_trades']}
-โ€ข Open Positions: {open_positions_count}
-โ€ข Days Active: {basic['days_active']}
+            # Main stats text block
+            stats_text_parts = []
+            stats_text_parts.append(f"๐Ÿ“Š <b>Trading Statistics</b>\n")
+            stats_text_parts.append(f"\n๐Ÿ’ฐ <b>Account Overview:</b>")
+            stats_text_parts.append(f"โ€ข Current Balance: {formatter.format_price_with_symbol(effective_current_balance)}")
+            stats_text_parts.append(f"โ€ข Initial Balance: {formatter.format_price_with_symbol(initial_bal)}")
+            stats_text_parts.append(f"โ€ข {pnl_emoji} Total P&L: {formatter.format_price_with_symbol(total_pnl_val)} ({total_return_pct:+.2f}%)\n")
+            stats_text_parts.append(f"\n๐Ÿ“ˆ <b>Trading Activity:</b>")
+            stats_text_parts.append(f"โ€ข Total Orders: {basic['total_trades']}")
+            stats_text_parts.append(f"โ€ข Completed Trades (Cycles): {basic['completed_trades']}")
+            stats_text_parts.append(f"โ€ข Open Positions: {open_positions_count}")
+            stats_text_parts.append(f"โ€ข Days Active: {basic['days_active']}\n")
+            stats_text_parts.append(f"\n๐Ÿ† <b>Performance Metrics:</b>")
+            stats_text_parts.append(f"โ€ข Win Rate: {perf['win_rate']:.1f}% ({perf['total_wins']}W/{perf['total_losses']}L)")
+            stats_text_parts.append(f"โ€ข Profit Factor: {perf['profit_factor']:.2f}")
+            stats_text_parts.append(f"โ€ข Avg Win: {formatter.format_price_with_symbol(perf['avg_win'])} | Avg Loss: {formatter.format_price_with_symbol(perf['avg_loss'])}")
+            stats_text_parts.append(f"โ€ข Largest Win: {formatter.format_price_with_symbol(perf['largest_win'])} | Largest Loss: {formatter.format_price_with_symbol(perf['largest_loss'])}")
 
-๐Ÿ† <b>Performance Metrics:</b>
-โ€ข Win Rate: {perf['win_rate']:.1f}% ({perf['total_wins']}W/{perf['total_losses']}L)
-โ€ข Profit Factor: {perf['profit_factor']:.2f}
-โ€ข Avg Win: ${perf['avg_win']:.2f} | Avg Loss: ${perf['avg_loss']:.2f}
-โ€ข Largest Win: ${perf['largest_win']:.2f} | Largest Loss: ${perf['largest_loss']:.2f}
-"""
             if adjustments_summary['adjustment_count'] > 0:
                 adj_emoji = "๐Ÿ’ฐ" if adjustments_summary['net_adjustment'] >= 0 else "๐Ÿ’ธ"
-                stats_text += f"""
-๐Ÿ’ฐ <b>Balance Adjustments:</b>
-โ€ข Deposits: ${adjustments_summary['total_deposits']:,.2f}
-โ€ข Withdrawals: ${adjustments_summary['total_withdrawals']:,.2f}
-โ€ข {adj_emoji} Net: ${adjustments_summary['net_adjustment']:,.2f} ({adjustments_summary['adjustment_count']} transactions)
-"""
+                stats_text_parts.append(f"\n\n๐Ÿ’ฐ <b>Balance Adjustments:</b>")
+                stats_text_parts.append(f"โ€ข Deposits: {formatter.format_price_with_symbol(adjustments_summary['total_deposits'])}")
+                stats_text_parts.append(f"โ€ข Withdrawals: {formatter.format_price_with_symbol(adjustments_summary['total_withdrawals'])}")
+                stats_text_parts.append(f"โ€ข {adj_emoji} Net: {formatter.format_price_with_symbol(adjustments_summary['net_adjustment'])} ({adjustments_summary['adjustment_count']} transactions)")
+            
+            stats_text_parts.append(f"\n\n๐ŸŽฏ <b>Trade Distribution:</b>")
+            stats_text_parts.append(f"โ€ข Buy Orders: {basic['buy_trades']} | Sell Orders: {basic['sell_trades']}")
+            stats_text_parts.append(f"โ€ข Volume Traded (Sells): {formatter.format_price_with_symbol(total_sell_volume)}")
+            stats_text_parts.append(f"โ€ข Avg Sell Trade Size: {formatter.format_price_with_symbol(avg_trade_size_sell)}\n")
+            stats_text_parts.append(f"\nโฐ <b>Session Info:</b>")
+            stats_text_parts.append(f"โ€ข Started: {basic['start_date']}")
+            stats_text_parts.append(f"โ€ข Last Update: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}")
             
-            stats_text += f"""
-๐ŸŽฏ <b>Trade Distribution:</b>
-โ€ข Buy Orders: {basic['buy_trades']} | Sell Orders: {basic['sell_trades']}
-โ€ข Volume Traded (Sells): ${total_sell_volume:,.2f}
-โ€ข Avg Sell Trade Size: ${avg_trade_size_sell:.2f}
-
-โฐ <b>Session Info:</b>
-โ€ข Started: {basic['start_date']}
-โ€ข Last Update: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}
-            """ # Changed Last Update format
-            return stats_text.strip()
+            return "\n".join(stats_text_parts).strip()
                 
         except Exception as e:
             logger.error(f"Error formatting stats message: {e}", exc_info=True)
-            return f"๐Ÿ“Š <b>Trading Statistics</b>\n\nโŒ <b>Error loading statistics</b>\n\n๐Ÿ”ง <b>Debug info:</b> {str(e)[:100]}"
+            return f"""๐Ÿ“Š <b>Trading Statistics</b>\n\nโŒ <b>Error loading statistics</b>\n\n๐Ÿ”ง <b>Debug info:</b> {str(e)[:100]}"""
 
     def get_recent_trades(self, limit: int = 10) -> List[Dict[str, Any]]:
         """Get recent trades from DB."""
@@ -939,31 +952,34 @@ class TradingStats:
                        deposit_id: Optional[str] = None, description: Optional[str] = None):
         """Record a deposit."""
         ts = timestamp if timestamp else datetime.now(timezone.utc).isoformat()
-        desc = description if description else f'Deposit of ${amount:.2f}'
+        formatter = get_formatter()
+        formatted_amount_str = formatter.format_price_with_symbol(amount)
+        desc = description if description else f'Deposit of {formatted_amount_str}'
         
         self._execute_query(
             "INSERT INTO balance_adjustments (adjustment_id, timestamp, type, amount, description) VALUES (?, ?, ?, ?, ?)",
-            (deposit_id, ts, 'deposit', amount, desc)
+            (deposit_id or str(uuid.uuid4()), ts, 'deposit', amount, desc) # Ensured uuid is string
         )
         # Adjust initial_balance in metadata to reflect capital changes
         current_initial = float(self._get_metadata('initial_balance') or '0.0')
         self._set_metadata('initial_balance', str(current_initial + amount))
-        logger.info(f"๐Ÿ’ฐ Recorded deposit: ${amount:.2f}. New effective initial balance: ${current_initial + amount:.2f}")
+        logger.info(f"๐Ÿ’ฐ Recorded deposit: {formatted_amount_str}. New effective initial balance: {formatter.format_price_with_symbol(current_initial + amount)}")
 
     def record_withdrawal(self, amount: float, timestamp: Optional[str] = None, 
                           withdrawal_id: Optional[str] = None, description: Optional[str] = None):
         """Record a withdrawal."""
         ts = timestamp if timestamp else datetime.now(timezone.utc).isoformat()
-        desc = description if description else f'Withdrawal of ${amount:.2f}'
+        formatter = get_formatter()
+        formatted_amount_str = formatter.format_price_with_symbol(amount)
+        desc = description if description else f'Withdrawal of {formatted_amount_str}'
         
         self._execute_query(
             "INSERT INTO balance_adjustments (adjustment_id, timestamp, type, amount, description) VALUES (?, ?, ?, ?, ?)",
-            (withdrawal_id, ts, 'withdrawal', amount, desc) # Store positive amount, type indicates withdrawal
+            (withdrawal_id or str(uuid.uuid4()), ts, 'withdrawal', amount, desc) # Ensured uuid is string
         )
         current_initial = float(self._get_metadata('initial_balance') or '0.0')
         self._set_metadata('initial_balance', str(current_initial - amount))
-        logger.info(f"๐Ÿ’ธ Recorded withdrawal: ${amount:.2f}. New effective initial balance: ${current_initial - amount:.2f}")
-
+        logger.info(f"๐Ÿ’ธ Recorded withdrawal: {formatted_amount_str}. New effective initial balance: {formatter.format_price_with_symbol(current_initial - amount)}")
 
     def get_balance_adjustments_summary(self) -> Dict[str, Any]:
         """Get summary of all balance adjustments from DB."""
@@ -1083,7 +1099,7 @@ class TradingStats:
 
         if not set_clauses:
             logger.info("No fields to update for order.")
-            return True # Or False if an update was expected
+            return True # No update needed, not an error
 
         set_clauses.append("timestamp_updated = ?")
         params.append(now_iso)
@@ -1093,7 +1109,7 @@ class TradingStats:
         query = f"UPDATE orders SET { ', '.join(set_clauses) } WHERE {identifier_clause}"
         
         try:
-            self._execute_query(query, params)
+            self._execute_query(query, tuple(params))
             log_msg = f"Updated order ({identifier_clause}={identifier_param}): Status to '{new_status or 'N/A'}', Filled increment {amount_filled_increment or 0.0}"
             if set_exchange_order_id is not None:
                 log_msg += f", Exchange ID set to '{set_exchange_order_id}'"
@@ -1319,7 +1335,12 @@ class TradingStats:
             
             self._execute_query(query, params)
             
-            logger.info(f"๐Ÿ“ˆ Trade lifecycle {lifecycle_id} position opened: {entry_amount} @ ${entry_price:.2f}")
+            formatter = get_formatter()
+            trade_info = self.get_trade_by_lifecycle_id(lifecycle_id) # Fetch to get symbol for formatting
+            symbol_for_formatting = trade_info.get('symbol', 'UNKNOWN_SYMBOL') if trade_info else 'UNKNOWN_SYMBOL'
+            base_asset_for_amount = symbol_for_formatting.split('/')[0] if '/' in symbol_for_formatting else symbol_for_formatting
+
+            logger.info(f"๐Ÿ“ˆ Trade lifecycle {lifecycle_id} position opened: {formatter.format_amount(entry_amount, base_asset_for_amount)} {symbol_for_formatting} @ {formatter.format_price(entry_price, symbol_for_formatting)}")
             return True
             
         except Exception as e:
@@ -1345,8 +1366,11 @@ class TradingStats:
             
             self._execute_query(query, params)
             
+            formatter = get_formatter()
+            trade_info = self.get_trade_by_lifecycle_id(lifecycle_id) # Fetch to get symbol for P&L formatting context
+            symbol_for_formatting = trade_info.get('symbol', 'USD') # Default to USD for PNL if symbol unknown
             pnl_emoji = "๐ŸŸข" if realized_pnl >= 0 else "๐Ÿ”ด"
-            logger.info(f"{pnl_emoji} Trade lifecycle {lifecycle_id} position closed: P&L ${realized_pnl:.2f}")
+            logger.info(f"{pnl_emoji} Trade lifecycle {lifecycle_id} position closed: P&L {formatter.format_price_with_symbol(realized_pnl, quote_asset=symbol_for_formatting.split('/')[-1] if '/' in symbol_for_formatting else 'USD')}")
             return True
             
         except Exception as e:
@@ -1391,7 +1415,10 @@ class TradingStats:
             
             self._execute_query(query, params)
             
-            logger.info(f"๐Ÿ›‘ Linked stop loss order {stop_loss_order_id} (${stop_loss_price:.2f}) to trade {lifecycle_id}")
+            formatter = get_formatter()
+            trade_info = self.get_trade_by_lifecycle_id(lifecycle_id) # Fetch to get symbol for formatting
+            symbol_for_formatting = trade_info.get('symbol', 'UNKNOWN_SYMBOL') if trade_info else 'UNKNOWN_SYMBOL'
+            logger.info(f"๐Ÿ›‘ Linked stop loss order {stop_loss_order_id} ({formatter.format_price(stop_loss_price, symbol_for_formatting)}) to trade {lifecycle_id}")
             return True
             
         except Exception as e:
@@ -1414,7 +1441,10 @@ class TradingStats:
             
             self._execute_query(query, params)
             
-            logger.info(f"๐ŸŽฏ Linked take profit order {take_profit_order_id} (${take_profit_price:.2f}) to trade {lifecycle_id}")
+            formatter = get_formatter()
+            trade_info = self.get_trade_by_lifecycle_id(lifecycle_id) # Fetch to get symbol for formatting
+            symbol_for_formatting = trade_info.get('symbol', 'UNKNOWN_SYMBOL') if trade_info else 'UNKNOWN_SYMBOL'
+            logger.info(f"๐ŸŽฏ Linked take profit order {take_profit_order_id} ({formatter.format_price(take_profit_price, symbol_for_formatting)}) to trade {lifecycle_id}")
             return True
             
         except Exception as e:

+ 0 - 208
src/utils/price_formatter.py

@@ -1,208 +0,0 @@
-"""
-Price formatting utilities with exchange-specific precision handling.
-"""
-
-import logging
-import math
-from typing import Dict, Optional, Any
-from functools import lru_cache
-
-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 PriceFormatter:
-    """Handles price formatting with proper decimal precision from exchange data."""
-    
-    def __init__(self, trading_engine=None):
-        """
-        Initialize price formatter.
-        
-        Args:
-            trading_engine: TradingEngine instance for accessing exchange data
-        """
-        self.trading_engine = trading_engine
-        self._precision_cache: Dict[str, int] = {}
-        self._markets_cache: Optional[Dict[str, Any]] = None
-    
-    def _load_markets_data(self) -> Dict[str, Any]:
-        """Load markets data with caching."""
-        if self._markets_cache is None and self.trading_engine:
-            try:
-                markets = self.trading_engine.client.get_markets()
-                if markets:
-                    self._markets_cache = markets
-                    logger.info(f"๐Ÿ“Š Loaded {len(markets)} markets for precision data")
-                else:
-                    logger.warning("โš ๏ธ Could not load markets data from exchange")
-                    self._markets_cache = {}
-            except Exception as e:
-                logger.error(f"โŒ Error loading markets data: {e}")
-                self._markets_cache = {}
-        
-        return self._markets_cache or {}
-    
-    def get_token_decimal_places(self, token: str) -> int:
-        """
-        Get the number of decimal places for a token price based on exchange precision.
-        
-        Args:
-            token: Token symbol (e.g., 'BTC', 'PEPE')
-            
-        Returns:
-            Number of decimal places for price formatting
-        """
-        # Check cache first
-        if token in self._precision_cache:
-            return self._precision_cache[token]
-        
-        # Default decimal places for fallback
-        default_decimals = 2
-        
-        try:
-            markets = self._load_markets_data()
-            if not markets:
-                logger.debug(f"No markets data available for {token}, using default {default_decimals} decimals")
-                return default_decimals
-            
-            # Try different symbol formats
-            possible_symbols = [
-                f"{token}/USDC:USDC",
-                f"{token}/USDC",
-                f"k{token}/USDC:USDC",  # For some tokens like kPEPE
-                f"H{token}/USDC",       # For some tokens like HPEPE
-            ]
-            
-            for symbol in possible_symbols:
-                if symbol in markets:
-                    market_info = markets[symbol]
-                    precision_info = market_info.get('precision', {})
-                    price_precision = precision_info.get('price')
-                    
-                    if price_precision and price_precision > 0:
-                        # Convert precision to decimal places
-                        # precision 0.01 -> 2 decimals, 0.001 -> 3 decimals, etc.
-                        decimal_places = int(-math.log10(price_precision))
-                        
-                        # Cache the result
-                        self._precision_cache[token] = decimal_places
-                        logger.debug(f"๐Ÿ“Š {token} precision: {price_precision} -> {decimal_places} decimal places")
-                        return decimal_places
-            
-            # If no matching symbol found, use smart defaults based on token type
-            decimal_places = self._get_default_decimals_for_token(token)
-            self._precision_cache[token] = decimal_places
-            logger.debug(f"๐Ÿ’ก {token} using default precision: {decimal_places} decimal places")
-            return decimal_places
-            
-        except Exception as e:
-            logger.error(f"โŒ Error getting precision for {token}: {e}")
-            self._precision_cache[token] = default_decimals
-            return default_decimals
-    
-    def _get_default_decimals_for_token(self, token: str) -> int:
-        """Get smart default decimal places based on token characteristics."""
-        token = _normalize_token_case(token)
-        
-        # High-value tokens (usually need fewer decimals)
-        if token in ['BTC', 'ETH', 'BNB', 'SOL', 'ADA', 'DOT', 'AVAX', 'MATIC', 'LINK']:
-            return 2
-        
-        # Mid-range tokens
-        if token in ['DOGE', 'XRP', 'LTC', 'BCH', 'ETC', 'FIL', 'AAVE', 'UNI']:
-            return 4
-        
-        # Meme/micro-cap tokens (usually need more decimals)
-        if any(meme in token for meme in ['PEPE', 'SHIB', 'DOGE', 'FLOKI', 'BONK', 'WIF']):
-            return 6
-        
-        # Default for unknown tokens
-        return 4
-    
-    def format_price(self, price: float, token: str = None) -> str:
-        """
-        Format a price with appropriate decimal places.
-        
-        Args:
-            price: Price value to format
-            token: Token symbol for precision lookup (optional)
-            
-        Returns:
-            Formatted price string
-        """
-        try:
-            if token:
-                decimal_places = self.get_token_decimal_places(token)
-            else:
-                # Use smart default based on price magnitude
-                if price >= 1000:
-                    decimal_places = 2
-                elif price >= 1:
-                    decimal_places = 3
-                elif price >= 0.01:
-                    decimal_places = 4
-                elif price >= 0.0001:
-                    decimal_places = 6
-                else:
-                    decimal_places = 8
-            
-            # Format with proper decimal places and thousand separators
-            formatted = f"{price:,.{decimal_places}f}"
-            return formatted
-            
-        except Exception as e:
-            logger.error(f"โŒ Error formatting price {price} for {token}: {e}")
-            return f"{price:,.2f}"  # Fallback to 2 decimals
-    
-    def format_price_with_symbol(self, price: float, token: str = None) -> str:
-        """
-        Format a price with currency symbol and appropriate decimal places.
-        
-        Args:
-            price: Price value to format
-            token: Token symbol for precision lookup (optional)
-            
-        Returns:
-            Formatted price string with $ symbol
-        """
-        formatted_price = self.format_price(price, token)
-        return f"${formatted_price}"
-    
-    def clear_cache(self):
-        """Clear the precision cache."""
-        self._precision_cache.clear()
-        self._markets_cache = None
-        logger.info("๐Ÿ—‘๏ธ Cleared price formatter cache")
-
-
-# Global formatter instance
-_global_formatter: Optional[PriceFormatter] = None
-
-def set_global_trading_engine(trading_engine):
-    """Set the trading engine for the global formatter."""
-    global _global_formatter
-    _global_formatter = PriceFormatter(trading_engine)
-
-def get_formatter() -> PriceFormatter:
-    """Get the global formatter instance."""
-    global _global_formatter
-    if _global_formatter is None:
-        _global_formatter = PriceFormatter()
-    return _global_formatter
-
-def format_price(price: float, token: str = None) -> str:
-    """Convenience function to format price using global formatter."""
-    return get_formatter().format_price(price, token)
-
-def format_price_with_symbol(price: float, token: str = None) -> str:
-    """Convenience function to format price with $ symbol using global formatter."""
-    return get_formatter().format_price_with_symbol(price, token) 

+ 237 - 0
src/utils/token_display_formatter.py

@@ -0,0 +1,237 @@
+"""
+Price formatting utilities with exchange-specific precision handling.
+"""
+
+import logging
+import math
+from typing import Dict, Optional, Any
+from functools import lru_cache
+
+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.
+    """
+    if any(c.isupper() for c in token):
+        return token
+    else:
+        return token.upper()
+
+class TokenDisplayFormatter:
+    """Handles price and amount formatting with proper decimal precision from exchange data."""
+
+    def __init__(self, trading_engine=None):
+        """
+        Initialize price formatter.
+
+        Args:
+            trading_engine: TradingEngine instance for accessing exchange data
+        """
+        self.trading_engine = trading_engine
+        # Cache structure: {token: {'price_decimals': X, 'amount_decimals': Y}}
+        self._precision_cache: Dict[str, Dict[str, int]] = {}
+        self._markets_cache: Optional[Dict[str, Any]] = None
+
+    def _load_markets_data(self) -> Dict[str, Any]:
+        """Load markets data with caching."""
+        if self._markets_cache is None and self.trading_engine:
+            try:
+                markets = self.trading_engine.client.get_markets()
+                if markets:
+                    self._markets_cache = markets
+                    logger.info(f"๐Ÿ“Š Loaded {len(markets)} markets for precision data")
+                else:
+                    logger.warning("โš ๏ธ Could not load markets data from exchange")
+                    self._markets_cache = {}
+            except Exception as e:
+                logger.error(f"โŒ Error loading markets data: {e}")
+                self._markets_cache = {}
+        return self._markets_cache or {}
+
+    def _fetch_and_cache_precisions(self, token: str) -> Optional[Dict[str, int]]:
+        """
+        Fetches price and amount precisions for a token from market data and caches them.
+        Returns the cached dict {'price_decimals': X, 'amount_decimals': Y} or None if not found.
+        """
+        normalized_token = _normalize_token_case(token)
+        if normalized_token in self._precision_cache:
+            return self._precision_cache[normalized_token]
+
+        markets = self._load_markets_data()
+        if not markets:
+            logger.debug(f"No markets data available for {normalized_token}, cannot fetch precisions.")
+            return None
+
+        possible_symbols = [
+            f"{normalized_token}/USDC:USDC",
+            f"{normalized_token}/USDC",
+            # Add other common quote currencies or symbol formats if necessary
+        ]
+        # Handle k-tokens or other prefixed tokens if that's a pattern
+        if normalized_token.startswith('K') and len(normalized_token) > 1 and not normalized_token[1].isnumeric(): # e.g. kPEPE but not KSM
+             possible_symbols.append(f"{normalized_token}/USDC:USDC") # Already covered if normalized_token includes 'k'
+             possible_symbols.append(f"{normalized_token.upper()}/USDC:USDC") # Try uppercase original if 'k' was part of normalization strategy
+
+        market_info = None
+        for symbol_variant in possible_symbols:
+            if symbol_variant in markets:
+                market_info = markets[symbol_variant]
+                break
+        
+        if not market_info:
+            # Try direct lookup for already fully qualified symbols (e.g. "PEPE/USDC:USDC")
+            if token in markets:
+                 market_info = markets[token]
+            else:
+                logger.warning(f"Market info not found for {normalized_token} or its variants.")
+                return None
+
+        precision_info = market_info.get('precision', {})
+        price_precision_val = precision_info.get('price')
+        amount_precision_val = precision_info.get('amount')
+
+        price_decimals = self._get_default_price_decimals_for_token(normalized_token) # Default
+        if price_precision_val and price_precision_val > 0:
+            price_decimals = int(-math.log10(price_precision_val))
+
+        # For amount, we typically need it from the exchange; default might be less useful
+        # Defaulting to a common high precision if not found, but ideally exchange provides this.
+        amount_decimals = 6 
+        if amount_precision_val and amount_precision_val > 0:
+            amount_decimals = int(-math.log10(amount_precision_val))
+        
+        self._precision_cache[normalized_token] = {
+            'price_decimals': price_decimals,
+            'amount_decimals': amount_decimals
+        }
+        logger.debug(f"๐Ÿ“Š Cached precisions for {normalized_token}: price {price_decimals}, amount {amount_decimals}")
+        return self._precision_cache[normalized_token]
+
+    def get_token_price_decimal_places(self, token: str) -> int:
+        """
+        Get the number of decimal places for a token's price.
+        """
+        normalized_token = _normalize_token_case(token)
+        precisions = self._precision_cache.get(normalized_token)
+        if not precisions:
+            precisions = self._fetch_and_cache_precisions(normalized_token)
+
+        if precisions:
+            return precisions['price_decimals']
+        
+        # Fallback to smart default if fetching failed completely
+        return self._get_default_price_decimals_for_token(normalized_token)
+
+    def get_token_amount_decimal_places(self, token: str) -> int:
+        """
+        Get the number of decimal places for a token's amount (quantity).
+        """
+        normalized_token = _normalize_token_case(token)
+        precisions = self._precision_cache.get(normalized_token)
+        if not precisions:
+            precisions = self._fetch_and_cache_precisions(normalized_token)
+            
+        if precisions:
+            return precisions['amount_decimals']
+        
+        # Fallback if fetching failed - consider a sensible default for amounts
+        logger.warning(f"Amount precision not found for {normalized_token}, defaulting to 6.")
+        return 6 # Default amount precision
+
+    def _get_default_price_decimals_for_token(self, token: str) -> int: # Renamed from _get_default_decimals_for_token
+        """Get smart default price decimal places based on token characteristics."""
+        token_upper = token.upper() # Ensure consistent casing for checks
+
+        if token_upper in ['BTC', 'ETH', 'BNB', 'SOL', 'ADA', 'DOT', 'AVAX', 'MATIC', 'LINK']:
+            return 2
+        if token_upper in ['DOGE', 'XRP', 'LTC', 'BCH', 'ETC', 'FIL', 'AAVE', 'UNI']:
+            return 4
+        if any(meme in token_upper for meme in ['PEPE', 'SHIB', 'FLOKI', 'BONK', 'WIF']): # DOGE already covered
+            return 6
+        return 4
+
+    def format_price(self, price: float, token: str = None) -> str:
+        """
+        Format a price with appropriate decimal places.
+        """
+        try:
+            decimal_places = 2 # Default if no token
+            if token:
+                decimal_places = self.get_token_price_decimal_places(token)
+            else:
+                # Smart default based on price magnitude if no token provided
+                if price == 0: decimal_places = 2
+                elif abs(price) >= 1000: decimal_places = 2
+                elif abs(price) >= 1: decimal_places = 3
+                elif abs(price) >= 0.01: decimal_places = 4
+                elif abs(price) >= 0.0001: decimal_places = 6
+                else: decimal_places = 8
+            
+            return f"{price:,.{decimal_places}f}"
+        except Exception as e:
+            logger.error(f"โŒ Error formatting price {price} for {token}: {e}")
+            return f"{price:,.2f}"
+
+    def format_price_with_symbol(self, price: float, token: str = None) -> str:
+        """
+        Format a price with currency symbol and appropriate decimal places.
+        """
+        formatted_price = self.format_price(price, token)
+        return f"${formatted_price}"
+
+    def format_amount(self, amount: float, token: str) -> str:
+        """
+        Format an amount (quantity) with appropriate decimal places for the given token.
+        """
+        try:
+            decimal_places = self.get_token_amount_decimal_places(token)
+            return f"{amount:,.{decimal_places}f}"
+        except Exception as e:
+            logger.error(f"โŒ Error formatting amount {amount} for {token}: {e}")
+            # Fallback, ensuring a reasonable number of decimals for an amount
+            return f"{amount:,.6f}"
+
+    def clear_cache(self):
+        """Clear the precision cache."""
+        self._precision_cache.clear()
+        self._markets_cache = None
+        logger.info("๐Ÿ—‘๏ธ Cleared price formatter cache")
+
+# Global formatter instance
+_global_formatter: Optional[TokenDisplayFormatter] = None
+
+def set_global_trading_engine(trading_engine):
+    """Set the trading engine for the global formatter."""
+    global _global_formatter
+    if _global_formatter is None or _global_formatter.trading_engine is None:
+        _global_formatter = TokenDisplayFormatter(trading_engine)
+        logger.info("Global TokenDisplayFormatter initialized with trading engine.")
+    elif trading_engine is not _global_formatter.trading_engine:
+        _global_formatter.trading_engine = trading_engine
+        _global_formatter.clear_cache() # Clear cache if engine changes
+        logger.info("Global TokenDisplayFormatter updated with a new trading engine and cache cleared.")
+
+
+def get_formatter() -> TokenDisplayFormatter:
+    """Get the global formatter instance. Ensures trading_engine is set if possible."""
+    global _global_formatter
+    if _global_formatter is None:
+        # Attempt to create a basic formatter.
+        # It's better if set_global_trading_engine is called explicitly during app setup.
+        logger.warning("TokenDisplayFormatter.get_formatter() called before trading_engine was set globally. Creating instance without it. Precision data may be limited to defaults.")
+        _global_formatter = TokenDisplayFormatter()
+    return _global_formatter
+
+def format_price(price: float, token: str = None) -> str:
+    """Convenience function to format price using global formatter."""
+    return get_formatter().format_price(price, token)
+
+def format_price_with_symbol(price: float, token: str = None) -> str:
+    """Convenience function to format price with $ symbol using global formatter."""
+    return get_formatter().format_price_with_symbol(price, token)
+
+def format_amount(amount: float, token: str) -> str:
+    """Convenience function to format amount using global formatter."""
+    return get_formatter().format_amount(amount, token)