Ver código fonte

Refactor token case normalization across commands and alarm manager - Introduced a new utility function, _normalize_token_case, to standardize token case handling in InfoCommands, ManagementCommands, TradingCommands, AlarmManager, TradingStats, and PriceFormatter. This enhancement improves consistency in token processing, particularly for mixed-case tokens, ensuring accurate command execution and alarm management.

Carles Sentis 4 dias atrás
pai
commit
a80a67f780

+ 19 - 4
src/commands/info_commands.py

@@ -4,16 +4,31 @@ Info Commands - Handles information-related Telegram commands.
 """
 """
 
 
 import logging
 import logging
-from datetime import datetime
+from datetime import datetime, timezone, timedelta
 from typing import Optional, Dict, Any, List
 from typing import Optional, Dict, Any, List
 from telegram import Update
 from telegram import Update
 from telegram.ext import ContextTypes
 from telegram.ext import ContextTypes
+import matplotlib.pyplot as plt
+import matplotlib.dates as mdates
+from io import BytesIO
+import traceback
 
 
 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.price_formatter import format_price_with_symbol, get_formatter
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
+def _normalize_token_case(token: str) -> str:
+    """
+    Normalize token case: if any characters are already uppercase, keep as-is.
+    Otherwise, convert to uppercase. This handles mixed-case tokens like kPEPE, kBONK.
+    """
+    # Check if any character is already uppercase
+    if any(c.isupper() for c in token):
+        return token  # Keep original case for mixed-case tokens
+    else:
+        return token.upper()  # Convert to uppercase for all-lowercase input
+
 class InfoCommands:
 class InfoCommands:
     """Handles all information-related Telegram commands."""
     """Handles all information-related Telegram commands."""
     
     
@@ -708,7 +723,7 @@ class InfoCommands:
         
         
         # Get token from arguments or use default
         # Get token from arguments or use default
         if context.args and len(context.args) > 0:
         if context.args and len(context.args) > 0:
-            token = context.args[0].upper()
+            token = _normalize_token_case(context.args[0])
         else:
         else:
             token = Config.DEFAULT_TRADING_TOKEN
             token = Config.DEFAULT_TRADING_TOKEN
         
         
@@ -774,7 +789,7 @@ class InfoCommands:
         
         
         # Get token from arguments or use default
         # Get token from arguments or use default
         if context.args and len(context.args) > 0:
         if context.args and len(context.args) > 0:
-            token = context.args[0].upper()
+            token = _normalize_token_case(context.args[0])
         else:
         else:
             token = Config.DEFAULT_TRADING_TOKEN
             token = Config.DEFAULT_TRADING_TOKEN
         
         
@@ -821,7 +836,7 @@ class InfoCommands:
             # Check if specific token is requested
             # Check if specific token is requested
             if context.args and len(context.args) >= 1:
             if context.args and len(context.args) >= 1:
                 # Detailed performance for specific token
                 # Detailed performance for specific token
-                token = context.args[0].upper()
+                token = _normalize_token_case(context.args[0])
                 await self._show_token_performance(chat_id, token, context)
                 await self._show_token_performance(chat_id, token, context)
             else:
             else:
                 # Show token performance ranking
                 # Show token performance ranking

+ 13 - 2
src/commands/management_commands.py

@@ -17,6 +17,17 @@ from src.monitoring.alarm_manager import AlarmManager
 
 
 logger = logging.getLogger(__name__)
 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 ManagementCommands:
 class ManagementCommands:
     """Handles all management-related Telegram commands."""
     """Handles all management-related Telegram commands."""
     
     
@@ -137,7 +148,7 @@ class ManagementCommands:
                     return
                     return
                 except ValueError:
                 except ValueError:
                     # Not a number, treat as token
                     # Not a number, treat as token
-                    token = arg.upper()
+                    token = _normalize_token_case(arg)
                     alarms = self.alarm_manager.get_alarms_by_token(token)
                     alarms = self.alarm_manager.get_alarms_by_token(token)
                     message = self.alarm_manager.format_alarm_list(alarms, f"{token} Price Alarms")
                     message = self.alarm_manager.format_alarm_list(alarms, f"{token} Price Alarms")
                     await context.bot.send_message(chat_id=chat_id, text=message, parse_mode='HTML')
                     await context.bot.send_message(chat_id=chat_id, text=message, parse_mode='HTML')
