Parcourir la source

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 il y a 5 jours
Parent
commit
ccb9a192ce
3 fichiers modifiés avec 386 ajouts et 30 suppressions
  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:
 
+> ⚠️ **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
 # 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_TESTNET=true
 
@@ -241,9 +243,10 @@ python utils/simple_chat_id.py
 - Switch to mainnet only when fully tested
 
 ### **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
-- **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**
 - ✅ **YES** - All statistics are permanently saved

+ 379 - 26
src/telegram_bot.py

@@ -54,6 +54,9 @@ class TelegramTradingBot:
         # Alarm management
         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
         self._initialize_stats()
     
@@ -114,13 +117,17 @@ class TelegramTradingBot:
 
 <b>🚀 Trading:</b>
 • /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 3500 sl:3600 - With stop loss
 • /exit BTC - Close position
 • /coo BTC - Cancel open orders
 
 <b>🛡️ Risk Management:</b>
 • Enabled: {risk_enabled}
 • Auto Stop Loss: {stop_loss}%
+• Order Stop Loss: Use sl:price parameter
 • /sl BTC 44000 - Manual stop loss
 • /tp BTC 50000 - Take profit order
 
@@ -147,6 +154,7 @@ class TelegramTradingBot:
 • Price alarm triggers
 • External trade detection & sync
 • Auto stats synchronization
+• Automatic stop loss placement
 • {heartbeat}-second monitoring interval
 
 <b>📊 Universal Trade Tracking:</b>
@@ -171,6 +179,7 @@ Type /help for detailed command information.
 • Comprehensive performance tracking
 • Real-time balance monitoring
 • Risk metrics calculation
+• Automatic stop loss protection
 
 <b>📱 Mobile Optimized:</b>
 • Quick action buttons
@@ -237,8 +246,12 @@ For support, contact your bot administrator.
 <b>🚀 Perps Trading:</b>
 • /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 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 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
 
 <b>🛡️ Risk Management:</b>
@@ -249,6 +262,7 @@ For support, contact your bot administrator.
 • Enabled: {risk_enabled}
 • Stop Loss: {stop_loss}% (automatic execution)
 • Monitoring: Every {heartbeat} seconds
+• Order-based: Use sl:price parameter for automatic placement
 
 <b>📋 Order Management:</b>
 • /orders - Show all open orders
@@ -283,6 +297,7 @@ For support, contact your bot administrator.
 • Real-time balance monitoring
 • Deposit/withdrawal tracking (hourly)
 • Risk metrics calculation
+• Automatic stop loss placement
 
 <b>📱 Mobile Optimized:</b>
 • Quick action buttons
@@ -729,7 +744,15 @@ Tap any button below for instant access to bot functions:
             usdc_amount = float(parts[3])
             price = float(parts[4])
             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
             
         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])
             price = float(parts[4])
             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
             
         elif callback_data.startswith('confirm_exit_'):
@@ -817,7 +848,7 @@ Tap any button below for instant access to bot functions:
         elif callback_data == "logs":
             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."""
         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
                 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"""
 ✅ <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}
 • Order Type: {'Limit' if is_limit else 'Market'} Order
 • 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')
                 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)
             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."""
         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
                 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"""
 ✅ <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}
 • Order Type: {'Limit' if is_limit else 'Market'} Order
 • 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')
                 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:
             if not context.args or len(context.args) < 2:
                 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"
                     "• /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
             
             token = context.args[0].upper()
             usdc_amount = float(context.args[1])
             
-            # Check if price is provided for limit order
+            # Parse arguments for price and stop loss
             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_description = f"at ${limit_price:,.2f}"
             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}")
                 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)
             calculation_price = limit_price if limit_price else current_price
             token_amount = usdc_amount / calculation_price
@@ -1341,15 +1451,26 @@ Tap any button below for instant access to bot functions:
 🎯 <b>Execution:</b>
 • Will buy {token_amount:.6f} {token} {order_description}
 • 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
             callback_price = limit_price if limit_price else current_price
             callback_data = f"confirm_long_{token}_{usdc_amount}_{callback_price}"
             if limit_price:
                 callback_data += "_limit"
+            if stop_loss_price is not None:
+                callback_data += f"_sl_{stop_loss_price}"
             
             keyboard = [
                 [
@@ -1375,20 +1496,41 @@ Tap any button below for instant access to bot functions:
         try:
             if not context.args or len(context.args) < 2:
                 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"
                     "• /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
             
             token = context.args[0].upper()
             usdc_amount = float(context.args[1])
             
-            # Check if price is provided for limit order
+            # Parse arguments for price and stop loss
             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_description = f"at ${limit_price:,.2f}"
             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}")
                 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)
             calculation_price = limit_price if limit_price else current_price
             token_amount = usdc_amount / calculation_price
@@ -1428,15 +1582,26 @@ Tap any button below for instant access to bot functions:
 🎯 <b>Execution:</b>
 • Will sell {token_amount:.6f} {token} {order_description}
 • 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
             callback_price = limit_price if limit_price else current_price
             callback_data = f"confirm_short_{token}_{usdc_amount}_{callback_price}"
             if limit_price:
                 callback_data += "_limit"
+            if stop_loss_price is not None:
+                callback_data += f"_sl_{stop_loss_price}"
             
             keyboard = [
                 [
@@ -1967,6 +2132,9 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
             if filled_order_ids:
                 logger.info(f"🎯 Detected {len(filled_order_ids)} filled orders")
                 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
             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)
             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:
             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):
         """Check all active price alarms."""
         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
 • Orders Tracked: {len(last_known_orders)}
 • Positions Tracked: {len(last_known_positions)}
+• Pending Stop Losses: {len(getattr(self, 'pending_stop_losses', {}))}
 
 💰 <b>Deposit/Withdrawal Monitoring:</b>
 • 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'}
 • Stop Loss Threshold: {Config.STOP_LOSS_PERCENTAGE}%
 • Position Monitoring: {'✅ Active' if Config.RISK_MANAGEMENT_ENABLED else '❌ Inactive'}
+• Order-based Stop Loss: ✅ Enabled
 
 📈 <b>Notifications:</b>
 • 🚀 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
 • 💰 Deposit/Withdrawal Detection
 • 🛑 Automatic Stop Loss Triggers
+• 🛑 Order-based Stop Loss Placement
 
 ⏰ <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
 • Deposit/withdrawal tracking
 • Auto stats synchronization
+• Order-based stop loss placement
 • Instant Telegram notifications
         """
         

+ 1 - 1
trading_bot.py

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