Selaa lähdekoodia

Enhance position management in PositionMonitor and RiskCleanupManager

- Added logic to handle bot orders that increase existing positions, ensuring accurate updates to position sizes and notifications for lifecycle management.
- Improved handling of position data in RiskCleanupManager by safely converting values to prevent errors with None types, enhancing reliability in position checks and calculations.
- Updated logging to provide clearer feedback on position changes and bot exit processing, improving overall traceability in the system.
Carles Sentis 1 päivä sitten
vanhempi
sitoutus
a3ee05bd27
3 muutettua tiedostoa jossa 58 lisäystä ja 17 poistoa
  1. 44 5
      src/monitoring/position_monitor.py
  2. 13 11
      src/monitoring/risk_cleanup_manager.py
  3. 1 1
      trading_bot.py

+ 44 - 5
src/monitoring/position_monitor.py

@@ -521,6 +521,47 @@ class PositionMonitor:
                                 )
                             fill_processed_this_iteration = True
 
+                    # Check if this is a bot order to increase an existing position
+                    if not fill_processed_this_iteration and exchange_order_id_from_fill:
+                        bot_order_for_fill = stats.get_order_by_exchange_id(exchange_order_id_from_fill)
+                        if bot_order_for_fill and bot_order_for_fill.get('symbol') == full_symbol:
+                            order_type = bot_order_for_fill.get('type')
+                            order_side = bot_order_for_fill.get('side')
+                            
+                            # Check if this is a limit order that should increase an existing position
+                            if order_type == 'limit':
+                                existing_lc = stats.get_trade_by_symbol_and_status(full_symbol, 'position_opened')
+                                if existing_lc:
+                                    lc_pos_side = existing_lc.get('position_side')
+                                    # Check if this is a same-side order (position increase)
+                                    if ((lc_pos_side == 'long' and order_side == 'buy' and side_from_fill == 'buy') or 
+                                        (lc_pos_side == 'short' and order_side == 'sell' and side_from_fill == 'sell')):
+                                        
+                                        # Update existing position size
+                                        current_positions = self._safe_get_positions()
+                                        if current_positions is not None:
+                                            new_size = 0
+                                            for pos in current_positions:
+                                                if pos.get('symbol') == full_symbol:
+                                                    new_size = abs(float(pos.get('contracts', 0)))
+                                                    break
+                                            
+                                            # Update lifecycle with new position size
+                                            await self._update_lifecycle_position_size(existing_lc['trade_lifecycle_id'], new_size)
+                                            
+                                            # Mark order as filled
+                                            stats.update_order_status(order_db_id=bot_order_for_fill['id'], new_status='filled', amount_filled_increment=amount_from_fill)
+                                            
+                                            # Send position increased notification
+                                            await self._send_position_change_notification(
+                                                full_symbol, side_from_fill, amount_from_fill, price_from_fill,
+                                                'position_increased', timestamp_dt, existing_lc
+                                            )
+                                            
+                                            logger.info(f"📈 Position INCREASED: {full_symbol} by {amount_from_fill} for lifecycle {existing_lc['trade_lifecycle_id'][:8]}...")
+                                            symbols_with_fills.add(token)
+                                            fill_processed_this_iteration = True
+
                     # Check if this is a known bot order (SL/TP/exit)
                     if not fill_processed_this_iteration and exchange_order_id_from_fill:
                         active_lc = None
@@ -580,11 +621,9 @@ class PositionMonitor:
                                 logger.info(f"{pnl_emoji} Lifecycle CLOSED: {lc_id} ({closure_reason_action_type}). PNL for fill: {await formatter.format_price_with_symbol(realized_pnl)}")
                                 symbols_with_fills.add(token)
                                 
-                                # Send position closed notification
-                                await self._send_position_change_notification(
-                                    full_symbol, side_from_fill, amount_from_fill, price_from_fill,
-                                    'position_closed', timestamp_dt, active_lc, realized_pnl
-                                )
+                                # For bot exits, don't send "External" notification here - let reconciliation handle it
+                                # to avoid duplicate notifications
+                                logger.info(f"ℹ️ Bot exit processed for {full_symbol}. Reconciliation will handle position closed notification.")
                                 
                                 stats.migrate_trade_to_aggregated_stats(lc_id)
                                 if bot_order_db_id_to_update:

+ 13 - 11
src/monitoring/risk_cleanup_manager.py

@@ -79,7 +79,7 @@ class RiskCleanupManager:
                     active_position_exists = False
                     cached_positions = self.market_monitor_cache.cached_positions or []
                     for pos in cached_positions:
-                        if pos.get('symbol') == symbol and abs(float(pos.get('contracts', 0))) > 1e-9: # Check for non-zero contracts
+                        if pos.get('symbol') == symbol and abs(float(pos.get('contracts') or 0)) > 1e-9: # Check for non-zero contracts
                             active_position_exists = True
                             break
                     
@@ -139,10 +139,12 @@ class RiskCleanupManager:
             for position in positions:
                 try:
                     symbol = position.get('symbol', '')
-                    contracts = float(position.get('current_position_size', 0))
-                    entry_price = float(position.get('entry_price', 0))
-                    roe_percentage = float(position.get('roe_percentage', 0))
-                    unrealized_pnl = float(position.get('unrealized_pnl', 0))
+                    
+                    # Safely convert position values, handling None values
+                    contracts = float(position.get('current_position_size') or 0)
+                    entry_price = float(position.get('entry_price') or 0)
+                    roe_percentage = float(position.get('roe_percentage') or 0)
+                    unrealized_pnl = float(position.get('unrealized_pnl') or 0)
 
                     if contracts == 0 or entry_price <= 0:
                         logger.info(f"Skipping position {symbol}: contracts={contracts}, entry_price={entry_price}")
@@ -225,7 +227,7 @@ Action: Executing emergency exit order..."""
             if current_positions:
                 for pos in current_positions:
                     symbol = pos.get('symbol')
-                    contracts = float(pos.get('contracts', 0))
+                    contracts = float(pos.get('contracts') or 0)
                     if symbol and contracts != 0:
                         position_symbols.add(symbol)
 
@@ -324,14 +326,14 @@ Time: {datetime.now(timezone.utc).strftime('%H:%M:%S')}\\n\\n
             position_map = {}
             for position in positions:
                 symbol = position.get('symbol')
-                contracts = float(position.get('contracts', 0))
+                contracts = float(position.get('contracts') or 0)
                 if symbol and contracts != 0:
                     token_map_key = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
                     position_map[token_map_key] = {
                         'symbol': symbol,
                         'contracts': contracts,
                         'side': 'long' if contracts > 0 else 'short',
-                        'entry_price': float(position.get('entryPx', 0))
+                        'entry_price': float(position.get('entryPx') or 0)
                     }
             
             newly_detected = 0
@@ -397,7 +399,7 @@ Time: {datetime.now(timezone.utc).strftime('%H:%M:%S')}\\n\\n
                 return
 
             current_positions = self.market_monitor_cache.cached_positions or []
-            position_symbols = {pos.get('symbol') for pos in current_positions if pos.get('symbol') and abs(float(pos.get('contracts', 0))) > 1e-9}
+            position_symbols = {pos.get('symbol') for pos in current_positions if pos.get('symbol') and abs(float(pos.get('contracts') or 0)) > 1e-9}
             
             current_open_orders_on_exchange = self.market_monitor_cache.cached_orders or []
             open_exchange_order_ids = {order.get('id') for order in current_open_orders_on_exchange if order.get('id')}
@@ -451,7 +453,7 @@ Time: {datetime.now(timezone.utc).strftime('%H:%M:%S')}\\n\\n
             # Get current open positions on the exchange from the cache
             active_position_symbols = {
                 pos.get('symbol') for pos in (self.market_monitor_cache.cached_positions or [])
-                if pos.get('symbol') and abs(float(pos.get('contracts', 0))) > 1e-9
+                if pos.get('symbol') and abs(float(pos.get('contracts') or 0)) > 1e-9
             }
 
             cleaned_count = 0
@@ -547,7 +549,7 @@ Time: {datetime.now(timezone.utc).strftime('%H:%M:%S')}\\n\\n
                         logger.warning("⚠️ Failed to fetch exchange positions - skipping stop loss check")
                         continue
                     position_exists = any(
-                        pos['symbol'] == symbol and abs(float(pos.get('contracts', 0))) > 1e-9 
+                        pos['symbol'] == symbol and abs(float(pos.get('contracts') or 0)) > 1e-9 
                         for pos in exchange_positions
                     )
                     

+ 1 - 1
trading_bot.py

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