@@ -145,7 +156,7 @@ class ManagementCommands:
             
             
             elif len(context.args) == 2:
             elif len(context.args) == 2:
                 # Set new alarm: /alarm TOKEN PRICE
                 # Set new alarm: /alarm TOKEN PRICE
-                token = context.args[0].upper()
+                token = _normalize_token_case(context.args[0])
                 target_price = float(context.args[1])
                 target_price = float(context.args[1])
                 
                 
                 # Get current market price
                 # Get current market price

+ 17 - 6
src/commands/trading_commands.py

@@ -13,6 +13,17 @@ from src.utils.price_formatter import get_formatter
 
 
 logger = logging.getLogger(__name__)
 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 TradingCommands:
 class TradingCommands:
     """Handles all trading-related Telegram commands."""
     """Handles all trading-related Telegram commands."""
     
     
@@ -46,7 +57,7 @@ class TradingCommands:
                 ))
                 ))
                 return
                 return
             
             
-            token = context.args[0].upper()
+            token = _normalize_token_case(context.args[0])
             usdc_amount = float(context.args[1])
             usdc_amount = float(context.args[1])
             
             
             # Parse arguments for price and stop loss
             # Parse arguments for price and stop loss
@@ -170,7 +181,7 @@ This will {"place a limit buy order" if limit_price else "execute a market buy o
                 ))
                 ))
                 return
                 return
             
             
-            token = context.args[0].upper()
+            token = _normalize_token_case(context.args[0])
             usdc_amount = float(context.args[1])
             usdc_amount = float(context.args[1])
             
             
             # Parse arguments (similar to long_command)
             # Parse arguments (similar to long_command)
@@ -288,7 +299,7 @@ This will {"place a limit sell order" if limit_price else "execute a market sell
                 ))
                 ))
                 return
                 return
             
             
-            token = context.args[0].upper()
+            token = _normalize_token_case(context.args[0])
             
             
             # Find the position
             # Find the position
             position = self.trading_engine.find_position(token)
             position = self.trading_engine.find_position(token)
@@ -365,7 +376,7 @@ This will {"place a limit sell order" if limit_price else "execute a market sell
                 ))
                 ))
                 return
                 return
             
             
-            token = context.args[0].upper()
+            token = _normalize_token_case(context.args[0])
             stop_price = float(context.args[1])
             stop_price = float(context.args[1])
             
             
             # Find the position
             # Find the position
@@ -469,7 +480,7 @@ This will place a limit {exit_side} order at {formatter.format_price_with_symbol
                 ))
                 ))
                 return
                 return
             
             
-            token = context.args[0].upper()
+            token = _normalize_token_case(context.args[0])
             tp_price = float(context.args[1])
             tp_price = float(context.args[1])
             
             
             # Find the position
             # Find the position
@@ -573,7 +584,7 @@ This will place a limit {exit_side} order at {formatter.format_price_with_symbol
                 ))
                 ))
                 return
                 return
             
             
-            token = context.args[0].upper()
+            token = _normalize_token_case(context.args[0])
             
             
             confirmation_text = f"""
             confirmation_text = f"""
 🚫 <b>Cancel All Orders Confirmation</b>
 🚫 <b>Cancel All Orders Confirmation</b>

+ 14 - 3
src/monitoring/alarm_manager.py

@@ -8,11 +8,22 @@ Manages price alarms for tokens with persistence and monitoring integration.
 import json
 import json
 import os
 import os
 import logging
 import logging
-from datetime import datetime
+from datetime import datetime, timezone
 from typing import Dict, List, Any, Optional
 from typing import Dict, List, Any, Optional
 
 
 logger = logging.getLogger(__name__)
 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 AlarmManager:
 class AlarmManager:
     """Manages price alarms with persistence and monitoring."""
     """Manages price alarms with persistence and monitoring."""
     
     
