|
@@ -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
|
|
|
"""
|
|
|
|