Просмотр исходного кода

Add stop loss configuration testing and automatic execution - Introduce a new test script for stop loss configuration, showcasing its functionality and scenarios. Update configuration settings to reflect a 10% stop loss threshold. Enhance the Telegram bot to include automatic stop loss notifications and execution, improving risk management capabilities.

Carles Sentis 6 дней назад
Родитель
Сommit
dd18f41e2f
4 измененных файлов с 311 добавлено и 10 удалено
  1. 1 3
      config/env.example
  2. 1 5
      src/config.py
  3. 181 2
      src/telegram_bot.py
  4. 128 0
      test_stop_loss_config.py

+ 1 - 3
config/env.example

@@ -22,9 +22,7 @@ DEFAULT_TRADE_AMOUNT=0.001
 
 # Risk management settings
 RISK_MANAGEMENT_ENABLED=true
-MAX_POSITION_SIZE=100
-STOP_LOSS_PERCENTAGE=2.0
-TAKE_PROFIT_PERCENTAGE=5.0
+STOP_LOSS_PERCENTAGE=10.0
 
 # ========================================
 # Telegram Bot Configuration

+ 1 - 5
src/config.py

@@ -20,9 +20,7 @@ class Config:
     DEFAULT_TRADING_SYMBOL: str = os.getenv('DEFAULT_TRADING_SYMBOL', 'BTC/USDC:USDC')
     DEFAULT_TRADE_AMOUNT: float = float(os.getenv('DEFAULT_TRADE_AMOUNT', '0.001'))
     RISK_MANAGEMENT_ENABLED: bool = os.getenv('RISK_MANAGEMENT_ENABLED', 'true').lower() == 'true'
-    MAX_POSITION_SIZE: float = float(os.getenv('MAX_POSITION_SIZE', '100'))
-    STOP_LOSS_PERCENTAGE: float = float(os.getenv('STOP_LOSS_PERCENTAGE', '2.0'))
-    TAKE_PROFIT_PERCENTAGE: float = float(os.getenv('TAKE_PROFIT_PERCENTAGE', '5.0'))
+    STOP_LOSS_PERCENTAGE: float = float(os.getenv('STOP_LOSS_PERCENTAGE', '10.0'))
     
     # Telegram Bot Configuration
     TELEGRAM_BOT_TOKEN: Optional[str] = os.getenv('TELEGRAM_BOT_TOKEN')
@@ -119,9 +117,7 @@ class Config:
         print(f"  DEFAULT_TRADING_SYMBOL: {cls.DEFAULT_TRADING_SYMBOL}")
         print(f"  DEFAULT_TRADE_AMOUNT: {cls.DEFAULT_TRADE_AMOUNT}")
         print(f"  RISK_MANAGEMENT_ENABLED: {cls.RISK_MANAGEMENT_ENABLED}")
-        print(f"  MAX_POSITION_SIZE: {cls.MAX_POSITION_SIZE}")
         print(f"  STOP_LOSS_PERCENTAGE: {cls.STOP_LOSS_PERCENTAGE}%")
-        print(f"  TAKE_PROFIT_PERCENTAGE: {cls.TAKE_PROFIT_PERCENTAGE}%")
         print(f"  TELEGRAM_ENABLED: {cls.TELEGRAM_ENABLED}")
         print(f"  LOG_LEVEL: {cls.LOG_LEVEL}")
         print(f"  PRIVATE_KEY: {'✅ Set' if cls.HYPERLIQUID_PRIVATE_KEY else '❌ Not Set'}")

+ 181 - 2
src/telegram_bot.py

@@ -108,6 +108,11 @@ Tap the buttons below for instant access to key functions.
 • /sl BTC 44000 - Set stop loss for BTC at $44,000
 • /tp BTC 50000 - Set take profit for BTC at $50,000
 
+<b>🚨 Automatic Stop Loss:</b>
+• Enabled: {risk_enabled}
+• Stop Loss: {stop_loss}% (automatic execution)
+• Monitoring: Every {heartbeat} seconds
+
 <b>📋 Order Management:</b>
 • /orders - Show all open orders
 • /orders BTC - Show open orders for BTC only
@@ -166,7 +171,10 @@ For support, contact your bot administrator.
         """.format(
             symbol=Config.DEFAULT_TRADING_SYMBOL,
             amount=Config.DEFAULT_TRADE_AMOUNT,
-            network="Testnet" if Config.HYPERLIQUID_TESTNET else "Mainnet"
+            network="Testnet" if Config.HYPERLIQUID_TESTNET else "Mainnet",
+            risk_enabled=Config.RISK_MANAGEMENT_ENABLED,
+            stop_loss=Config.STOP_LOSS_PERCENTAGE,
+            heartbeat=Config.BOT_HEARTBEAT_SECONDS
         )
         
         keyboard = [
@@ -222,6 +230,11 @@ For support, contact your bot administrator.
 • /sl BTC 44000 - Set stop loss for BTC at $44,000
 • /tp BTC 50000 - Set take profit for BTC at $50,000
 
+<b>🚨 Automatic Stop Loss:</b>
+• Enabled: {risk_enabled}
+• Stop Loss: {stop_loss}% (automatic execution)
+• Monitoring: Every {heartbeat} seconds
+
 <b>📋 Order Management:</b>
 • /orders - Show all open orders
 • /orders BTC - Show open orders for BTC only
@@ -264,7 +277,10 @@ For support, contact your bot administrator.
         """.format(
             symbol=Config.DEFAULT_TRADING_SYMBOL,
             amount=Config.DEFAULT_TRADE_AMOUNT,
-            network="Testnet" if Config.HYPERLIQUID_TESTNET else "Mainnet"
+            network="Testnet" if Config.HYPERLIQUID_TESTNET else "Mainnet",
+            risk_enabled=Config.RISK_MANAGEMENT_ENABLED,
+            stop_loss=Config.STOP_LOSS_PERCENTAGE,
+            heartbeat=Config.BOT_HEARTBEAT_SECONDS
         )
         
         await update.message.reply_text(help_text, parse_mode='HTML')
@@ -1844,6 +1860,10 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
             # Check external trades (trades made outside the bot)
             await self._check_external_trades()
             
+            # Check stop losses (if risk management is enabled)
+            if Config.RISK_MANAGEMENT_ENABLED:
+                await self._check_stop_losses(current_positions)
+            
         except Exception as e:
             logger.error(f"❌ Error checking order fills: {e}")
 
@@ -2000,6 +2020,159 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
         except Exception as e:
             logger.error(f"❌ Error sending external trade notification: {e}")
 
+    async def _check_stop_losses(self, current_positions: list):
+        """Check all positions for stop loss triggers and execute automatic exits."""
+        try:
+            if not current_positions:
+                return
+            
+            stop_loss_triggers = []
+            
+            for position in current_positions:
+                symbol = position.get('symbol')
+                contracts = float(position.get('contracts', 0))
+                entry_price = float(position.get('entryPx', 0))
+                
+                if not symbol or contracts == 0 or entry_price == 0:
+                    continue
+                
+                # Get current market price
+                market_data = self.client.get_market_data(symbol)
+                if not market_data or not market_data.get('ticker'):
+                    continue
+                
+                current_price = float(market_data['ticker'].get('last', 0))
+                if current_price == 0:
+                    continue
+                
+                # Calculate current P&L percentage
+                if contracts > 0:  # Long position
+                    pnl_percent = ((current_price - entry_price) / entry_price) * 100
+                else:  # Short position
+                    pnl_percent = ((entry_price - current_price) / entry_price) * 100
+                
+                # Check if stop loss should trigger
+                if pnl_percent <= -Config.STOP_LOSS_PERCENTAGE:
+                    token = symbol.split('/')[0] if '/' in symbol else symbol
+                    stop_loss_triggers.append({
+                        'symbol': symbol,
+                        'token': token,
+                        'contracts': contracts,
+                        'entry_price': entry_price,
+                        'current_price': current_price,
+                        'pnl_percent': pnl_percent
+                    })
+            
+            # Execute stop losses
+            for trigger in stop_loss_triggers:
+                await self._execute_automatic_stop_loss(trigger)
+                
+        except Exception as e:
+            logger.error(f"❌ Error checking stop losses: {e}")
+
+    async def _execute_automatic_stop_loss(self, trigger: Dict[str, Any]):
+        """Execute an automatic stop loss order."""
+        try:
+            symbol = trigger['symbol']
+            token = trigger['token']
+            contracts = trigger['contracts']
+            entry_price = trigger['entry_price']
+            current_price = trigger['current_price']
+            pnl_percent = trigger['pnl_percent']
+            
+            # Determine the exit side (opposite of position)
+            exit_side = 'sell' if contracts > 0 else 'buy'
+            contracts_abs = abs(contracts)
+            
+            # Send notification before executing
+            await self._send_stop_loss_notification(trigger, "triggered")
+            
+            # Execute the stop loss order (market order for immediate execution)
+            try:
+                if exit_side == 'sell':
+                    order = self.client.create_market_sell_order(symbol, contracts_abs)
+                else:
+                    order = self.client.create_market_buy_order(symbol, contracts_abs)
+                
+                if order:
+                    logger.info(f"🛑 Stop loss executed: {token} {exit_side} {contracts_abs} @ ${current_price}")
+                    
+                    # Record the trade in stats
+                    self.stats.record_trade(symbol, exit_side, contracts_abs, current_price, order.get('id', 'stop_loss'))
+                    
+                    # Send success notification
+                    await self._send_stop_loss_notification(trigger, "executed", order)
+                else:
+                    logger.error(f"❌ Stop loss order failed for {token}")
+                    await self._send_stop_loss_notification(trigger, "failed")
+                    
+            except Exception as order_error:
+                logger.error(f"❌ Stop loss order execution failed for {token}: {order_error}")
+                await self._send_stop_loss_notification(trigger, "failed", error=str(order_error))
+                
+        except Exception as e:
+            logger.error(f"❌ Error executing automatic stop loss: {e}")
+
+    async def _send_stop_loss_notification(self, trigger: Dict[str, Any], status: str, order: Dict = None, error: str = None):
+        """Send notification for stop loss events."""
+        try:
+            token = trigger['token']
+            contracts = trigger['contracts']
+            entry_price = trigger['entry_price']
+            current_price = trigger['current_price']
+            pnl_percent = trigger['pnl_percent']
+            
+            position_type = "LONG" if contracts > 0 else "SHORT"
+            contracts_abs = abs(contracts)
+            
+            if status == "triggered":
+                title = "🛑 Stop Loss Triggered"
+                status_text = f"Stop loss triggered at {Config.STOP_LOSS_PERCENTAGE}% loss"
+                emoji = "🚨"
+            elif status == "executed":
+                title = "✅ Stop Loss Executed"
+                status_text = "Position closed automatically"
+                emoji = "🛑"
+            elif status == "failed":
+                title = "❌ Stop Loss Failed"
+                status_text = f"Stop loss execution failed{': ' + error if error else ''}"
+                emoji = "⚠️"
+            else:
+                return
+            
+            # Calculate loss
+            loss_value = contracts_abs * abs(current_price - entry_price)
+            
+            message = f"""
+{title}
+
+{emoji} <b>Risk Management Alert</b>
+
+📊 <b>Position Details:</b>
+• Token: {token}
+• Direction: {position_type}
+• Size: {contracts_abs} contracts
+• Entry Price: ${entry_price:,.2f}
+• Current Price: ${current_price:,.2f}
+
+🔴 <b>Loss Details:</b>
+• Loss: ${loss_value:,.2f} ({pnl_percent:.2f}%)
+• Stop Loss Threshold: {Config.STOP_LOSS_PERCENTAGE}%
+
+📋 <b>Action:</b> {status_text}
+⏰ <b>Time:</b> {datetime.now().strftime('%H:%M:%S')}
+            """
+            
+            if order and status == "executed":
+                order_id = order.get('id', 'N/A')
+                message += f"\n🆔 <b>Order ID:</b> {order_id}"
+            
+            await self.send_message(message.strip())
+            logger.info(f"📢 Sent stop loss notification: {token} {status}")
+            
+        except Exception as e:
+            logger.error(f"❌ Error sending stop loss notification: {e}")
+
     async def _process_filled_orders(self, filled_order_ids: set, current_positions: list):
         """Process filled orders and determine if they opened or closed positions."""
         try:
@@ -2219,12 +2392,18 @@ This will place a limit {exit_side} order at ${profit_price:,.2f} to capture pro
 • Auto Stats Update: ✅ Enabled
 • External Notifications: ✅ Enabled
 
+🛡️ <b>Risk Management:</b>
+• 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'}
+
 📈 <b>Notifications:</b>
 • 🚀 Position Opened/Increased
 • 📉 Position Partially/Fully Closed
 • 🎯 P&L Calculations
 • 🔔 Price Alarm Triggers
 • 🔄 External Trade Detection
+• 🛑 Automatic Stop Loss Triggers
 
 ⏰ <b>Last Check:</b> {datetime.now().strftime('%H:%M:%S')}
 

+ 128 - 0
test_stop_loss_config.py