@@ -75,7 +86,7 @@ class AlarmManager:
         
         
         alarm = {
         alarm = {
             'id': self.data['next_id'],
             'id': self.data['next_id'],
-            'token': token.upper(),
+            'token': _normalize_token_case(token),
             'target_price': target_price,
             'target_price': target_price,
             'current_price_at_creation': current_price,
             'current_price_at_creation': current_price,
             'direction': direction,
             'direction': direction,
@@ -102,7 +113,7 @@ class AlarmManager:
     
     
     def get_alarms_by_token(self, token: str) -> List[Dict[str, Any]]:
     def get_alarms_by_token(self, token: str) -> List[Dict[str, Any]]:
         """Get all active alarms for a specific token."""
         """Get all active alarms for a specific token."""
-        token = token.upper()
+        token = _normalize_token_case(token)
         return [alarm for alarm in self.data.get('alarms', []) if alarm['token'] == token and alarm['status'] == 'active']
         return [alarm for alarm in self.data.get('alarms', []) if alarm['token'] == token and alarm['status'] == 'active']
     
     
     def get_all_active_alarms(self) -> List[Dict[str, Any]]:
     def get_all_active_alarms(self) -> List[Dict[str, Any]]:

+ 13 - 2
src/trading/trading_stats.py

@@ -17,6 +17,17 @@ import uuid
 
 
 logger = logging.getLogger(__name__)
 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 TradingStats:
 class TradingStats:
     """Comprehensive trading statistics tracker using SQLite."""
     """Comprehensive trading statistics tracker using SQLite."""
 
 
@@ -713,7 +724,7 @@ class TradingStats:
 
 
     def get_token_detailed_stats(self, token: str) -> Dict[str, Any]:
     def get_token_detailed_stats(self, token: str) -> Dict[str, Any]:
         """Get detailed statistics for a specific token using DB queries and cycle calculation."""
         """Get detailed statistics for a specific token using DB queries and cycle calculation."""
-        upper_token = token.upper()
+        upper_token = _normalize_token_case(token)
         
         
         # Get all trades for this specific token (symbol starts with token + '/')
         # Get all trades for this specific token (symbol starts with token + '/')
         # This is simpler than trying to filter cycles by token string directly in SQL for complex symbols
         # This is simpler than trying to filter cycles by token string directly in SQL for complex symbols
@@ -738,7 +749,7 @@ class TradingStats:
         # We need a version of cycle calculation that can be limited or we filter its output.
         # We need a version of cycle calculation that can be limited or we filter its output.
         
         
         all_completed_cycles = self.calculate_completed_trade_cycles()
         all_completed_cycles = self.calculate_completed_trade_cycles()
-        token_cycles = [c for c in all_completed_cycles if c['token'].upper() == upper_token]
+        token_cycles = [c for c in all_completed_cycles if _normalize_token_case(c['token']) == upper_token]
 
 
         total_individual_orders = len(all_trades_for_token_symbol_prefix)
         total_individual_orders = len(all_trades_for_token_symbol_prefix)
         buy_orders = len([t for t in all_trades_for_token_symbol_prefix if t['side'] == 'buy'])
         buy_orders = len([t for t in all_trades_for_token_symbol_prefix if t['side'] == 'buy'])

+ 11 - 1
src/utils/price_formatter.py

@@ -9,6 +9,16 @@ from functools import lru_cache
 
 
 logger = logging.getLogger(__name__)
 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:
 class PriceFormatter:
     """Handles price formatting with proper decimal precision from exchange data."""
     """Handles price formatting with proper decimal precision from exchange data."""
@@ -101,7 +111,7 @@ class PriceFormatter:
     
     
     def _get_default_decimals_for_token(self, token: str) -> int:
     def _get_default_decimals_for_token(self, token: str) -> int:
         """Get smart default decimal places based on token characteristics."""
         """Get smart default decimal places based on token characteristics."""
-        token = token.upper()
+        token = _normalize_token_case(token)
         
         
         # High-value tokens (usually need fewer decimals)
         # High-value tokens (usually need fewer decimals)
         if token in ['BTC', 'ETH', 'BNB', 'SOL', 'ADA', 'DOT', 'AVAX', 'MATIC', 'LINK']:
         if token in ['BTC', 'ETH', 'BNB', 'SOL', 'ADA', 'DOT', 'AVAX', 'MATIC', 'LINK']: