Ver código fonte

Enhance trading bot with automatic stop loss functionality - Implemented a new feature allowing users to set stop loss parameters for long and short orders via Telegram commands. Updated README for security best practices and command usage, and improved order management with notifications for pending stop losses. Version updated to 2.1.1.

Carles Sentis 5 dias atrás
pai
commit
ccb9a192ce
3 arquivos alterados com 386 adições e 30 exclusões
  1. 6 3
      README.md
  2. 379 26
      src/telegram_bot.py
  3. 1 1
      trading_bot.py

+ 6 - 3
README.md

@@ -170,9 +170,11 @@ Send `/start` for one-tap access to:
 
 
 Following [CCXT standards](https://github.com/ccxt/ccxt) for exchange compatibility:
 Following [CCXT standards](https://github.com/ccxt/ccxt) for exchange compatibility:
 
 
+> ⚠️ **IMPORTANT SECURITY**: Use the **API Generator Key** from [https://app.hyperliquid.xyz/API](https://app.hyperliquid.xyz/API) for trading operations. **DO NOT use your wallet's private key directly** for security reasons. Generate a dedicated API key with appropriate permissions for trading through this bot.
+
 ```env
 ```env
 # Hyperliquid API (CCXT Style)
 # Hyperliquid API (CCXT Style)
-HYPERLIQUID_PRIVATE_KEY=your_private_key_here
+HYPERLIQUID_PRIVATE_KEY=your_api_generator_key_here  # ← Use API Generator Key from app.hyperliquid.xyz/API
 HYPERLIQUID_SECRET_KEY=your_secret_key_here
 HYPERLIQUID_SECRET_KEY=your_secret_key_here
 HYPERLIQUID_TESTNET=true
 HYPERLIQUID_TESTNET=true
 
 
@@ -241,9 +243,10 @@ python utils/simple_chat_id.py
 - Switch to mainnet only when fully tested
 - Switch to mainnet only when fully tested
 
 
 ### **Your Keys**
 ### **Your Keys**
-- **Private Key**: From Hyperliquid account settings
+- **API Generator Key**: Generate a dedicated API key from [Hyperliquid API Panel](https://app.hyperliquid.xyz/API) - **NEVER use your wallet's private key directly**
 - **Secret Key**: May be optional depending on implementation
 - **Secret Key**: May be optional depending on implementation
-- **Keep secure** - never share your private keys
+- **Keep secure** - never share your API keys or wallet credentials
+- **Best Practice**: Use API keys with limited permissions for trading bots
 
 
 ### **Statistics Persistence**
 ### **Statistics Persistence**
 - ✅ **YES** - All statistics are permanently saved
 - ✅ **YES** - All statistics are permanently saved

+ 379 - 26
src/telegram_bot.py

@@ -54,6 +54,9 @@ class TelegramTradingBot:
         # Alarm management
         # Alarm management
         self.alarm_manager = AlarmManager()
         self.alarm_manager = AlarmManager()
         
         
+        # Pending stop loss storage
+        self.pending_stop_losses = {}  # Format: {order_id: {'token': str, 'stop_price': float, 'side': str, 'amount': float, 'order_type': str}}
+        
         # Initialize stats
         # Initialize stats
         self._initialize_stats()
         self._initialize_stats()
     
     
@@ -114,13 +117,17 @@ class TelegramTradingBot:
 
 
 <b>🚀 Trading:</b>
 <b>🚀 Trading:</b>
 • /long BTC 100 - Long position
 • /long BTC 100 - Long position
+• /long BTC 100 45000 - Limit order
+• /long BTC 100 sl:44000 - With stop loss
 • /short ETH 50 - Short position  
 • /short ETH 50 - Short position  
+• /short ETH 50 3500 sl:3600 - With stop loss
 • /exit BTC - Close position
 • /exit BTC - Close position
 • /coo BTC - Cancel open orders
 • /coo BTC - Cancel open orders
 
 
 <b>🛡️ Risk Management:</b>
 <b>🛡️ Risk Management:</b>
 • Enabled: {risk_enabled}
 • Enabled: {risk_enabled}
 • Auto Stop Loss: {stop_loss}%
 • Auto Stop Loss: {stop_loss}%
+• Order Stop Loss: Use sl:price parameter
 • /sl BTC 44000 - Manual stop loss
 • /sl BTC 44000 - Manual stop loss
 • /tp BTC 50000 - Take profit order
 • /tp BTC 50000 - Take profit order
 
 
@@ -147,6 +154,7 @@ class TelegramTradingBot:
 • Price alarm triggers
 • Price alarm triggers
 • External trade detection & sync
 • External trade detection & sync
 • Auto stats synchronization
 • Auto stats synchronization
+• Automatic stop loss placement
 • {heartbeat}-second monitoring interval
 • {heartbeat}-second monitoring interval
 
 
 <b>📊 Universal Trade Tracking:</b>
 <b>📊 Universal Trade Tracking:</b>
@@ -171,6 +179,7 @@ Type /help for detailed command information.
 • Comprehensive performance tracking
 • Comprehensive performance tracking
 • Real-time balance monitoring
 • Real-time balance monitoring
 • Risk metrics calculation
 • Risk metrics calculation
+• Automatic stop loss protection
 
 
 <b>📱 Mobile Optimized:</b>
 <b>📱 Mobile Optimized:</b>
 • Quick action buttons
 • Quick action buttons
@@ -237,8 +246,12 @@ For support, contact your bot administrator.
 <b>🚀 Perps Trading:</b>
 <b>🚀 Perps Trading:</b>
 • /long BTC 100 - Long BTC with $100 USDC (Market Order)
 • /long BTC 100 - Long BTC with $100 USDC (Market Order)
 • /long BTC 100 45000 - Long BTC with $100 USDC at $45,000 (Limit Order)
 • /long BTC 100 45000 - Long BTC with $100 USDC at $45,000 (Limit Order)
+• /long BTC 100 sl:44000 - Market order with automatic stop loss
+• /long BTC 100 45000 sl:44000 - Limit order with automatic stop loss
 • /short ETH 50 - Short ETH with $50 USDC (Market Order)
 • /short ETH 50 - Short ETH with $50 USDC (Market Order)
 • /short ETH 50 3500 - Short ETH with $50 USDC at $3,500 (Limit Order)
 • /short ETH 50 3500 - Short ETH with $50 USDC at $3,500 (Limit Order)
+• /short ETH 50 sl:3600 - Market order with automatic stop loss
+• /short ETH 50 3500 sl:3600 - Limit order with automatic stop loss
 • /exit BTC - Close BTC position with Market Order
 • /exit BTC - Close BTC position with Market Order
 
 
 <b>🛡️ Risk Management:</b>
 <b>🛡️ Risk Management:</b>
@@ -249,6 +262,7 @@ For support, contact your bot administrator.
 • Enabled: {risk_enabled}
 • Enabled: {risk_enabled}
 • Stop Loss: {stop_loss}% (automatic execution)
 • Stop Loss: {stop_loss}% (automatic execution)
 • Monitoring: Every {heartbeat} seconds
 • Monitoring: Every {heartbeat} seconds
+• Order-based: Use sl:price parameter for automatic placement
 
 
 <b>📋 Order Management:</b>
 <b>📋 Order Management:</b>
 • /orders - Show all open orders
 • /orders - Show all open orders
@@ -283,6 +297,7 @@ For support, contact your bot administrator.
 • Real-time balance monitoring
 • Real-time balance monitoring
 • Deposit/withdrawal tracking (hourly)
 • Deposit/withdrawal tracking (hourly)
 • Risk metrics calculation
 • Risk metrics calculation
+• Automatic stop loss placement
 
 
 <b>📱 Mobile Optimized:</b>
 <b>📱 Mobile Optimized:</b>
 • Quick action buttons
 • Quick action buttons
@@ -729,7 +744,15 @@ Tap any button below for instant access to bot functions:
             usdc_amount = float(parts[3])
             usdc_amount = float(parts[3])
             price = float(parts[4])
             price = float(parts[4])
             is_limit = len(parts) > 5 and parts[5] == 'limit'
             is_limit = len(parts) > 5 and parts[5] == 'limit'
-            await self._execute_long_order(query, token, usdc_amount, price, is_limit)
+            
+            # Parse stop loss if present
+            stop_loss_price = None
+            if len(parts) > 6 and parts[6] == 'sl':
+                stop_loss_price = float(parts[7])
+            elif len(parts) > 5 and parts[5] == 'sl':
+                stop_loss_price = float(parts[6])
+            
+            await self._execute_long_order(query, token, usdc_amount, price, is_limit, stop_loss_price)
             return
             return
             
             
         elif callback_data.startswith('confirm_short_'):
         elif callback_data.startswith('confirm_short_'):
@@ -738,7 +761,15 @@ Tap any button below for instant access to bot functions:
             usdc_amount = float(parts[3])
             usdc_amount = float(parts[3])
             price = float(parts[4])
             price = float(parts[4])
             is_limit = len(parts) > 5 and parts[5] == 'limit'
             is_limit = len(parts) > 5 and parts[5] == 'limit'
-            await self._execute_short_order(query, token, usdc_amount, price, is_limit)
+            
+            # Parse stop loss if present
+            stop_loss_price = None
+            if len(parts) > 6 and parts[6] == 'sl':
+                stop_loss_price = float(parts[7])
+            elif len(parts) > 5 and parts[5] == 'sl':
+                stop_loss_price = float(parts[6])
+            
+            await self._execute_short_order(query, token, usdc_amount, price, is_limit, stop_loss_price)
             return
             return
             
             
         elif callback_data.startswith('confirm_exit_'):
         elif callback_data.startswith('confirm_exit_'):
@@ -817,7 +848,7 @@ Tap any button below for instant access to bot functions:
         elif callback_data == "logs":
         elif callback_data == "logs":
             await self.logs_command(fake_update, context)
             await self.logs_command(fake_update, context)
     
     
-    async def _execute_long_order(self, query, token: str, usdc_amount: float, price: float, is_limit: bool):
+    async def _execute_long_order(self, query, token: str, usdc_amount: float, price: float, is_limit: bool, stop_loss_price: float = None):
         """Execute a long order."""
         """Execute a long order."""
         symbol = f"{token}/USDC:USDC"
         symbol = f"{token}/USDC:USDC"
         
         
@@ -839,6 +870,20 @@ Tap any button below for instant access to bot functions:
                 actual_price = order.get('average', price)  # Use actual fill price if available
                 actual_price = order.get('average', price)  # Use actual fill price if available
                 action_type = self.stats.record_trade_with_enhanced_tracking(symbol, 'buy', token_amount, actual_price, order_id, "bot")
                 action_type = self.stats.record_trade_with_enhanced_tracking(symbol, 'buy', token_amount, actual_price, order_id, "bot")
                 
                 
+                # Save pending stop loss if provided
+                if stop_loss_price is not None:
+                    self.pending_stop_losses[order_id] = {
+                        'token': token,
+                        'symbol': symbol,
+                        'stop_price': stop_loss_price,
+                        'side': 'sell',  # For long position, stop loss is a sell order
+                        'amount': token_amount,
+                        'order_type': 'long',
+                        'original_order_id': order_id,
+                        'is_limit': is_limit
+                    }
+                    logger.info(f"💾 Saved pending stop loss for order {order_id}: sell {token_amount:.6f} {token} @ ${stop_loss_price}")
+                
                 success_message = f"""
                 success_message = f"""
 ✅ <b>Long Position {'Placed' if is_limit else 'Opened'} Successfully!</b>
 ✅ <b>Long Position {'Placed' if is_limit else 'Opened'} Successfully!</b>
 
 
@@ -850,9 +895,18 @@ Tap any button below for instant access to bot functions:
 • USDC Value: ~${usdc_amount:,.2f}
 • USDC Value: ~${usdc_amount:,.2f}
 • Order Type: {'Limit' if is_limit else 'Market'} Order
 • Order Type: {'Limit' if is_limit else 'Market'} Order
 • Order ID: <code>{order_id}</code>
 • Order ID: <code>{order_id}</code>
-
-🚀 Your {'limit order has been placed' if is_limit else 'long position is now active'}!
-                """
+"""
+                
+                # Add stop loss confirmation if provided
+                if stop_loss_price is not None:
+                    success_message += f"""
+🛑 <b>Stop Loss Saved:</b>
+• Stop Price: ${stop_loss_price:,.2f}
+• Will be placed automatically when order fills
+• Status: PENDING ⏳
+"""
+                
+                success_message += f"\n🚀 Your {'limit order has been placed' if is_limit else 'long position is now active'}!"
                 
                 
                 await query.edit_message_text(success_message, parse_mode='HTML')
                 await query.edit_message_text(success_message, parse_mode='HTML')
                 logger.info(f"Long {'limit order placed' if is_limit else 'position opened'}: {token_amount:.6f} {token} @ ${price} ({'Limit' if is_limit else 'Market'})")
                 logger.info(f"Long {'limit order placed' if is_limit else 'position opened'}: {token_amount:.6f} {token} @ ${price} ({'Limit' if is_limit else 'Market'})")
@@ -864,7 +918,7 @@ Tap any button below for instant access to bot functions:
             await query.edit_message_text(error_message)
             await query.edit_message_text(error_message)
             logger.error(f"Error in long order: {e}")
             logger.error(f"Error in long order: {e}")
     
     
-    async def _execute_short_order(self, query, token: str, usdc_amount: float, price: float, is_limit: bool):
+    async def _execute_short_order(self, query, token: str, usdc_amount: float, price: float, is_limit: bool, stop_loss_price: float = None):
         """Execute a short order."""
         """Execute a short order."""
         symbol = f"{token}/USDC:USDC"
         symbol = f"{token}/USDC:USDC"
         
         
@@ -886,6 +940,20 @@ Tap any button below for instant access to bot functions:
                 actual_price = order.get('average', price)  # Use actual fill price if available
                 actual_price = order.get('average', price)  # Use actual fill price if available
                 action_type = self.stats.record_trade_with_enhanced_tracking(symbol, 'sell', token_amount, actual_price, order_id, "bot")
                 action_type = self.stats.record_trade_with_enhanced_tracking(symbol, 'sell', token_amount, actual_price, order_id, "bot")
                 
                 
+                # Save pending stop loss if provided
+                if stop_loss_price is not None:
+                    self.pending_stop_losses[order_id] = {
+                        'token': token,
+                        'symbol': symbol,
+                        'stop_price': stop_loss_price,
+                        'side': 'buy',  # For short position, stop loss is a buy order
+                        'amount': token_amount,
+                        'order_type': 'short',
+                        'original_order_id': order_id,
+                        'is_limit': is_limit
+                    }
+                    logger.info(f"💾 Saved pending stop loss for order {order_id}: buy {token_amount:.6f} {token} @ ${stop_loss_price}")
+                
                 success_message = f"""
                 success_message = f"""
 ✅ <b>Short Position {'Placed' if is_limit else 'Opened'} Successfully!</b>
 ✅ <b>Short Position {'Placed' if is_limit else 'Opened'} Successfully!</b>
 
 
@@ -897,9 +965,18 @@ Tap any button below for instant access to bot functions:
 • USDC Value: ~${usdc_amount:,.2f}
 • USDC Value: ~${usdc_amount:,.2f}
 • Order Type: {'Limit' if is_limit else 'Market'} Order
 • Order Type: {'Limit' if is_limit else 'Market'} Order
 • Order ID: <code>{order_id}</code>
 • Order ID: <code>{order_id}</code>
-
-📉 Your {'limit order has been placed' if is_limit else 'short position is now active'}!
-                """
+"""
+                
+                # Add stop loss confirmation if provided
+                if stop_loss_price is not None:
+                    success_message += f"""
+🛑 <b>Stop Loss Saved:</b>
+• Stop Price: ${stop_loss_price:,.2f}
+• Will be placed automatically when order fills
+• Status: PENDING ⏳
+"""
+                
+                success_message += f"\n📉 Your {'limit order has been placed' if is_limit else 'short position is now active'}!"
                 
                 
                 await query.edit_message_text(success_message, parse_mode='HTML')
                 await query.edit_message_text(success_message, parse_mode='HTML')
                 logger.info(f"Short {'limit order placed' if is_limit else 'position opened'}: {token_amount:.6f} {token} @ ${price} ({'Limit' if is_limit else 'Market'})")
                 logger.info(f"Short {'limit order placed' if is_limit else 'position opened'}: {token_amount:.6f} {token} @ ${price} ({'Limit' if is_limit else 'Market'})")
@@ -1288,20 +1365,41 @@ Tap any button below for instant access to bot functions:
         try:
         try:
             if not context.args or len(context.args) < 2:
             if not context.args or len(context.args) < 2:
                 await update.message.reply_text(
                 await update.message.reply_text(
-                    "❌ Usage: /long [token] [USDC amount] [price (optional)]\n"
+                    "❌ Usage: /long [token] [USDC amount] [price (optional)] [sl:price (optional)]\n"
                     "Examples:\n"
                     "Examples:\n"
                     "• /long BTC 100 - Market order\n"
                     "• /long BTC 100 - Market order\n"
-                    "• /long BTC 100 45000 - Limit order at $45,000"
+                    "• /long BTC 100 45000 - Limit order at $45,000\n"
+                    "• /long BTC 100 sl:44000 - Market order with stop loss at $44,000\n"
+                    "• /long BTC 100 45000 sl:44000 - Limit order at $45,000 with stop loss at $44,000"
                 )
                 )
                 return
                 return
             
             
             token = context.args[0].upper()
             token = context.args[0].upper()
             usdc_amount = float(context.args[1])
             usdc_amount = float(context.args[1])
             
             
-            # Check if price is provided for limit order
+            # Parse arguments for price and stop loss
             limit_price = None
             limit_price = None
-            if len(context.args) >= 3:
-                limit_price = float(context.args[2])
+            stop_loss_price = None
+            
+            # Parse remaining arguments
+            for i, arg in enumerate(context.args[2:], 2):
+                if arg.startswith('sl:'):
+                    # Stop loss parameter
+                    try:
+                        stop_loss_price = float(arg[3:])  # Remove 'sl:' prefix
+                    except ValueError:
+                        await update.message.reply_text("❌ Invalid stop loss price format. Use sl:price (e.g., sl:44000)")
+                        return
+                elif limit_price is None:
+                    # First non-sl parameter is the limit price
+                    try:
+                        limit_price = float(arg)
+                    except ValueError:
+                        await update.message.reply_text("❌ Invalid limit price format. Please use numbers only.")
+                        return
+            
+            # Determine order type
+            if limit_price:
                 order_type = "Limit"
                 order_type = "Limit"
                 order_description = f"at ${limit_price:,.2f}"
                 order_description = f"at ${limit_price:,.2f}"
             else:
             else:
@@ -1322,6 +1420,18 @@ Tap any button below for instant access to bot functions:
                 await update.message.reply_text(f"❌ Invalid price for {token}")
                 await update.message.reply_text(f"❌ Invalid price for {token}")
                 return
                 return
             
             
+            # Validate stop loss price for long positions
+            if stop_loss_price is not None:
+                entry_price = limit_price if limit_price else current_price
+                if stop_loss_price >= entry_price:
+                    await update.message.reply_text(
+                        f"❌ Stop loss price should be BELOW entry price for long positions\n\n"
+                        f"📊 Entry Price: ${entry_price:,.2f}\n"
+                        f"🛑 Stop Loss: ${stop_loss_price:,.2f} ❌\n\n"
+                        f"💡 Try a lower price like: /long {token} {usdc_amount} {f'{limit_price} ' if limit_price else ''}sl:{entry_price * 0.95:.0f}"
+                    )
+                    return
+            
             # Calculate token amount based on price (market or limit)
             # Calculate token amount based on price (market or limit)
             calculation_price = limit_price if limit_price else current_price
             calculation_price = limit_price if limit_price else current_price
             token_amount = usdc_amount / calculation_price
             token_amount = usdc_amount / calculation_price
@@ -1341,15 +1451,26 @@ Tap any button below for instant access to bot functions:
 🎯 <b>Execution:</b>
 🎯 <b>Execution:</b>
 • Will buy {token_amount:.6f} {token} {order_description}
 • Will buy {token_amount:.6f} {token} {order_description}
 • Est. Value: ${token_amount * calculation_price:,.2f}
 • Est. Value: ${token_amount * calculation_price:,.2f}
-
-⚠️ <b>Are you sure you want to open this long position?</b>
-            """
+"""
+            
+            # Add stop loss information if provided
+            if stop_loss_price is not None:
+                confirmation_text += f"""
+🛑 <b>Stop Loss:</b>
+• Stop Price: ${stop_loss_price:,.2f}
+• Will be placed automatically after order fills
+• Protection Level: {((calculation_price - stop_loss_price) / calculation_price * 100):.1f}% below entry
+"""
+            
+            confirmation_text += "\n⚠️ <b>Are you sure you want to open this long position?</b>"
             
             
             # Use limit_price for callback if provided, otherwise current_price
             # Use limit_price for callback if provided, otherwise current_price
             callback_price = limit_price if limit_price else current_price
             callback_price = limit_price if limit_price else current_price
             callback_data = f"confirm_long_{token}_{usdc_amount}_{callback_price}"
             callback_data = f"confirm_long_{token}_{usdc_amount}_{callback_price}"
             if limit_price:
             if limit_price:
                 callback_data += "_limit"
                 callback_data += "_limit"
+            if stop_loss_price is not None:
+                callback_data += f"_sl_{stop_loss_price}"
             
             
             keyboard = [
             keyboard = [
                 [
                 [
@@ -1375,20 +1496,41 @@ Tap any button below for instant access to bot functions:
         try:
         try:
             if not context.args or len(context.args) < 2:
             if not context.args or len(context.args) < 2:
                 await update.message.reply_text(
                 await update.message.reply_text(
-                    "❌ Usage: /short [token] [USDC amount] [price (optional)]\n"
+                    "❌ Usage: /short [token] [USDC amount] [price (optional)] [sl:price (optional)]\n"
                     "Examples:\n"
                     "Examples:\n"
                     "• /short BTC 100 - Market order\n"
                     "• /short BTC 100 - Market order\n"
-                    "• /short BTC 100 46000 - Limit order at $46,000"
+                    "• /short BTC 100 46000 - Limit order at $46,000\n"
+                    "• /short BTC 100 sl:47000 - Market order with stop loss at $47,000\n"
+                    "• /short BTC 100 46000 sl:47000 - Limit order at $46,000 with stop loss at $47,000"
                 )
                 )
                 return
                 return
             
             
             token = context.args[0].upper()
             token = context.args[0].upper()
             usdc_amount = float(context.args[1])
             usdc_amount = float(context.args[1])
             
             
-            # Check if price is provided for limit order
+            # Parse arguments for price and stop loss
             limit_price = None
             limit_price = None
-            if len(context.args) >= 3:
-                limit_price = float(context.args[2])
+            stop_loss_price = None
+            
+            # Parse remaining arguments
+            for i, arg in enumerate(context.args[2:], 2):
+                if arg.startswith('sl:'):
+                    # Stop loss parameter
+                    try:
+                        stop_loss_price = float(arg[3:])  # Remove 'sl:' prefix
+                    except ValueError:
+                        await update.message.reply_text("❌ Invalid stop loss price format. Use sl:price (e.g., sl:47000)")
+                        return
+                elif limit_price is None:
+                    # First non-sl parameter is the limit price
+                    try:
+                        limit_price = float(arg)
+                    except ValueError:
+                        await update.message.reply_text("❌ Invalid limit price format. Please use numbers only.")
+                        return
+            
+            # Determine order type
+            if limit_price:
                 order_type = "Limit"
                 order_type = "Limit"
                 order_description = f"at ${limit_price:,.2f}"
                 order_description = f"at ${limit_price:,.2f}"
             else:
             else:
@@ -1409,6 +1551,18 @@ Tap any button below for instant access to bot functions:
                 await update.message.reply_text(f"❌ Invalid price for {token}")
                 await update.message.reply_text(f"❌ Invalid price for {token}")
                 return
                 return
             
             
+            # Validate stop loss price for short positions
+            if stop_loss_price is not None:
+                entry_price = limit_price if limit_price else current_price
+                if stop_loss_price <= entry_price:
+                    await update.message.reply_text(
+                        f"❌ Stop loss price should be ABOVE entry price for short positions\n\n"
+                        f"📊 Entry Price: ${entry_price:,.2f}\n"
+                        f"🛑 Stop Loss: ${stop_loss_price:,.2f} ❌\n\n"
+                        f"💡 Try a higher price like: /short {token} {usdc_amount} {f'{limit_price} ' if limit_price else ''}sl:{entry_price * 1.05:.0f}"
+                    )
+                    return
+            
             # Calculate token amount based on price (market or limit)
             # Calculate token amount based on price (market or limit)
             calculation_price = limit_price if limit_price else current_price
             calculation_price = limit_price if limit_price else current_price
             token_amount = usdc_amount / calculation_price
             token_amount = usdc_amount / calculation_price
@@ -1428,15 +1582,26 @@ Tap any button below for instant access to bot functions:
 🎯 <b>Execution:</b>
 🎯 <b>Execution:</b>
 • Will sell {token_amount:.6f} {token} {order_description}
 • Will sell {token_amount:.6f} {token} {order_description}
 • Est. Value: ${token_amount * calculation_price:,.2f}
 • Est. Value: ${token_amount * calculation_price:,.2f}
-
-⚠️ <b>Are you sure you want to open this short position?</b>
-            """
+"""
+            
+            # Add stop loss information if provided
+            if stop_loss_price is not None:
+                confirmation_text += f"""
+🛑 <b>Stop Loss:</b>
+• Stop Price: ${stop_loss_price:,.2f}
+• Will be placed automatically after order fills
+• Protection Level: {((stop_loss_price - calculation_price) / calculation_price * 100):.1f}% above entry
+"""
+            
+            confirmation_text += "\n⚠️ <b>Are you sure you want to open this short position?</b>"
             
             
             # Use limit_price for callback if provided, otherwise current_price
             # Use limit_price for callback if provided, otherwise current_price
             callback_price = limit_price if limit_price else current_price
             callback_price = limit_price if limit_price else current_price
             callback_data = f"confirm_short_{token}_{usdc_amount}_{callback_price}"
             callback_data = f"confirm_short_{token}_{usdc_amount}_{callback_price}"
             if limit_price:
             if limit_price:
                 callback_data += "_limit"
                 callback_data += "_limit"
+            if stop_loss_price is not None:
+                callback_data += f"_sl_{stop_loss_price}"
             
             
             keyboard = [
             keyboard = [
                 [
                 [
@@ -1967,6 +2132,9 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
             if filled_order_ids:
             if filled_order_ids:
                 logger.info(f"🎯 Detected {len(filled_order_ids)} filled orders")
                 logger.info(f"🎯 Detected {len(filled_order_ids)} filled orders")
                 await self._process_filled_orders(filled_order_ids, current_positions)
                 await self._process_filled_orders(filled_order_ids, current_positions)
+                
+                # Process pending stop losses for filled orders
+                await self._process_pending_stop_losses(filled_order_ids)
             
             
             # Update tracking data
             # Update tracking data
             self.last_known_orders = current_order_ids
             self.last_known_orders = current_order_ids
@@ -1985,9 +2153,190 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
             # Check deposits/withdrawals (hourly)
             # Check deposits/withdrawals (hourly)
             await self._check_deposits_withdrawals()
             await self._check_deposits_withdrawals()
             
             
+            # Clean up cancelled orders from pending stop losses
+            await self._cleanup_cancelled_stop_losses(current_order_ids)
+            
         except Exception as e:
         except Exception as e:
             logger.error(f"❌ Error checking order fills: {e}")
             logger.error(f"❌ Error checking order fills: {e}")
 
 
+    async def _process_pending_stop_losses(self, filled_order_ids: set):
+        """Process pending stop losses for filled orders."""
+        try:
+            for order_id in filled_order_ids:
+                if order_id in self.pending_stop_losses:
+                    stop_loss_info = self.pending_stop_losses[order_id]
+                    
+                    # Place the stop loss order
+                    await self._place_pending_stop_loss(order_id, stop_loss_info)
+                    
+                    # Remove from pending after processing
+                    del self.pending_stop_losses[order_id]
+                    logger.info(f"🗑️ Removed processed stop loss for order {order_id}")
+                    
+        except Exception as e:
+            logger.error(f"❌ Error processing pending stop losses: {e}")
+
+    async def _place_pending_stop_loss(self, original_order_id: str, stop_loss_info: Dict[str, Any]):
+        """Place a pending stop loss order."""
+        try:
+            token = stop_loss_info['token']
+            symbol = stop_loss_info['symbol']
+            stop_price = stop_loss_info['stop_price']
+            side = stop_loss_info['side']
+            amount = stop_loss_info['amount']
+            order_type = stop_loss_info['order_type']
+            
+            logger.info(f"🛑 Placing automatic stop loss: {side} {amount:.6f} {token} @ ${stop_price}")
+            
+            # Place the stop loss order as a limit order
+            order = self.client.place_limit_order(symbol, side, amount, stop_price)
+            
+            if order:
+                order_id = order.get('id', 'N/A')
+                
+                # Send notification
+                await self._send_stop_loss_placed_notification(token, order_type, stop_price, amount, order_id, original_order_id)
+                
+                logger.info(f"✅ Successfully placed automatic stop loss for {token}: Order ID {order_id}")
+            else:
+                # Send failure notification
+                await self._send_stop_loss_failed_notification(token, order_type, stop_price, amount, original_order_id)
+                logger.error(f"❌ Failed to place automatic stop loss for {token}")
+                
+        except Exception as e:
+            logger.error(f"❌ Error placing pending stop loss: {e}")
+            await self._send_stop_loss_failed_notification(
+                stop_loss_info.get('token', 'Unknown'), 
+                stop_loss_info.get('order_type', 'Unknown'), 
+                stop_loss_info.get('stop_price', 0), 
+                stop_loss_info.get('amount', 0), 
+                original_order_id, 
+                str(e)
+            )
+
+    async def _cleanup_cancelled_stop_losses(self, current_order_ids: set):
+        """Remove pending stop losses for cancelled orders."""
+        try:
+            # Find orders that are no longer active but were not filled
+            orders_to_remove = []
+            
+            for order_id, stop_loss_info in self.pending_stop_losses.items():
+                if order_id not in current_order_ids:
+                    # Order is no longer in open orders, check if it was cancelled (not filled)
+                    # We assume if it's not in current_order_ids and we haven't processed it as filled,
+                    # then it was likely cancelled
+                    orders_to_remove.append(order_id)
+            
+            # Remove cancelled orders from pending stop losses
+            for order_id in orders_to_remove:
+                stop_loss_info = self.pending_stop_losses[order_id]
+                token = stop_loss_info['token']
+                
+                # Send notification about cancelled stop loss
+                await self._send_stop_loss_cancelled_notification(token, stop_loss_info, order_id)
+                
+                # Remove from pending
+                del self.pending_stop_losses[order_id]
+                logger.info(f"🗑️ Removed pending stop loss for cancelled order {order_id}")
+                
+        except Exception as e:
+            logger.error(f"❌ Error cleaning up cancelled stop losses: {e}")
+
+    async def _send_stop_loss_placed_notification(self, token: str, order_type: str, stop_price: float, amount: float, stop_order_id: str, original_order_id: str):
+        """Send notification when stop loss is successfully placed."""
+        try:
+            position_type = order_type.upper()
+            
+            message = f"""
+🛑 <b>Stop Loss Placed Automatically</b>
+
+✅ <b>Stop Loss Active</b>
+
+📊 <b>Details:</b>
+• Token: {token}
+• Position: {position_type}
+• Stop Price: ${stop_price:,.2f}
+• Amount: {amount:.6f} {token}
+• Stop Loss Order ID: <code>{stop_order_id}</code>
+• Original Order ID: <code>{original_order_id}</code>
+
+🎯 <b>Protection:</b>
+• Status: ACTIVE ✅
+• Will execute if price reaches ${stop_price:,.2f}
+• Order Type: Limit Order
+
+💡 Your {position_type} position is now protected with automatic stop loss!
+            """
+            
+            await self.send_message(message.strip())
+            logger.info(f"📢 Sent stop loss placed notification: {token} @ ${stop_price}")
+            
+        except Exception as e:
+            logger.error(f"❌ Error sending stop loss placed notification: {e}")
+
+    async def _send_stop_loss_failed_notification(self, token: str, order_type: str, stop_price: float, amount: float, original_order_id: str, error: str = None):
+        """Send notification when stop loss placement fails."""
+        try:
+            position_type = order_type.upper()
+            
+            message = f"""
+⚠️ <b>Stop Loss Placement Failed</b>
+
+❌ <b>Automatic Stop Loss Failed</b>
+
+📊 <b>Details:</b>
+• Token: {token}
+• Position: {position_type}
+• Intended Stop Price: ${stop_price:,.2f}
+• Amount: {amount:.6f} {token}
+• Original Order ID: <code>{original_order_id}</code>
+
+🚨 <b>Action Required:</b>
+• Your position is NOT protected
+• Consider manually setting stop loss: <code>/sl {token} {stop_price:.0f}</code>
+• Monitor your position closely
+
+{f'🔧 Error: {error}' if error else ''}
+
+💡 Use /sl command to manually set stop loss protection.
+            """
+            
+            await self.send_message(message.strip())
+            logger.info(f"📢 Sent stop loss failed notification: {token}")
+            
+        except Exception as e:
+            logger.error(f"❌ Error sending stop loss failed notification: {e}")
+
+    async def _send_stop_loss_cancelled_notification(self, token: str, stop_loss_info: Dict[str, Any], order_id: str):
+        """Send notification when stop loss is cancelled due to order cancellation."""
+        try:
+            position_type = stop_loss_info['order_type'].upper()
+            stop_price = stop_loss_info['stop_price']
+            
+            message = f"""
+🚫 <b>Stop Loss Cancelled</b>
+
+📊 <b>Original Order Cancelled</b>
+
+• Token: {token}
+• Position: {position_type}
+• Cancelled Stop Price: ${stop_price:,.2f}
+• Original Order ID: <code>{order_id}</code>
+
+💡 <b>Status:</b>
+• Pending stop loss automatically cancelled
+• No position protection was placed
+• Order was cancelled before execution
+
+🔄 If you still want to trade {token}, place a new order with stop loss protection.
+            """
+            
+            await self.send_message(message.strip())
+            logger.info(f"📢 Sent stop loss cancelled notification: {token}")
+            
+        except Exception as e:
+            logger.error(f"❌ Error sending stop loss cancelled notification: {e}")
+
     async def _check_price_alarms(self):
     async def _check_price_alarms(self):
         """Check all active price alarms."""
         """Check all active price alarms."""
         try:
         try:
@@ -2504,6 +2853,7 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
 • Check Interval: {Config.BOT_HEARTBEAT_SECONDS} seconds
 • Check Interval: {Config.BOT_HEARTBEAT_SECONDS} seconds
 • Orders Tracked: {len(last_known_orders)}
 • Orders Tracked: {len(last_known_orders)}
 • Positions Tracked: {len(last_known_positions)}
 • Positions Tracked: {len(last_known_positions)}
+• Pending Stop Losses: {len(getattr(self, 'pending_stop_losses', {}))}
 
 
 💰 <b>Deposit/Withdrawal Monitoring:</b>
 💰 <b>Deposit/Withdrawal Monitoring:</b>
 • Check Interval: {deposit_withdrawal_check_interval // 3600} hour(s)
 • Check Interval: {deposit_withdrawal_check_interval // 3600} hour(s)
@@ -2527,6 +2877,7 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
 • Automatic Stop Loss: {'✅ Enabled' if Config.RISK_MANAGEMENT_ENABLED else '❌ Disabled'}
 • Automatic Stop Loss: {'✅ Enabled' if Config.RISK_MANAGEMENT_ENABLED else '❌ Disabled'}
 • Stop Loss Threshold: {Config.STOP_LOSS_PERCENTAGE}%
 • Stop Loss Threshold: {Config.STOP_LOSS_PERCENTAGE}%
 • Position Monitoring: {'✅ Active' if Config.RISK_MANAGEMENT_ENABLED else '❌ Inactive'}
 • Position Monitoring: {'✅ Active' if Config.RISK_MANAGEMENT_ENABLED else '❌ Inactive'}
+• Order-based Stop Loss: ✅ Enabled
 
 
 📈 <b>Notifications:</b>
 📈 <b>Notifications:</b>
 • 🚀 Position Opened/Increased
 • 🚀 Position Opened/Increased
@@ -2536,6 +2887,7 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
 • 🔄 External Trade Detection
 • 🔄 External Trade Detection
 • 💰 Deposit/Withdrawal Detection
 • 💰 Deposit/Withdrawal Detection
 • 🛑 Automatic Stop Loss Triggers
 • 🛑 Automatic Stop Loss Triggers
+• 🛑 Order-based Stop Loss Placement
 
 
 ⏰ <b>Last Check:</b> {datetime.now().strftime('%H:%M:%S')}
 ⏰ <b>Last Check:</b> {datetime.now().strftime('%H:%M:%S')}
 
 
@@ -2547,6 +2899,7 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
 • External trade monitoring
 • External trade monitoring
 • Deposit/withdrawal tracking
 • Deposit/withdrawal tracking
 • Auto stats synchronization
 • Auto stats synchronization
+• Order-based stop loss placement
 • Instant Telegram notifications
 • Instant Telegram notifications
         """
         """
         
         

+ 1 - 1
trading_bot.py

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