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/
 trader_hyperliquid/
 โ”œโ”€โ”€ ๐Ÿ“‚ src/                     # Source code (modular architecture)
 โ”œโ”€โ”€ ๐Ÿ“‚ src/                     # Source code (modular architecture)
-โ”‚   โ”œโ”€โ”€ ๐Ÿ“ backup/              # Archive
+โ”‚   โ”œโ”€โ”€ ๐Ÿ“‚ backup/              # Archive
 โ”‚   โ”‚   โ””โ”€โ”€ ๐Ÿ“„ telegram_bot.py  # Original 4,627-line monolithic file
 โ”‚   โ”‚   โ””โ”€โ”€ ๐Ÿ“„ telegram_bot.py  # Original 4,627-line monolithic file
 โ”‚   โ”œโ”€โ”€ ๐Ÿ“ bot/                 # Core bot functionality
 โ”‚   โ”œโ”€โ”€ ๐Ÿ“ bot/                 # Core bot functionality
 โ”‚   โ”‚   โ””โ”€โ”€ ๐Ÿค– core.py          # Core bot setup & authentication (264 lines)
 โ”‚   โ”‚   โ””โ”€โ”€ ๐Ÿค– core.py          # Core bot setup & authentication (264 lines)
@@ -33,6 +33,8 @@ trader_hyperliquid/
 โ”‚   โ”œโ”€โ”€ ๐Ÿ“ trading/             # Trading logic
 โ”‚   โ”œโ”€โ”€ ๐Ÿ“ trading/             # Trading logic
 โ”‚   โ”‚   โ”œโ”€โ”€ ๐Ÿš€ trading_engine.py # Order execution (419 lines)
 โ”‚   โ”‚   โ”œโ”€โ”€ ๐Ÿš€ trading_engine.py # Order execution (419 lines)
 โ”‚   โ”‚   โ””โ”€โ”€ ๐Ÿ“Š trading_stats.py # Statistics tracking (1,161 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
 โ”‚   โ””โ”€โ”€ ๐Ÿ“„ __init__.py          # Root module init
 โ”œโ”€โ”€ ๐Ÿ“‚ data/                    # ๐Ÿ†• Persistent data storage
 โ”œโ”€โ”€ ๐Ÿ“‚ data/                    # ๐Ÿ†• Persistent data storage
 โ”‚   โ”œโ”€โ”€ ๐Ÿ“Š trading_stats.sqlite # SQLite database with all trading data
 โ”‚   โ”œโ”€โ”€ ๐Ÿ“Š 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 telegram.ext import ContextTypes
 
 
 from src.config.config import Config
 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__)
 logger = logging.getLogger(__name__)
 
 
@@ -204,14 +204,18 @@ class InfoCommands:
                 # --- Format Output String ---
                 # --- Format Output String ---
                 # Get token info for formatting prices
                 # Get token info for formatting prices
                 # Assuming get_formatter() is available and provides necessary precision
                 # 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
                 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)
                 entry_price_str = formatter.format_price_with_symbol(entry_price, base_asset)
                 mark_price_str = formatter.format_price_with_symbol(mark_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 = ""
                 type_indicator = ""
                 # Determine type_indicator based on trade_lifecycle_id or trade_type
                 # Determine type_indicator based on trade_lifecycle_id or trade_type
                 if position_trade.get('trade_lifecycle_id'): # Primary indicator for bot managed
                 if position_trade.get('trade_lifecycle_id'): # Primary indicator for bot managed
@@ -220,7 +224,7 @@ class InfoCommands:
                     type_indicator = " ๐Ÿ”„"
                     type_indicator = " ๐Ÿ”„"
                 
                 
                 positions_text += f"{pos_emoji} <b>{base_asset} ({direction_text}){type_indicator}</b>\n"
                 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"
                 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
                 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
             return
             
             
         token_stats = stats.get_token_detailed_stats(token)
         token_stats = stats.get_token_detailed_stats(token)
+        formatter = get_formatter() # Get the formatter instance
         
         
         # Check if token has any data
         # Check if token has any data
         if token_stats.get('total_trades', 0) == 0:
         if token_stats.get('total_trades', 0) == 0:
@@ -729,6 +734,7 @@ class InfoCommands:
         
         
         # Check if there's a message (no completed trades)
         # Check if there's a message (no completed trades)
         if 'message' in token_stats and token_stats.get('completed_trades', 0) == 0:
         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=
             await context.bot.send_message(chat_id=chat_id, text=
                 f"๐Ÿ“Š <b>{token} Performance</b>\n\n"
                 f"๐Ÿ“Š <b>{token} Performance</b>\n\n"
                 f"{token_stats['message']}\n\n"
                 f"{token_stats['message']}\n\n"
@@ -736,7 +742,7 @@ class InfoCommands:
                 f"โ€ข Total Trades: {token_stats['total_trades']}\n"
                 f"โ€ข Total Trades: {token_stats['total_trades']}\n"
                 f"โ€ข Buy Orders: {token_stats.get('buy_trades', 0)}\n"
                 f"โ€ข Buy Orders: {token_stats.get('buy_trades', 0)}\n"
                 f"โ€ข Sell Orders: {token_stats.get('sell_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"๐Ÿ’ก Complete some trades to see P&L statistics!\n"
                 f"๐Ÿ”„ Use <code>/performance</code> to see all token rankings.",
                 f"๐Ÿ”„ Use <code>/performance</code> to see all token rankings.",
                 parse_mode='HTML'
                 parse_mode='HTML'
@@ -746,13 +752,22 @@ class InfoCommands:
         # Detailed stats display
         # Detailed stats display
         pnl_emoji = "๐ŸŸข" if token_stats['total_pnl'] >= 0 else "๐Ÿ”ด"
         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"""
         performance_text = f"""
 ๐Ÿ“Š <b>{token} Detailed Performance</b>
 ๐Ÿ“Š <b>{token} Detailed Performance</b>
 
 
 ๐Ÿ’ฐ <b>P&L Summary:</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>
 ๐Ÿ“Š <b>Trading Activity:</b>
 โ€ข Total Trades: {token_stats['total_trades']}
 โ€ข Total Trades: {token_stats['total_trades']}
@@ -766,10 +781,10 @@ class InfoCommands:
 โ€ข Wins: {token_stats['total_wins']} | Losses: {token_stats['total_losses']}
 โ€ข Wins: {token_stats['total_wins']} | Losses: {token_stats['total_losses']}
 
 
 ๐Ÿ’ก <b>Best/Worst:</b>
 ๐Ÿ’ก <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
         # Add recent trades if available
@@ -778,9 +793,20 @@ class InfoCommands:
             for trade in token_stats['recent_trades'][-3:]:  # Last 3 trades
             for trade in token_stats['recent_trades'][-3:]:  # Last 3 trades
                 trade_time = datetime.fromisoformat(trade['timestamp']).strftime('%m/%d %H:%M')
                 trade_time = datetime.fromisoformat(trade['timestamp']).strftime('%m/%d %H:%M')
                 side_emoji = "๐ŸŸข" if trade['side'] == 'buy' else "๐Ÿ”ด"
                 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"
         performance_text += f"\n๐Ÿ”„ Use <code>/performance</code> to see all token rankings"
         
         
@@ -800,49 +826,50 @@ class InfoCommands:
                 return
                 return
                 
                 
             daily_stats = stats.get_daily_stats(10)
             daily_stats = stats.get_daily_stats(10)
+            formatter = get_formatter() # Get formatter
             
             
             if not daily_stats:
             if not daily_stats:
                 await context.bot.send_message(chat_id=chat_id, text=
                 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!",
                     "Start trading to see daily performance!",
                     parse_mode='HTML'
                     parse_mode='HTML'
                 )
                 )
                 return
                 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:
                 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 "๐Ÿ”ด"
                 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')
             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")
                 await context.bot.send_message(chat_id=chat_id, text="โŒ Could not load trading statistics")
                 return
                 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=
                 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!",
                     "Start trading to see weekly performance!",
                     parse_mode='HTML'
                     parse_mode='HTML'
                 )
                 )
                 return
                 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:
                 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')
             await context.bot.send_message(chat_id=chat_id, text=weekly_text.strip(), parse_mode='HTML')
-            
+                
         except Exception as e:
         except Exception as e:
             error_message = f"โŒ Error processing weekly command: {str(e)}"
             error_message = f"โŒ Error processing weekly command: {str(e)}"
             await context.bot.send_message(chat_id=chat_id, text=error_message)
             await context.bot.send_message(chat_id=chat_id, text=error_message)
             logger.error(f"Error in weekly command: {e}")
             logger.error(f"Error in weekly command: {e}")
-
+    
     async def monthly_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     async def monthly_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /monthly command to show monthly performance stats."""
         """Handle the /monthly command to show monthly performance stats."""
         chat_id = update.effective_chat.id
         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")
                 await context.bot.send_message(chat_id=chat_id, text="โŒ Could not load trading statistics")
                 return
                 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=
                 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!",
                     "Start trading to see monthly performance!",
                     parse_mode='HTML'
                     parse_mode='HTML'
                 )
                 )
                 return
                 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:
                 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')
             await context.bot.send_message(chat_id=chat_id, text=monthly_text.strip(), parse_mode='HTML')
-            
+                
         except Exception as e:
         except Exception as e:
             error_message = f"โŒ Error processing monthly command: {str(e)}"
             error_message = f"โŒ Error processing monthly command: {str(e)}"
             await context.bot.send_message(chat_id=chat_id, text=error_message)
             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.config.config import Config
 from src.monitoring.alarm_manager import AlarmManager
 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__)
 logger = logging.getLogger(__name__)
 
 
@@ -56,6 +59,7 @@ class ManagementCommands:
         adjustments_summary = stats.get_balance_adjustments_summary() if stats else {
         adjustments_summary = stats.get_balance_adjustments_summary() if stats else {
             'total_deposits': 0, 'total_withdrawals': 0, 'net_adjustment': 0, 'adjustment_count': 0
             'total_deposits': 0, 'total_withdrawals': 0, 'net_adjustment': 0, 'adjustment_count': 0
         }
         }
+        formatter = get_formatter()
         
         
         # Safety checks for monitoring attributes
         # Safety checks for monitoring attributes
         monitoring_active = self.market_monitor.is_running
         monitoring_active = self.market_monitor.is_running
@@ -70,7 +74,7 @@ class ManagementCommands:
 
 
 ๐Ÿ’ฐ <b>Balance Tracking:</b>
 ๐Ÿ’ฐ <b>Balance Tracking:</b>
 โ€ข Total Adjustments: {adjustments_summary['adjustment_count']}
 โ€ข 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>
 ๐Ÿ”” <b>Price Alarms:</b>
 โ€ข Active Alarms: {alarm_stats['total_active']}
 โ€ข Active Alarms: {alarm_stats['total_active']}
@@ -174,27 +178,32 @@ class ManagementCommands:
                 
                 
                 # Create the alarm
                 # Create the alarm
                 alarm = self.alarm_manager.create_alarm(token, target_price, current_price)
                 alarm = self.alarm_manager.create_alarm(token, target_price, current_price)
+                formatter = get_formatter()
                 
                 
                 # Format confirmation message
                 # Format confirmation message
                 direction_emoji = "๐Ÿ“ˆ" if alarm['direction'] == 'above' else "๐Ÿ“‰"
                 direction_emoji = "๐Ÿ“ˆ" if alarm['direction'] == 'above' else "๐Ÿ“‰"
                 price_diff = abs(target_price - current_price)
                 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"""
                 message = f"""
 โœ… <b>Price Alarm Created</b>
 โœ… <b>Price Alarm Created</b>
 
 
 ๐Ÿ“Š <b>Alarm Details:</b>
 ๐Ÿ“Š <b>Alarm Details:</b>
 โ€ข Alarm ID: {alarm['id']}
 โ€ข Alarm ID: {alarm['id']}
 โ€ข Token: {token}
 โ€ข 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: {alarm['direction'].upper()}
 
 
 {direction_emoji} <b>Alert Condition:</b>
 {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>
 ๐Ÿ’ฐ <b>Price Difference:</b>
-โ€ข Distance: ${price_diff:,.2f} ({price_diff_percent:.2f}%)
+โ€ข Distance: {price_diff_str} ({price_diff_percent:.2f}%)
 โ€ข Status: ACTIVE โœ…
 โ€ข Status: ACTIVE โœ…
 
 
 โฐ <b>Created:</b> {datetime.now().strftime('%H:%M:%S')}
 โฐ <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 telegram.ext import ContextTypes
 
 
 from src.config.config import Config
 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__)
 logger = logging.getLogger(__name__)
 
 
@@ -122,7 +122,7 @@ class TradingCommands:
 ๐Ÿ“Š <b>Order Details:</b>
 ๐Ÿ“Š <b>Order Details:</b>
 โ€ข Token: {token}
 โ€ข Token: {token}
 โ€ข USDC Amount: {formatter.format_price_with_symbol(usdc_amount)}
 โ€ข 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}
 โ€ข Order Type: {order_type}
 โ€ข Price: {formatter.format_price_with_symbol(price, token)}
 โ€ข Price: {formatter.format_price_with_symbol(price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_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>
 ๐Ÿ“Š <b>Order Details:</b>
 โ€ข Token: {token}
 โ€ข Token: {token}
 โ€ข USDC Amount: {formatter.format_price_with_symbol(usdc_amount)}
 โ€ข 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}
 โ€ข Order Type: {order_type}
 โ€ข Price: {formatter.format_price_with_symbol(price, token)}
 โ€ข Price: {formatter.format_price_with_symbol(price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_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>
 ๐Ÿ“Š <b>Position Details:</b>
 โ€ข Token: {token}
 โ€ข Token: {token}
 โ€ข Position: {position_type}
 โ€ข Position: {position_type}
-โ€ข Size: {contracts:.6f} contracts
+โ€ข Size: {formatter.format_amount(contracts, token)} contracts
 โ€ข Entry Price: {formatter.format_price_with_symbol(entry_price, token)}
 โ€ข Entry Price: {formatter.format_price_with_symbol(entry_price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_price, token)}
 โ€ข {pnl_emoji} Unrealized P&L: {formatter.format_price_with_symbol(unrealized_pnl)}
 โ€ข {pnl_emoji} Unrealized P&L: {formatter.format_price_with_symbol(unrealized_pnl)}
 
 
 ๐ŸŽฏ <b>Exit Order:</b>
 ๐ŸŽฏ <b>Exit Order:</b>
 โ€ข Action: {exit_side.upper()} (Close {position_type})
 โ€ข 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)}
 โ€ข Est. Value: ~{formatter.format_price_with_symbol(exit_value)}
 โ€ข Order Type: Market Order
 โ€ข 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>
 ๐Ÿ“Š <b>Position Details:</b>
 โ€ข Token: {token}
 โ€ข Token: {token}
 โ€ข Position: {position_type}
 โ€ข Position: {position_type}
-โ€ข Size: {contracts:.6f} contracts
+โ€ข Size: {formatter.format_amount(contracts, token)} contracts
 โ€ข Entry Price: {formatter.format_price_with_symbol(entry_price, token)}
 โ€ข Entry Price: {formatter.format_price_with_symbol(entry_price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_price, token)}
 
 
 ๐ŸŽฏ <b>Stop Loss Order:</b>
 ๐ŸŽฏ <b>Stop Loss Order:</b>
 โ€ข Stop Price: {formatter.format_price_with_symbol(stop_price, token)}
 โ€ข Stop Price: {formatter.format_price_with_symbol(stop_price, token)}
 โ€ข Action: {exit_side.upper()} (Close {position_type})
 โ€ข Action: {exit_side.upper()} (Close {position_type})
-โ€ข Amount: {contracts:.6f} {token}
+โ€ข Amount: {formatter.format_amount(contracts, token)} {token}
 โ€ข Order Type: Limit Order
 โ€ข Order Type: Limit Order
 โ€ข {pnl_emoji} Est. P&L: {formatter.format_price_with_symbol(pnl_at_stop)}
 โ€ข {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>
 ๐Ÿ“Š <b>Position Details:</b>
 โ€ข Token: {token}
 โ€ข Token: {token}
 โ€ข Position: {position_type}
 โ€ข Position: {position_type}
-โ€ข Size: {contracts:.6f} contracts
+โ€ข Size: {formatter.format_amount(contracts, token)} contracts
 โ€ข Entry Price: {formatter.format_price_with_symbol(entry_price, token)}
 โ€ข Entry Price: {formatter.format_price_with_symbol(entry_price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_price, token)}
 โ€ข Current Price: {formatter.format_price_with_symbol(current_price, token)}
 
 
 ๐ŸŽฏ <b>Take Profit Order:</b>
 ๐ŸŽฏ <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})
 โ€ข Action: {exit_side.upper()} (Close {position_type})
-โ€ข Amount: {contracts:.6f} {token}
+โ€ข Amount: {formatter.format_amount(contracts, token)} {token}
 โ€ข Order Type: Limit Order
 โ€ข Order Type: Limit Order
 โ€ข {pnl_emoji} Est. P&L: {formatter.format_price_with_symbol(pnl_at_tp)}
 โ€ข {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>
 โš ๏ธ <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 = [
             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')
 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 logger = logging.getLogger(__name__)
 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",
     "liquidation_price": "REAL DEFAULT NULL",
     "margin_used": "REAL DEFAULT NULL",
     "margin_used": "REAL DEFAULT NULL",
     "leverage": "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 = 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:
     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():
 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
     conn = None
     try:
     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:
         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:
     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:
     finally:
         if conn:
         if conn:
             conn.close()
             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()
     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
 import logging
 from datetime import datetime, timezone
 from datetime import datetime, timezone
 from typing import Dict, List, Any, Optional
 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__)
 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."
             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"
         message = f"๐Ÿ”” <b>{title}</b>\n\n"
+        formatter = get_formatter()
         for alarm in alarms:
         for alarm in alarms:
             direction_emoji = "๐Ÿ“ˆ" if alarm['direction'] == 'above' else "๐Ÿ“‰"
             direction_emoji = "๐Ÿ“ˆ" if alarm['direction'] == 'above' else "๐Ÿ“‰"
             created_price = alarm.get('current_price_at_creation', alarm['target_price'])
             created_price = alarm.get('current_price_at_creation', alarm['target_price'])
             price_diff = abs(alarm['target_price'] - created_price)
             price_diff = abs(alarm['target_price'] - created_price)
             price_diff_percent = (price_diff / created_price * 100) if created_price > 0 else 0
             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."
         message += "\n๐Ÿ’ก Use <code>/alarm ID</code> to remove an alarm."
         return message.strip()
         return message.strip()
@@ -212,6 +220,7 @@ class AlarmManager:
     def format_triggered_alarm(self, alarm: Dict[str, Any]) -> str:
     def format_triggered_alarm(self, alarm: Dict[str, Any]) -> str:
         """Format a triggered alarm notification."""
         """Format a triggered alarm notification."""
         direction_emoji = "๐Ÿ“ˆ" if alarm['direction'] == 'above' else "๐Ÿ“‰"
         direction_emoji = "๐Ÿ“ˆ" if alarm['direction'] == 'above' else "๐Ÿ“‰"
+        formatter = get_formatter()
         triggered_at_str = "Unknown time"
         triggered_at_str = "Unknown time"
         if alarm.get('triggered_at'):
         if alarm.get('triggered_at'):
             try:
             try:
@@ -219,13 +228,17 @@ class AlarmManager:
             except ValueError:
             except ValueError:
                 pass # Keep as Unknown time
                 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"""
         message = f"""
 ๐Ÿšจ <b>Price Alarm Triggered!</b>
 ๐Ÿšจ <b>Price Alarm Triggered!</b>
 
 
 ๐Ÿ”” <b>Alarm ID: {alarm['id']}</b>
 ๐Ÿ”” <b>Alarm ID: {alarm['id']}</b>
 โ€ข Token: {alarm['token']}
 โ€ข 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}
 โฐ <b>Time:</b> {triggered_at_str}
 ๐Ÿ’ก This alarm is now inactive.
 ๐Ÿ’ก 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 os
 import json
 import json
 
 
+from telegram.ext import CallbackContext
+
 from src.config.config import Config
 from src.config.config import Config
 from src.monitoring.alarm_manager import AlarmManager
 from src.monitoring.alarm_manager import AlarmManager
+from src.utils.token_display_formatter import get_formatter
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -387,6 +390,7 @@ class MarketMonitor:
         """Update position tracking and calculate P&L changes."""
         """Update position tracking and calculate P&L changes."""
         try:
         try:
             new_position_map = {}
             new_position_map = {}
+            formatter = get_formatter() # Get formatter
             
             
             for position in current_positions:
             for position in current_positions:
                 symbol = position.get('symbol')
                 symbol = position.get('symbol')
@@ -402,14 +406,18 @@ class MarketMonitor:
             # Compare with previous positions to detect changes
             # Compare with previous positions to detect changes
             for symbol, new_data in new_position_map.items():
             for symbol, new_data in new_position_map.items():
                 old_data = self.last_known_positions.get(symbol)
                 old_data = self.last_known_positions.get(symbol)
+                token = symbol.split('/')[0] if '/' in symbol else symbol # Extract token
                 
                 
                 if not old_data:
                 if not old_data:
                     # New position opened
                     # 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:
                 elif abs(new_data['contracts'] - old_data['contracts']) > 0.000001:
                     # Position size changed
                     # Position size changed
                     change = new_data['contracts'] - old_data['contracts']
                     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
             # Check for closed positions
             for symbol in self.last_known_positions:
             for symbol in self.last_known_positions:
@@ -760,7 +768,9 @@ class MarketMonitor:
                                 )
                                 )
                                 if success:
                                 if success:
                                     pnl_emoji = "๐ŸŸข" if realized_pnl >= 0 else "๐Ÿ”ด"
                                     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)
                                     symbols_with_fills.add(token)
                                     if self.notification_manager:
                                     if self.notification_manager:
                                         await self.notification_manager.send_external_trade_notification(
                                         await self.notification_manager.send_external_trade_notification(
@@ -777,7 +787,8 @@ class MarketMonitor:
                     if not fill_processed_this_iteration:
                     if not fill_processed_this_iteration:
                         if exchange_order_id_from_fill and exchange_order_id_from_fill in self.external_stop_losses:
                         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]
                             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')
                             sl_active_lc = stats.get_trade_by_symbol_and_status(full_symbol, 'position_opened')
                             if sl_active_lc:
                             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)
                                 success = stats.update_trade_position_closed(lc_id, price_from_fill, realized_pnl, trade_id)
                                 if success:
                                 if success:
                                     pnl_emoji = "๐ŸŸข" if realized_pnl >= 0 else "๐Ÿ”ด"
                                     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:
                                     if self.notification_manager:
                                         await self.notification_manager.send_stop_loss_execution_notification(
                                         await self.notification_manager.send_stop_loss_execution_notification(
                                             stop_loss_info, full_symbol, side_from_fill, amount_from_fill, price_from_fill, 
                                             stop_loss_info, full_symbol, side_from_fill, amount_from_fill, price_from_fill, 
@@ -1163,7 +1174,7 @@ class MarketMonitor:
             if not stats:
             if not stats:
                 return
                 return
             
             
-            # Get open positions that need stop loss activation
+            formatter = get_formatter() # Get formatter
             trades_needing_sl = stats.get_pending_stop_loss_activations()
             trades_needing_sl = stats.get_pending_stop_loss_activations()
             
             
             if not trades_needing_sl:
             if not trades_needing_sl:
@@ -1196,12 +1207,14 @@ class MarketMonitor:
                     trigger_reason = ""
                     trigger_reason = ""
                     
                     
                     if current_price and current_price > 0 and stop_loss_price and stop_loss_price > 0:
                     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:
                         if sl_side == 'sell' and current_price <= stop_loss_price:
                             trigger_already_hit = True
                             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:
                         elif sl_side == 'buy' and current_price >= stop_loss_price:
                             trigger_already_hit = True
                             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:
                     if trigger_already_hit:
                         logger.warning(f"๐Ÿšจ IMMEDIATE SL EXECUTION (Trades Table): {token} (Lifecycle: {lifecycle_id[:8]}) - {trigger_reason}. Executing market exit.")
                         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:
                                 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(
                                     await self.notification_manager.send_generic_notification(
                                         f"๐Ÿšจ <b>Immediate Stop Loss Execution</b>\n\n"
                                         f"๐Ÿšจ <b>Immediate Stop Loss Execution</b>\n\n"
                                         f"๐Ÿ†• <b>Source: Unified Trades Table</b>\n"
                                         f"๐Ÿ†• <b>Source: Unified Trades Table</b>\n"
                                         f"Token: {token}\n"
                                         f"Token: {token}\n"
                                         f"Lifecycle ID: {lifecycle_id[:8]}...\n"
                                         f"Lifecycle ID: {lifecycle_id[:8]}...\n"
                                         f"Position Type: {position_side.upper()}\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"Trigger Logic: {trigger_reason}\n"
                                         f"Action: Market close order placed immediately\n"
                                         f"Action: Market close order placed immediately\n"
                                         f"Exit Order ID: {exit_order_id}\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
                                 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)
                                     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:
                                     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(
                                         await self.notification_manager.send_generic_notification(
                                             f"๐Ÿ›‘ <b>Stop Loss Activated</b>\n\n"
                                             f"๐Ÿ›‘ <b>Stop Loss Activated</b>\n\n"
                                             f"๐Ÿ†• <b>Source: Unified Trades Table</b>\n"
                                             f"๐Ÿ†• <b>Source: Unified Trades Table</b>\n"
                                             f"Token: {token}\n"
                                             f"Token: {token}\n"
                                             f"Lifecycle ID: {lifecycle_id[:8]}...\n"
                                             f"Lifecycle ID: {lifecycle_id[:8]}...\n"
                                             f"Position Type: {position_side.upper()}\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"Exchange SL Order ID: {sl_exchange_order_id}\n" # Actual exchange order
                                             f"Time: {datetime.now().strftime('%H:%M:%S')}"
                                             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."""
         """Estimate entry price for an orphaned position by checking recent fills and market data."""
         try:
         try:
             entry_fill_side = 'buy' if side == 'long' else 'sell'
             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)
             recent_fills = self.trading_engine.get_recent_fills(symbol=symbol, limit=20)
             if recent_fills:
             if recent_fills:
                 symbol_side_fills = [
                 symbol_side_fills = [
                     fill for fill in recent_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
                     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:
                 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: (
                     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),
                         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)
                         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_price = float(best_fill.get('price', 0))
+                    fill_amount = float(best_fill.get('amount', 0))
                     if fill_price > 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
                         return fill_price
             
             
             market_data = self.trading_engine.get_market_data(symbol)
             market_data = self.trading_engine.get_market_data(symbol)
             if market_data and market_data.get('ticker'):
             if market_data and market_data.get('ticker'):
                 current_price = float(market_data['ticker'].get('last', 0))
                 current_price = float(market_data['ticker'].get('last', 0))
                 if current_price > 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
                     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))
                 bid = float(market_data['ticker'].get('bid', 0))
                 ask = float(market_data['ticker'].get('ask', 0))
                 ask = float(market_data['ticker'].get('ask', 0))
                 if bid > 0 and ask > 0: return (bid + ask) / 2
                 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.")
                 logger.warning("โš ๏ธ STARTUP: TradingStats not available for auto-sync.")
                 return
                 return
 
 
+            formatter = get_formatter() # Ensure formatter is available
             exchange_positions = self.trading_engine.get_positions() or []
             exchange_positions = self.trading_engine.get_positions() or []
             if not exchange_positions:
             if not exchange_positions:
                 logger.info("โœ… STARTUP: No positions found on exchange.")
                 logger.info("โœ… STARTUP: No positions found on exchange.")
@@ -1699,18 +1719,18 @@ class MarketMonitor:
             for exchange_pos in exchange_positions:
             for exchange_pos in exchange_positions:
                 symbol = exchange_pos.get('symbol')
                 symbol = exchange_pos.get('symbol')
                 contracts_abs = abs(float(exchange_pos.get('contracts', 0)))
                 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
                 if not (symbol and contracts_abs > 1e-9): continue
 
 
                 existing_trade_lc = stats.get_trade_by_symbol_and_status(symbol, 'position_opened')
                 existing_trade_lc = stats.get_trade_by_symbol_and_status(symbol, 'position_opened')
                 if not existing_trade_lc:
                 if not existing_trade_lc:
-                    # Determine position side and entry order side
                     position_side, order_side = '', ''
                     position_side, order_side = '', ''
                     ccxt_side = exchange_pos.get('side', '').lower()
                     ccxt_side = exchange_pos.get('side', '').lower()
                     if ccxt_side == 'long': position_side, order_side = 'long', 'buy'
                     if ccxt_side == 'long': position_side, order_side = 'long', 'buy'
                     elif ccxt_side == 'short': position_side, order_side = 'short', 'sell'
                     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', {})
                         raw_info = exchange_pos.get('info', {}).get('position', {})
                         if isinstance(raw_info, dict):
                         if isinstance(raw_info, dict):
                             szi_str = raw_info.get('szi')
                             szi_str = raw_info.get('szi')
@@ -1720,10 +1740,10 @@ class MarketMonitor:
                                 if szi_val > 1e-9: position_side, order_side = 'long', 'buy'
                                 if szi_val > 1e-9: position_side, order_side = 'long', 'buy'
                                 elif szi_val < -1e-9: position_side, order_side = 'short', 'sell'
                                 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))
                         contracts_val = float(exchange_pos.get('contracts',0))
                         if contracts_val > 1e-9: position_side, order_side = 'long', 'buy'
                         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:
                         else:
                             logger.warning(f"AUTO-SYNC: Position size is effectively 0 for {symbol} after side checks, skipping sync. Data: {exchange_pos}")
                             logger.warning(f"AUTO-SYNC: Position size is effectively 0 for {symbol} after side checks, skipping sync. Data: {exchange_pos}")
                             continue
                             continue
@@ -1743,7 +1763,7 @@ class MarketMonitor:
                             logger.error(f"AUTO-SYNC: Could not determine/estimate entry price for {symbol}. Skipping sync.")
                             logger.error(f"AUTO-SYNC: Could not determine/estimate entry price for {symbol}. Skipping sync.")
                             continue
                             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(
                     lifecycle_id = stats.create_trade_lifecycle(
                         symbol=symbol, side=order_side,
                         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 update lifecycle for {symbol} (Lifecycle: {lifecycle_id[:8]}).")
                     else: logger.error(f"โŒ STARTUP: Failed to create lifecycle for {symbol}.")
                     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.")
                  logger.info("โœ… STARTUP: All existing exchange positions are already tracked.")
             elif synced_count > 0:
             elif synced_count > 0:
                  logger.info(f"๐ŸŽ‰ STARTUP: Auto-synced {synced_count} orphaned position(s).")
                  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:
         except Exception as e:
             logger.error(f"โŒ Error in startup auto-sync: {e}", exc_info=True)
             logger.error(f"โŒ Error in startup auto-sync: {e}", exc_info=True)
@@ -1777,34 +1796,39 @@ class MarketMonitor:
         try:
         try:
             if not self.notification_manager: return
             if not self.notification_manager: return
 
 
+            formatter = get_formatter()
             token = symbol.split('/')[0] if '/' in symbol else symbol
             token = symbol.split('/')[0] if '/' in symbol else symbol
             unrealized_pnl = float(exchange_pos.get('unrealizedPnl', 0))
             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 "๐Ÿ”ด"
             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))
             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]}).")
             logger.info(f"๐Ÿ“ค STARTUP: Sent auto-sync notification for {symbol} (Lifecycle: {lifecycle_id[:8]}).")
             
             
         except Exception as e:
         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
 import logging
 from typing import Optional, Dict, Any, List
 from typing import Optional, Dict, Any, List
 from datetime import datetime
 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__)
 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):
     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."""
         """Send notification for successful long order."""
         try:
         try:
-            # Use PriceFormatter for consistent formatting
+            # Use TokenDisplayFormatter for consistent formatting
             formatter = get_formatter() # Get formatter
             formatter = get_formatter() # Get formatter
             
             
             price_str = formatter.format_price_with_symbol(price, token)
             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)
             value_str = formatter.format_price_with_symbol(amount * price, token)
             order_id_str = order_details.get('id', 'N/A')
             order_id_str = order_details.get('id', 'N/A')
             
             
             message = (
             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"๐Ÿ’ฐ Value: {value_str}\n"
                 f"๐Ÿ†” Order ID: <code>{order_id_str}</code>"
                 f"๐Ÿ†” Order ID: <code>{order_id_str}</code>"
             )
             )
@@ -54,12 +55,12 @@ class NotificationManager:
             formatter = get_formatter() # Get formatter
             formatter = get_formatter() # Get formatter
             
             
             price_str = formatter.format_price_with_symbol(price, token)
             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)
             value_str = formatter.format_price_with_symbol(amount * price, token)
             order_id_str = order_details.get('id', 'N/A')
             order_id_str = order_details.get('id', 'N/A')
             
             
             message = (
             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"๐Ÿ’ฐ Value: {value_str}\n"
                 f"๐Ÿ†” Order ID: <code>{order_id_str}</code>"
                 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
             # Price is the execution price, PnL is calculated based on it
             # For market orders, price might be approximate or from fill later
             # 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"
             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_str = formatter.format_price_with_symbol(pnl)
             pnl_emoji = "๐ŸŸข" if pnl >= 0 else "๐Ÿ”ด"
             pnl_emoji = "๐ŸŸข" if pnl >= 0 else "๐Ÿ”ด"
             order_id_str = order_details.get('id', 'N/A')
             order_id_str = order_details.get('id', 'N/A')
             cancelled_sl_count = order_details.get('cancelled_stop_losses', 0)
             cancelled_sl_count = order_details.get('cancelled_stop_losses', 0)
             
             
             message = (
             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"
                 f"๐Ÿ†” Exit Order ID: <code>{order_id_str}</code>\n"
                 # P&L and price are more reliably determined when MarketMonitor processes the fill.
                 # P&L and price are more reliably determined when MarketMonitor processes the fill.
                 # This notification confirms the exit order was PLACED.
                 # This notification confirms the exit order was PLACED.
@@ -111,11 +112,11 @@ class NotificationManager:
             formatter = get_formatter() # Get formatter
             formatter = get_formatter() # Get formatter
             
             
             stop_price_str = formatter.format_price_with_symbol(stop_price, token)
             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
             order_id_str = order_details.get('exchange_order_id', 'N/A') # From order_placed_details
             
             
             message = (
             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"๐ŸŽฏ Trigger Price: {stop_price_str}\n"
                 f"๐Ÿ†” SL Order ID: <code>{order_id_str}</code>"
                 f"๐Ÿ†” SL Order ID: <code>{order_id_str}</code>"
             )
             )
@@ -132,11 +133,11 @@ class NotificationManager:
             formatter = get_formatter() # Get formatter
             formatter = get_formatter() # Get formatter
             
             
             tp_price_str = formatter.format_price_with_symbol(tp_price, token)
             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
             order_id_str = order_details.get('exchange_order_id', 'N/A') # From order_placed_details
 
 
             message = (
             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"๐Ÿ’ฐ Target Price: {tp_price_str}\n"
                 f"๐Ÿ†” TP Order ID: <code>{order_id_str}</code>"
                 f"๐Ÿ†” TP Order ID: <code>{order_id_str}</code>"
             )
             )
@@ -170,13 +171,21 @@ class NotificationManager:
             success_message += f"""
             success_message += f"""
 
 
 ๐Ÿ—‘๏ธ <b>Successfully Cancelled:</b>"""
 ๐Ÿ—‘๏ธ <b>Successfully Cancelled:</b>"""
+            formatter = get_formatter() # Get formatter for loop
             for order in cancelled_orders:
             for order in cancelled_orders:
                 side = order.get('side', 'Unknown')
                 side = order.get('side', 'Unknown')
                 amount = order.get('amount', 0)
                 amount = order.get('amount', 0)
                 price = order.get('price', 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 "๐Ÿ”ด"
                 side_emoji = "๐ŸŸข" if side.lower() == 'buy' else "๐Ÿ”ด"
                 success_message += f"""
                 success_message += f"""
-{side_emoji} {side.upper()} {amount} @ ${price:,.2f}"""
+{side_emoji} {side.upper()} {amount_str} {order_token_symbol} @ {price_str}"""
         
         
         # Overall status
         # Overall status
         if cancelled_count == (cancelled_count + failed_count) and failed_count == 0:
         if cancelled_count == (cancelled_count + failed_count) and failed_count == 0:
@@ -211,13 +220,18 @@ class NotificationManager:
         
         
         direction_emoji = "๐Ÿ“ˆ" if direction == 'above' else "๐Ÿ“‰"
         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"""
         alarm_message = f"""
 ๐Ÿ”” <b>Price Alarm Triggered!</b>
 ๐Ÿ”” <b>Price Alarm Triggered!</b>
 
 
 {direction_emoji} <b>Alert Details:</b>
 {direction_emoji} <b>Alert Details:</b>
 โ€ข Token: {token}
 โ€ข 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()}
 โ€ข Direction: {direction.upper()}
 
 
 โฐ <b>Trigger Time:</b> {datetime.now().strftime('%H:%M:%S')}
 โฐ <b>Trigger Time:</b> {datetime.now().strftime('%H:%M:%S')}
@@ -230,7 +244,6 @@ class NotificationManager:
         """
         """
         
         
         try:
         try:
-            from src.config.config import Config
             if Config.TELEGRAM_CHAT_ID:
             if Config.TELEGRAM_CHAT_ID:
                 await self.bot_application.bot.send_message(
                 await self.bot_application.bot.send_message(
                     chat_id=Config.TELEGRAM_CHAT_ID,
                     chat_id=Config.TELEGRAM_CHAT_ID,
@@ -328,7 +341,6 @@ class NotificationManager:
             """
             """
         
         
         try:
         try:
-            from src.config.config import Config
             if Config.TELEGRAM_CHAT_ID:
             if Config.TELEGRAM_CHAT_ID:
                 await self.bot_application.bot.send_message(
                 await self.bot_application.bot.send_message(
                     chat_id=Config.TELEGRAM_CHAT_ID,
                     chat_id=Config.TELEGRAM_CHAT_ID,
@@ -346,7 +358,6 @@ class NotificationManager:
             return
             return
         
         
         try:
         try:
-            from src.config.config import Config
             if Config.TELEGRAM_CHAT_ID:
             if Config.TELEGRAM_CHAT_ID:
                 await self.bot_application.bot.send_message(
                 await self.bot_application.bot.send_message(
                     chat_id=Config.TELEGRAM_CHAT_ID,
                     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}")
             logger.info(f"๐Ÿ›‘ Stop loss execution notification sent: {token} {position_side} @ ${price:.2f}")
             
             
         except Exception as e:
         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.config.config import Config
 from src.clients.hyperliquid_client import HyperliquidClient
 from src.clients.hyperliquid_client import HyperliquidClient
 from src.trading.trading_stats import TradingStats
 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__)
 logger = logging.getLogger(__name__)
 
 
@@ -237,6 +238,7 @@ class TradingEngine:
                                 limit_price_arg: Optional[float] = None,
                                 limit_price_arg: Optional[float] = None,
                                 stop_loss_price: Optional[float] = None) -> Dict[str, Any]:
                                 stop_loss_price: Optional[float] = None) -> Dict[str, Any]:
         symbol = f"{token}/USDC:USDC"
         symbol = f"{token}/USDC:USDC"
+        formatter = get_formatter() # Get formatter
         
         
         try:
         try:
             # Validate inputs
             # Validate inputs
@@ -284,10 +286,11 @@ class TradingEngine:
 
 
             # 2. Place the order with the exchange
             # 2. Place the order with the exchange
             if order_type_for_stats == 'limit':
             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)
                 exchange_order_data, error_msg = self.client.place_limit_order(symbol, 'buy', token_amount, order_placement_price)
             else: # Market order
             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)
                 exchange_order_data, error_msg = self.client.place_market_order(symbol, 'buy', token_amount)
             
             
             if error_msg:
             if error_msg:
@@ -395,6 +398,7 @@ class TradingEngine:
                                  limit_price_arg: Optional[float] = None,
                                  limit_price_arg: Optional[float] = None,
                                  stop_loss_price: Optional[float] = None) -> Dict[str, Any]:
                                  stop_loss_price: Optional[float] = None) -> Dict[str, Any]:
         symbol = f"{token}/USDC:USDC"
         symbol = f"{token}/USDC:USDC"
+        formatter = get_formatter() # Get formatter
         
         
         try:
         try:
             if usdc_amount <= 0:
             if usdc_amount <= 0:
@@ -438,10 +442,10 @@ class TradingEngine:
 
 
             # 2. Place the order with the exchange
             # 2. Place the order with the exchange
             if order_type_for_stats == 'limit':
             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)
                 exchange_order_data, error_msg = self.client.place_limit_order(symbol, 'sell', token_amount, order_placement_price)
             else: # Market order
             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)
                 exchange_order_data, error_msg = self.client.place_market_order(symbol, 'sell', token_amount)
             
             
             if error_msg:
             if error_msg:
@@ -542,6 +546,7 @@ class TradingEngine:
         if not position:
         if not position:
             return {"success": False, "error": f"No open position found for {token}"}
             return {"success": False, "error": f"No open position found for {token}"}
         
         
+        formatter = get_formatter() # Get formatter
         try:
         try:
             symbol = f"{token}/USDC:USDC"
             symbol = f"{token}/USDC:USDC"
             position_type, exit_side, contracts_to_close = self.get_position_direction(position)
             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."}
                 return {"success": False, "error": "Failed to record exit order intent in database."}
 
 
             # 2. Execute market order to close position
             # 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)
             exchange_order_data, error_msg = self.client.place_market_order(symbol, exit_side, contracts_to_close)
             
             
             if error_msg:
             if error_msg:
@@ -640,6 +645,7 @@ class TradingEngine:
         if not position:
         if not position:
             return {"success": False, "error": f"No open position found for {token}"}
             return {"success": False, "error": f"No open position found for {token}"}
         
         
+        formatter = get_formatter() # Get formatter
         try:
         try:
             symbol = f"{token}/USDC:USDC"
             symbol = f"{token}/USDC:USDC"
             position_type, exit_side, contracts = self.get_position_direction(position)
             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."}
                 return {"success": False, "error": "Failed to record SL order intent in database."}
 
 
             # 2. Place limit order at stop loss price
             # 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)
             exchange_order_data, error_msg = self.client.place_limit_order(symbol, exit_side, contracts, stop_price)
             
             
             if error_msg:
             if error_msg:
@@ -730,6 +736,7 @@ class TradingEngine:
         if not position:
         if not position:
             return {"success": False, "error": f"No open position found for {token}"}
             return {"success": False, "error": f"No open position found for {token}"}
         
         
+        formatter = get_formatter() # Get formatter
         try:
         try:
             symbol = f"{token}/USDC:USDC"
             symbol = f"{token}/USDC:USDC"
             position_type, exit_side, contracts = self.get_position_direction(position)
             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."}
                 return {"success": False, "error": "Failed to record TP order intent in database."}
 
 
             # 2. Place limit order at take profit price
             # 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)
             exchange_order_data, error_msg = self.client.place_limit_order(symbol, exit_side, contracts, profit_price)
             
             
             if error_msg:
             if error_msg:
@@ -958,6 +965,7 @@ class TradingEngine:
         if not self.stats:
         if not self.stats:
             return {"success": False, "error": "TradingStats not available."}
             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)
         trigger_order_details = self.stats.get_order_by_db_id(original_trigger_order_db_id)
         if not trigger_order_details:
         if not trigger_order_details:
             return {"success": False, "error": f"Original trigger order DB ID {original_trigger_order_db_id} not found."}
             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
         order_price = stop_price  # Default to stop price
 
 
         if current_price and current_price > 0:
         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':
             if sl_order_side.lower() == 'buy':
                 # SHORT position stop loss (BUY to close)
                 # 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 (price moved beyond stop)
                 if current_price > stop_price:
                 if current_price > stop_price:
                     use_market_order = True
                     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:
                 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':
             elif sl_order_side.lower() == 'sell':
                 # LONG position stop loss (SELL to close)
                 # 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 (price moved beyond stop)
                 if current_price < stop_price:
                 if current_price < stop_price:
                     use_market_order = True
                     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:
                 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:
         if use_market_order:
             order_type_for_actual_sl = 'market'
             order_type_for_actual_sl = 'market'
@@ -1034,10 +1046,10 @@ class TradingEngine:
 
 
         # 2. Place the actual SL order on the exchange
         # 2. Place the actual SL order on the exchange
         if use_market_order:
         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)
             exchange_order_data, error_msg = self.client.place_market_order(symbol, sl_order_side, amount)
         else:
         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)
             exchange_order_data, error_msg = self.client.place_limit_order(symbol, sl_order_side, amount, stop_price)
 
 
         if error_msg:
         if error_msg:
@@ -1070,7 +1082,7 @@ class TradingEngine:
         if use_market_order:
         if use_market_order:
             success_message += " for immediate execution (price moved beyond stop level)."
             success_message += " for immediate execution (price moved beyond stop level)."
         else:
         else:
-            success_message += f" at ${stop_price}."
+            success_message += f" at {formatter.format_price_with_symbol(stop_price, token)}."
 
 
         return {
         return {
             "success": True,
             "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
 # ๐Ÿ†• Import the migration runner
 from src.migrations.migrate_db import run_migrations as run_db_migrations
 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__)
 logger = logging.getLogger(__name__)
 
 
@@ -247,9 +248,11 @@ class TradingStats:
             # Also set start_date if it's the first time setting balance
             # 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:
             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())
                  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:
         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):
     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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
             "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)
             (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]]:
     def get_all_trades(self) -> List[Dict[str, Any]]:
         """Fetch all trades from the database, ordered by timestamp."""
         """Fetch all trades from the database, ordered by timestamp."""
         return self._fetch_query("SELECT * FROM trades ORDER BY timestamp ASC")
         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]]:
     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."""
         """Format stats for Telegram display using data from DB."""
         try:
         try:
             stats = self.get_comprehensive_stats(current_balance)
             stats = self.get_comprehensive_stats(current_balance)