@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+"""
+Test script to demonstrate the stop loss configuration and functionality.
+This script shows how the automatic stop loss system would work.
+"""
+
+import sys
+import os
+sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
+
+from config import Config
+
+def test_stop_loss_config():
+    """Test and display stop loss configuration."""
+    print("🛡️ Stop Loss Configuration Test")
+    print("=" * 50)
+    
+    # Display current configuration
+    print(f"📊 Risk Management Enabled: {Config.RISK_MANAGEMENT_ENABLED}")
+    print(f"🛑 Stop Loss Percentage: {Config.STOP_LOSS_PERCENTAGE}%")
+    print(f"⏰ Monitoring Interval: {Config.BOT_HEARTBEAT_SECONDS} seconds")
+    print()
+    
+    # Simulate position scenarios
+    scenarios = [
+        {
+            'name': 'Long BTC Position',
+            'position_type': 'long',
+            'entry_price': 45000,
+            'current_prices': [44000, 42000, 40500, 39000],
+            'position_size': 0.1
+        },
+        {
+            'name': 'Short ETH Position', 
+            'position_type': 'short',
+            'entry_price': 3000,
+            'current_prices': [3100, 3200, 3300, 3400],
+            'position_size': 2.0
+        }
+    ]
+    
+    for scenario in scenarios:
+        print(f"📋 Scenario: {scenario['name']}")
+        print(f"   Direction: {scenario['position_type'].upper()}")
+        print(f"   Entry Price: ${scenario['entry_price']:,.2f}")
+        print(f"   Position Size: {scenario['position_size']}")
+        print()
+        
+        for current_price in scenario['current_prices']:
+            # Calculate P&L percentage
+            if scenario['position_type'] == 'long':
+                pnl_percent = ((current_price - scenario['entry_price']) / scenario['entry_price']) * 100
+            else:  # short
+                pnl_percent = ((scenario['entry_price'] - current_price) / scenario['entry_price']) * 100
+            
+            # Check if stop loss would trigger
+            would_trigger = pnl_percent <= -Config.STOP_LOSS_PERCENTAGE
+            
+            # Calculate loss value
+            loss_value = scenario['position_size'] * abs(current_price - scenario['entry_price'])
+            
+            status = "🛑 STOP LOSS TRIGGERED!" if would_trigger else "✅ Safe"
+            
+            print(f"   Current Price: ${current_price:,.2f} | P&L: {pnl_percent:+.2f}% | Loss: ${loss_value:,.2f} | {status}")
+        
+        print()
+
+def test_stop_loss_thresholds():
+    """Test different stop loss threshold scenarios."""
+    print("🔧 Stop Loss Threshold Testing")
+    print("=" * 50)
+    
+    thresholds = [5.0, 10.0, 15.0, 20.0]
+    entry_price = 50000  # BTC example
+    
+    print(f"Entry Price: ${entry_price:,.2f}")
+    print()
+    
+    for threshold in thresholds:
+        # Calculate trigger prices
+        long_trigger_price = entry_price * (1 - threshold/100)
+        short_trigger_price = entry_price * (1 + threshold/100)
+        
+        print(f"Stop Loss Threshold: {threshold}%")
+        print(f"   Long Position Trigger: ${long_trigger_price:,.2f} (loss of ${entry_price - long_trigger_price:,.2f})")
+        print(f"   Short Position Trigger: ${short_trigger_price:,.2f} (loss of ${short_trigger_price - entry_price:,.2f})")
+        print()
+
+def test_monitoring_frequency():
+    """Test monitoring frequency scenarios."""
+    print("⏰ Monitoring Frequency Analysis")
+    print("=" * 50)
+    
+    frequencies = [10, 30, 60, 120, 300]  # seconds
+    
+    print("Different monitoring intervals and their implications:")
+    print()
+    
+    for freq in frequencies:
+        checks_per_minute = 60 / freq
+        checks_per_hour = 3600 / freq
+        
+        print(f"Interval: {freq} seconds")
+        print(f"   Checks per minute: {checks_per_minute:.1f}")
+        print(f"   Checks per hour: {checks_per_hour:.0f}")
+        print(f"   Responsiveness: {'High' if freq <= 30 else 'Medium' if freq <= 120 else 'Low'}")
+        print(f"   API Usage: {'High' if freq <= 10 else 'Medium' if freq <= 60 else 'Low'}")
+        print()
+
+if __name__ == "__main__":
+    print("🚀 Stop Loss System Configuration Test")
+    print("=" * 60)
+    print()
+    
+    # Test current configuration
+    test_stop_loss_config()
+    
+    print("\n" + "=" * 60)
+    test_stop_loss_thresholds()
+    
+    print("\n" + "=" * 60)
+    test_monitoring_frequency()
+    
+    print("\n" + "=" * 60)
+    print("✅ Stop Loss System Ready!")
+    print(f"📊 Current Settings: {Config.STOP_LOSS_PERCENTAGE}% stop loss, {Config.BOT_HEARTBEAT_SECONDS}s monitoring")
+    print("🛡️ Automatic position protection is enabled")
+    print("📱 You'll receive Telegram notifications for all stop loss events")