+            formatter = get_formatter() # Get formatter
             
             
             basic = stats['basic']
             basic = stats['basic']
             perf = stats['performance']
             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']
             effective_current_balance = stats['current_balance']
             initial_bal = basic['initial_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
             total_return_pct = (total_pnl_val / initial_bal * 100) if initial_bal > 0 else 0.0
             
             
             pnl_emoji = "๐ŸŸข" if total_pnl_val >= 0 else "๐Ÿ”ด"
             pnl_emoji = "๐ŸŸข" if total_pnl_val >= 0 else "๐Ÿ”ด"
             
             
             open_positions_count = self._get_open_positions_count_from_db()
             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'")
             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)
             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
             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()
             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:
             if adjustments_summary['adjustment_count'] > 0:
                 adj_emoji = "๐Ÿ’ฐ" if adjustments_summary['net_adjustment'] >= 0 else "๐Ÿ’ธ"
                 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:
         except Exception as e:
             logger.error(f"Error formatting stats message: {e}", exc_info=True)
             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]]:
     def get_recent_trades(self, limit: int = 10) -> List[Dict[str, Any]]:
         """Get recent trades from DB."""
         """Get recent trades from DB."""
@@ -939,31 +952,34 @@ class TradingStats:
                        deposit_id: Optional[str] = None, description: Optional[str] = None):
                        deposit_id: Optional[str] = None, description: Optional[str] = None):
         """Record a deposit."""
         """Record a deposit."""
         ts = timestamp if timestamp else datetime.now(timezone.utc).isoformat()
         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(
         self._execute_query(
             "INSERT INTO balance_adjustments (adjustment_id, timestamp, type, amount, description) VALUES (?, ?, ?, ?, ?)",
             "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
         # Adjust initial_balance in metadata to reflect capital changes
         current_initial = float(self._get_metadata('initial_balance') or '0.0')
         current_initial = float(self._get_metadata('initial_balance') or '0.0')
         self._set_metadata('initial_balance', str(current_initial + amount))
         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, 
     def record_withdrawal(self, amount: float, timestamp: Optional[str] = None, 
                           withdrawal_id: Optional[str] = None, description: Optional[str] = None):
                           withdrawal_id: Optional[str] = None, description: Optional[str] = None):
         """Record a withdrawal."""
         """Record a withdrawal."""
         ts = timestamp if timestamp else datetime.now(timezone.utc).isoformat()
         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(
         self._execute_query(
             "INSERT INTO balance_adjustments (adjustment_id, timestamp, type, amount, description) VALUES (?, ?, ?, ?, ?)",
             "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')
         current_initial = float(self._get_metadata('initial_balance') or '0.0')
         self._set_metadata('initial_balance', str(current_initial - amount))
         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]:
     def get_balance_adjustments_summary(self) -> Dict[str, Any]:
         """Get summary of all balance adjustments from DB."""
         """Get summary of all balance adjustments from DB."""
@@ -1083,7 +1099,7 @@ class TradingStats:
 
 
         if not set_clauses:
         if not set_clauses:
             logger.info("No fields to update for order.")
             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 = ?")
         set_clauses.append("timestamp_updated = ?")
         params.append(now_iso)
         params.append(now_iso)
@@ -1093,7 +1109,7 @@ class TradingStats:
         query = f"UPDATE orders SET { ', '.join(set_clauses) } WHERE {identifier_clause}"
         query = f"UPDATE orders SET { ', '.join(set_clauses) } WHERE {identifier_clause}"
         
         
         try:
         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}"
             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:
             if set_exchange_order_id is not None:
                 log_msg += f", Exchange ID set to '{set_exchange_order_id}'"
                 log_msg += f", Exchange ID set to '{set_exchange_order_id}'"
@@ -1319,7 +1335,12 @@ class TradingStats:
             
             
             self._execute_query(query, params)
             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
             return True
             
             
         except Exception as e:
         except Exception as e:
@@ -1345,8 +1366,11 @@ class TradingStats:
             
             
             self._execute_query(query, params)
             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 "๐Ÿ”ด"
             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
             return True
             
             
         except Exception as e:
         except Exception as e:
@@ -1391,7 +1415,10 @@ class TradingStats:
             
             
             self._execute_query(query, params)
             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
             return True
             
             
         except Exception as e:
         except Exception as e:
@@ -1414,7 +1441,10 @@ class TradingStats:
             
             
             self._execute_query(query, params)
             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
             return True
             
             
         except Exception as e:
         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)