Przeglądaj źródła

Enhance order management and stop loss functionality - Updated InfoCommands to display pending stop losses linked to orders, improving user visibility. Refactored TradingCommands to ensure accurate stop loss notifications and confirmations. Transitioned MarketMonitor to utilize SQLite for state management, replacing file-based persistence, and added comprehensive logging for order status updates. Improved TradingEngine to handle order placements and cancellations more robustly, including linked stop loss management, ensuring better tracking and execution of trades.

Carles Sentis 3 dni temu
rodzic
commit
5ae857d2d6

+ 20 - 1
src/commands/info_commands.py

@@ -209,7 +209,26 @@ class InfoCommands:
                         side_emoji = "🟢" if side == "BUY" else "🔴"
                         
                         orders_text += f"   {side_emoji} {side} {amount:.6f} @ ${price:,.2f}\n"
-                        orders_text += f"   📋 Type: {order_type} | ID: {order_id}\n\n"
+                        orders_text += f"   📋 Type: {order_type} | ID: {order_id}\n"
+                        
+                        # Check for pending stop losses linked to this order
+                        stats = self.trading_engine.get_stats()
+                        if stats:
+                            # Try to find this order in our database to get its bot_order_ref_id
+                            order_in_db = stats.get_order_by_exchange_id(order_id)
+                            if order_in_db:
+                                bot_ref_id = order_in_db.get('bot_order_ref_id')
+                                if bot_ref_id:
+                                    # Look for pending stop losses with this order as parent
+                                    pending_sls = stats.get_orders_by_status('pending_trigger', 'stop_limit_trigger')
+                                    linked_sls = [sl for sl in pending_sls if sl.get('parent_bot_order_ref_id') == bot_ref_id]
+                                    
+                                    if linked_sls:
+                                        sl_order = linked_sls[0]  # Should only be one
+                                        sl_price = sl_order.get('price', 0)
+                                        orders_text += f"   🛑 Pending SL: ${sl_price:,.2f} (activates when filled)\n"
+                        
+                        orders_text += "\n"
                 
                 orders_text += f"💼 <b>Total Orders:</b> {len(orders)}\n"
                 orders_text += f"💡 Use /coo [token] to cancel orders"

+ 34 - 17
src/commands/trading_commands.py

@@ -112,14 +112,20 @@ class TradingCommands:
 • Order Type: {order_type}
 • Price: ${price:,.2f}
 • Current Price: ${current_price:,.2f}
-• Est. Value: ${token_amount * price:,.2f}
-
-{f"🛑 Stop Loss: ${stop_loss_price:,.2f}" if stop_loss_price else ""}
+• Est. Value: ${token_amount * price:,.2f}"""
+            
+            if stop_loss_price:
+                confirmation_text += f"""
+• 🛑 Stop Loss: ${stop_loss_price:,.2f}"""
+            
+            confirmation_text += f"""
 
 ⚠️ <b>Are you sure you want to open this LONG position?</b>
 
-This will {"place a limit buy order" if limit_price else "execute a market buy order"} for {token}.
-            """
+This will {"place a limit buy order" if limit_price else "execute a market buy order"} for {token}."""
+            
+            if stop_loss_price:
+                confirmation_text += f"\nStop loss will be set automatically when order fills."
             
             # Create callback data for confirmation
             callback_data = f"confirm_long_{token}_{usdc_amount}_{price if limit_price else 'market'}"
@@ -225,14 +231,20 @@ This will {"place a limit buy order" if limit_price else "execute a market buy o
 • Order Type: {order_type}
 • Price: ${price:,.2f}
 • Current Price: ${current_price:,.2f}
-• Est. Value: ${token_amount * price:,.2f}
-
-{f"🛑 Stop Loss: ${stop_loss_price:,.2f}" if stop_loss_price else ""}
+• Est. Value: ${token_amount * price:,.2f}"""
+            
+            if stop_loss_price:
+                confirmation_text += f"""
+• 🛑 Stop Loss: ${stop_loss_price:,.2f}"""
+            
+            confirmation_text += f"""
 
 ⚠️ <b>Are you sure you want to open this SHORT position?</b>
 
-This will {"place a limit sell order" if limit_price else "execute a market sell order"} for {token}.
-            """
+This will {"place a limit sell order" if limit_price else "execute a market sell order"} for {token}."""
+            
+            if stop_loss_price:
+                confirmation_text += f"\nStop loss will be set automatically when order fills."
             
             # Create callback data for confirmation
             callback_data = f"confirm_short_{token}_{usdc_amount}_{price if limit_price else 'market'}"
@@ -737,8 +749,9 @@ This action cannot be undone.
         
         if result["success"]:
             await self.notification_manager.send_exit_success_notification(
-                query, token, result["position_type"], result["contracts"], 
-                result["actual_price"], result["pnl"], result["order"]
+                query, token, result["position_type_closed"], result["contracts_intended_to_close"], 
+                result.get("actual_price", 0), result.get("pnl", 0), 
+                {**result.get("order_placed_details", {}), "cancelled_stop_losses": result.get("cancelled_stop_losses", 0)}
             )
         else:
             await query.edit_message_text(f"❌ Exit order failed: {result['error']}")
@@ -755,8 +768,8 @@ This action cannot be undone.
         
         if result["success"]:
             await self.notification_manager.send_sl_success_notification(
-                query, token, result["position_type"], result["contracts"], 
-                stop_price, result["order"]
+                query, token, result["position_type_for_sl"], result["contracts_for_sl"], 
+                stop_price, result.get("order_placed_details", {})
             )
         else:
             await query.edit_message_text(f"❌ Stop loss failed: {result['error']}")
@@ -773,8 +786,8 @@ This action cannot be undone.
         
         if result["success"]:
             await self.notification_manager.send_tp_success_notification(
-                query, token, result["position_type"], result["contracts"], 
-                tp_price, result["order"]
+                query, token, result["position_type_for_tp"], result["contracts_for_tp"], 
+                tp_price, result.get("order_placed_details", {})
             )
         else:
             await query.edit_message_text(f"❌ Take profit failed: {result['error']}")
@@ -789,8 +802,12 @@ This action cannot be undone.
         result = await self.trading_engine.execute_coo_order(token)
         
         if result["success"]:
+            cancelled_count = len(result.get("db_updates_successful_ids", []))
+            failed_count = len(result.get("db_updates_failed_ids", []))
+            cancelled_linked_sls = result.get("cancelled_linked_stop_losses", 0)
+            
             await self.notification_manager.send_coo_success_notification(
-                query, token, result["cancelled_count"], result["failed_count"]
+                query, token, cancelled_count, failed_count, cancelled_linked_sls
             )
         else:
             await query.edit_message_text(f"❌ Cancel orders failed: {result['error']}") 

+ 645 - 46
src/monitoring/market_monitor.py

@@ -25,7 +25,7 @@ class MarketMonitor:
         self._monitor_task = None
         
         # External trade monitoring
-        self.state_file = "data/market_monitor_state.json"
+        # self.state_file = "data/market_monitor_state.json" # Removed, state now in DB
         self.last_processed_trade_time: Optional[datetime] = None
         
         # Alarm management
@@ -76,42 +76,55 @@ class MarketMonitor:
         logger.info("🛑 Market monitor stopped")
     
     def _load_state(self):
-        """Load market monitor state from disk."""
+        """Load market monitor state from SQLite DB via TradingStats."""
+        stats = self.trading_engine.get_stats()
+        if not stats:
+            logger.warning("⚠️ TradingStats not available, cannot load MarketMonitor state.")
+            self.last_processed_trade_time = None
+            return
+
         try:
-            if os.path.exists(self.state_file):
-                with open(self.state_file, 'r') as f:
-                    state_data = json.load(f)
-                
-                last_time_str = state_data.get('last_processed_trade_time')
-                if last_time_str:
-                    self.last_processed_trade_time = datetime.fromisoformat(last_time_str)
-                    logger.info(f"🔄 Loaded MarketMonitor state: last_processed_trade_time = {self.last_processed_trade_time.isoformat()}")
+            last_time_str = stats._get_metadata('market_monitor_last_processed_trade_time')
+            if last_time_str:
+                self.last_processed_trade_time = datetime.fromisoformat(last_time_str)
+                # Ensure it's timezone-aware (UTC)
+                if self.last_processed_trade_time.tzinfo is None:
+                    self.last_processed_trade_time = self.last_processed_trade_time.replace(tzinfo=timezone.utc)
                 else:
-                    logger.info("🔄 MarketMonitor state file found, but no last_processed_trade_time.")
+                    self.last_processed_trade_time = self.last_processed_trade_time.astimezone(timezone.utc)
+                logger.info(f"🔄 Loaded MarketMonitor state from DB: last_processed_trade_time = {self.last_processed_trade_time.isoformat()}")
             else:
-                logger.info("💨 No MarketMonitor state file found. Will start with fresh external trade tracking.")
+                logger.info("💨 No MarketMonitor state (last_processed_trade_time) found in DB. Will start with fresh external trade tracking.")
+                self.last_processed_trade_time = None
         except Exception as e:
-            logger.error(f"Error loading MarketMonitor state from {self.state_file}: {e}. Proceeding with default state.")
+            logger.error(f"Error loading MarketMonitor state from DB: {e}. Proceeding with default state.")
             self.last_processed_trade_time = None
 
     def _save_state(self):
-        """Save market monitor state to disk."""
-        try:
-            # Ensure the data directory exists
-            data_dir = os.path.dirname(self.state_file)
-            if data_dir and not os.path.exists(data_dir):
-                os.makedirs(data_dir)
-                logger.info(f"Created data directory for MarketMonitor state: {data_dir}")
+        """Save market monitor state to SQLite DB via TradingStats."""
+        stats = self.trading_engine.get_stats()
+        if not stats:
+            logger.warning("⚠️ TradingStats not available, cannot save MarketMonitor state.")
+            return
 
-            state_data = {}
+        try:
             if self.last_processed_trade_time:
-                state_data['last_processed_trade_time'] = self.last_processed_trade_time.isoformat()
-            
-            with open(self.state_file, 'w') as f:
-                json.dump(state_data, f, indent=2)
-            logger.info(f"💾 Saved MarketMonitor state to {self.state_file}")
+                # Ensure timestamp is UTC before saving
+                lptt_utc = self.last_processed_trade_time
+                if lptt_utc.tzinfo is None:
+                    lptt_utc = lptt_utc.replace(tzinfo=timezone.utc)
+                else:
+                    lptt_utc = lptt_utc.astimezone(timezone.utc)
+                
+                stats._set_metadata('market_monitor_last_processed_trade_time', lptt_utc.isoformat())
+                logger.info(f"💾 Saved MarketMonitor state (last_processed_trade_time) to DB: {lptt_utc.isoformat()}")
+            else:
+                # If it's None, we might want to remove the key or save it as an empty string
+                # For now, let's assume we only save if there is a time. Or remove it.
+                stats._set_metadata('market_monitor_last_processed_trade_time', '') # Or handle deletion
+                logger.info("💾 MarketMonitor state (last_processed_trade_time) is None, saved as empty in DB.")
         except Exception as e:
-            logger.error(f"Error saving MarketMonitor state to {self.state_file}: {e}")
+            logger.error(f"Error saving MarketMonitor state to DB: {e}")
     
     async def _initialize_tracking(self):
         """Initialize order and position tracking."""
@@ -143,10 +156,20 @@ class MarketMonitor:
     async def _monitor_loop(self):
         """Main monitoring loop that runs every BOT_HEARTBEAT_SECONDS."""
         try:
+            loop_count = 0
             while self.is_running:
                 await self._check_order_fills()
                 await self._check_price_alarms()
                 await self._check_external_trades()
+                await self._check_pending_triggers()
+                await self._check_automatic_risk_management()
+                
+                # Run orphaned stop loss cleanup every 10 heartbeats (less frequent but regular)
+                loop_count += 1
+                if loop_count % 10 == 0:
+                    await self._cleanup_orphaned_stop_losses()
+                    loop_count = 0  # Reset counter to prevent overflow
+                
                 await asyncio.sleep(Config.BOT_HEARTBEAT_SECONDS)
         except asyncio.CancelledError:
             logger.info("Market monitor loop cancelled")
@@ -169,15 +192,17 @@ class MarketMonitor:
             current_order_ids = {order.get('id') for order in current_orders if order.get('id')}
             
             # Find filled orders (orders that were in last_known_orders but not in current_orders)
-            filled_order_ids = self.last_known_orders - current_order_ids
+            disappeared_order_ids = self.last_known_orders - current_order_ids
             
-            if filled_order_ids:
-                logger.info(f"🎯 Detected {len(filled_order_ids)} filled orders: {list(filled_order_ids)}")
-                await self._process_filled_orders(filled_order_ids, current_positions)
+            if disappeared_order_ids:
+                logger.info(f"🎯 Detected {len(disappeared_order_ids)} bot orders no longer open: {list(disappeared_order_ids)}. Corresponding fills (if any) are processed by external trade checker.")
+                await self._process_disappeared_orders(disappeared_order_ids)
             
-            # Update tracking data
+            # Update tracking data for open bot orders
             self.last_known_orders = current_order_ids
-            await self._update_position_tracking(current_positions)
+            # Position state is primarily managed by TradingStats based on all fills.
+            # This local tracking can provide supplementary logging if needed.
+            # await self._update_position_tracking(current_positions) 
             
         except Exception as e:
             logger.error(f"❌ Error checking order fills: {e}")
@@ -219,16 +244,16 @@ class MarketMonitor:
                 
                 if not old_data:
                     # New position opened
-                    logger.info(f"📈 New position detected: {symbol} {new_data['contracts']} @ ${new_data['entry_price']}")
+                    logger.info(f"📈 New position detected (observed by MarketMonitor): {symbol} {new_data['contracts']} @ ${new_data['entry_price']}. TradingStats is the definitive source.")
                 elif abs(new_data['contracts'] - old_data['contracts']) > 0.000001:
                     # Position size changed
                     change = new_data['contracts'] - old_data['contracts']
-                    logger.info(f"📊 Position change detected: {symbol} {change:+.6f} contracts")
+                    logger.info(f"📊 Position change detected (observed by MarketMonitor): {symbol} {change:+.6f} contracts. TradingStats is the definitive source.")
             
             # Check for closed positions
             for symbol in self.last_known_positions:
                 if symbol not in new_position_map:
-                    logger.info(f"📉 Position closed: {symbol}")
+                    logger.info(f"📉 Position closed (observed by MarketMonitor): {symbol}. TradingStats is the definitive source.")
             
             # Update tracking
             self.last_known_positions = new_position_map
@@ -236,6 +261,100 @@ class MarketMonitor:
         except Exception as e:
             logger.error(f"❌ Error updating position tracking: {e}")
     
+    async def _process_disappeared_orders(self, disappeared_order_ids: set):
+        """Log and investigate bot orders that have disappeared from the exchange."""
+        stats = self.trading_engine.get_stats()
+        if not stats:
+            logger.warning("⚠️ TradingStats not available in _process_disappeared_orders.")
+            return
+
+        try:
+            total_linked_cancelled = 0
+            external_cancellations = []
+            
+            for exchange_oid in disappeared_order_ids:
+                order_in_db = stats.get_order_by_exchange_id(exchange_oid)
+                
+                if order_in_db:
+                    last_status = order_in_db.get('status', 'unknown')
+                    order_type = order_in_db.get('type', 'unknown')
+                    symbol = order_in_db.get('symbol', 'unknown')
+                    token = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
+                    
+                    logger.info(f"Order {exchange_oid} was in our DB with status '{last_status}' but has now disappeared from exchange.")
+                    
+                    # Check if this was an unexpected disappearance (likely external cancellation)
+                    active_statuses = ['open', 'submitted', 'partially_filled', 'pending_submission']
+                    if last_status in active_statuses:
+                        logger.warning(f"⚠️ EXTERNAL CANCELLATION: Order {exchange_oid} with status '{last_status}' was likely cancelled externally on Hyperliquid")
+                        stats.update_order_status(exchange_order_id=exchange_oid, new_status='cancelled_externally')
+                        
+                        # Track external cancellations for notification
+                        external_cancellations.append({
+                            'exchange_oid': exchange_oid,
+                            'token': token,
+                            'type': order_type,
+                            'last_status': last_status
+                        })
+                        
+                        # Send notification about external cancellation
+                        if self.notification_manager:
+                            await self.notification_manager.send_generic_notification(
+                                f"⚠️ <b>External Order Cancellation Detected</b>\n\n"
+                                f"Token: {token}\n"
+                                f"Order Type: {order_type.replace('_', ' ').title()}\n"
+                                f"Exchange Order ID: <code>{exchange_oid[:8]}...</code>\n"
+                                f"Previous Status: {last_status.replace('_', ' ').title()}\n"
+                                f"Source: Cancelled directly on Hyperliquid\n"
+                                f"Time: {datetime.now().strftime('%H:%M:%S')}\n\n"
+                                f"🤖 Bot status updated automatically"
+                            )
+                    else:
+                        # Normal completion/cancellation - update status
+                        stats.update_order_status(exchange_order_id=exchange_oid, new_status='disappeared_from_exchange')
+                        
+                    # Cancel any pending stop losses linked to this order
+                    if order_in_db.get('bot_order_ref_id'):
+                        cancelled_sl_count = stats.cancel_linked_orders(
+                            parent_bot_order_ref_id=order_in_db['bot_order_ref_id'],
+                            new_status='cancelled_parent_disappeared'
+                        )
+                        total_linked_cancelled += cancelled_sl_count
+                        
+                        if cancelled_sl_count > 0:
+                            logger.info(f"Cancelled {cancelled_sl_count} pending stop losses linked to disappeared order {exchange_oid}")
+                            
+                            if self.notification_manager:
+                                await self.notification_manager.send_generic_notification(
+                                    f"🛑 <b>Linked Stop Losses Cancelled</b>\n\n"
+                                    f"Token: {token}\n"
+                                    f"Cancelled: {cancelled_sl_count} stop loss(es)\n"
+                                    f"Reason: Parent order {exchange_oid[:8]}... disappeared\n"
+                                    f"Likely Cause: External cancellation on Hyperliquid\n"
+                                    f"Time: {datetime.now().strftime('%H:%M:%S')}"
+                                )
+                else:
+                    logger.warning(f"Order {exchange_oid} disappeared from exchange but was not found in our DB. This might be an order placed externally.")
+
+            # Send summary notification if multiple external cancellations occurred
+            if len(external_cancellations) > 1:
+                tokens_affected = list(set(item['token'] for item in external_cancellations))
+                
+                if self.notification_manager:
+                    await self.notification_manager.send_generic_notification(
+                        f"⚠️ <b>Multiple External Cancellations Detected</b>\n\n"
+                        f"Orders Cancelled: {len(external_cancellations)}\n"
+                        f"Tokens Affected: {', '.join(tokens_affected)}\n"
+                        f"Source: Direct cancellation on Hyperliquid\n"
+                        f"Linked Stop Losses Cancelled: {total_linked_cancelled}\n"
+                        f"Time: {datetime.now().strftime('%H:%M:%S')}\n\n"
+                        f"🤖 All bot tracking has been updated automatically\n"
+                        f"💡 Use /orders to verify current order status"
+                    )
+
+        except Exception as e:
+            logger.error(f"❌ Error processing disappeared bot orders: {e}", exc_info=True)
+    
     async def _check_price_alarms(self):
         """Check price alarms and trigger notifications."""
         try:
@@ -325,6 +444,8 @@ class MarketMonitor:
             
             for fill in recent_fills:
                 fill_time_data = fill.get('timestamp') # Renamed for clarity
+                processed_fill_datetime_utc_iso: Optional[str] = None # For storing the processed ISO timestamp
+
                 if fill_time_data:
                     fill_datetime_utc: Optional[datetime] = None
                     try:
@@ -341,8 +462,13 @@ class MarketMonitor:
                         else:
                             logger.warning(f"⚠️ Unknown timestamp format for {fill_time_data}")
                             continue
+                        
+                        if fill_datetime_utc:
+                            processed_fill_datetime_utc_iso = fill_datetime_utc.isoformat()
 
                         if fill_datetime_utc and self.last_processed_trade_time and fill_datetime_utc > self.last_processed_trade_time:
+                            # Store the ISO formatted timestamp directly in the fill dict for later use
+                            fill['iso_timestamp'] = processed_fill_datetime_utc_iso 
                             new_trades.append(fill)
                             if latest_trade_time is None or fill_datetime_utc > latest_trade_time: # Ensure latest_trade_time is updated
                                 latest_trade_time = fill_datetime_utc
@@ -391,26 +517,98 @@ class MarketMonitor:
             if not all([symbol, side, amount, price]):
                 return
             
-            # Skip bot-generated trades to prevent double processing
-            if trade_id in self.trading_engine.bot_trade_ids:
-                logger.debug(f"🤖 Skipping bot-generated trade: {trade_id}")
-                return
-            
+            # Hyperliquid fill object typically has 'roid' (Referral Order ID) or 'oid' for the actual order ID.
+            # Assuming 'oid' is the exchange order ID we might have stored.
+            exchange_order_id_from_fill = trade.get('oid') 
+
+            # Skip bot-generated trades to prevent double processing IF an order system isn't fully in place yet for them.
+            # However, with the new orders table, we WANT to process bot-generated fills to link them.
+            # The check for `trade_id in self.trading_engine.bot_trade_ids` might need re-evaluation or removal
+            # if bot_trade_ids are exchange_fill_ids and we want to link them to an order.
+            # For now, let's assume trade_id from get_recent_fills is the fill_id (Hyperliquid calls it 'tid').
+            # And exchange_order_id_from_fill is the one to link to orders table.
+
             # Record trade in stats and get action type using enhanced tracking
             stats = self.trading_engine.get_stats()
             if stats:
-                action_type = stats.record_trade_with_enhanced_tracking(symbol, side, amount, price, trade_id, "external")
+                linked_order_db_id = None
+                if exchange_order_id_from_fill:
+                    order_in_db = stats.get_order_by_exchange_id(exchange_order_id_from_fill)
+                    if order_in_db:
+                        linked_order_db_id = order_in_db['id']
+                        logger.info(f"🔗 Linking fill {trade_id} to order DB ID {linked_order_db_id} (Exchange Order ID: {exchange_order_id_from_fill})")
+                        # Update the order status and amount filled
+                        new_status_after_fill = order_in_db['status'] # Default to current
+                        current_filled = order_in_db.get('amount_filled', 0.0)
+                        requested_amount = order_in_db.get('amount_requested', 0.0)
+                        
+                        if abs((current_filled + amount) - requested_amount) < 1e-9: # Comparing floats
+                            new_status_after_fill = 'filled'
+                        elif (current_filled + amount) < requested_amount:
+                            new_status_after_fill = 'partially_filled'
+                        else: # Overfilled? Or issue with amounts. Log a warning.
+                            logger.warning(f"Order {linked_order_db_id} might be overfilled. Current: {current_filled}, Fill: {amount}, Requested: {requested_amount}")
+                            new_status_after_fill = 'filled' # Assume filled for now if it exceeds
+
+                        stats.update_order_status(order_db_id=linked_order_db_id, 
+                                                new_status=new_status_after_fill, 
+                                                amount_filled_increment=amount)
+                        
+                        # Check if this order is now fully filled and has pending stop losses to activate
+                        if new_status_after_fill == 'filled':
+                            await self._activate_pending_stop_losses(order_in_db, stats)
+                            
+                    else:
+                        logger.info(f"ℹ️ Fill {trade_id} has Exchange Order ID {exchange_order_id_from_fill}, but no matching order found in DB. Processing as unlinked fill.")
+                
+                iso_timestamp_from_trade_dict = trade.get('iso_timestamp') # Use the correct variable name
+                if not iso_timestamp_from_trade_dict:
+                    logger.error(f"❌ Critical: iso_timestamp missing in trade data for fill {trade_id}. Aborting processing for this fill.")
+                    return
+
+                # Pass the ISO formatted timestamp and potentially linked order ID to stats
+                action_type = stats.record_trade_with_enhanced_tracking(
+                    symbol, side, amount, price, trade_id, # trade_id here is the fill ID (e.g. Hyperliquid 'tid')
+                    trade_type="external", # Or determine more specific type if possible
+                    timestamp=iso_timestamp_from_trade_dict, # Use the fetched iso_timestamp
+                    linked_order_table_id_to_link=linked_order_db_id
+                )
+                
+                # Handle position closures - cancel pending stop losses if position was closed
+                token = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
+                if action_type in ['long_closed', 'short_closed', 'long_closed_and_short_opened', 'short_closed_and_long_opened']:
+                    logger.info(f"🔄 External trade resulted in position closure: {action_type} for {token}")
+                    
+                    # Cancel any pending stop losses for this symbol
+                    cancelled_sl_count = stats.cancel_pending_stop_losses_by_symbol(
+                        symbol=symbol,
+                        new_status='cancelled_external_position_close'
+                    )
+                    
+                    if cancelled_sl_count > 0:
+                        logger.info(f"🛑 Cancelled {cancelled_sl_count} pending stop losses for {symbol} due to external position closure")
+                        
+                        if self.notification_manager:
+                            await self.notification_manager.send_generic_notification(
+                                f"🛑 <b>Stop Losses Cancelled</b>\n\n"
+                                f"Symbol: {token}\n"
+                                f"Cancelled: {cancelled_sl_count} stop loss(es)\n"
+                                f"Reason: Position closed externally\n"
+                                f"Action: {action_type.replace('_', ' ').title()}\n"
+                                f"Time: {datetime.now().strftime('%H:%M:%S')}"
+                            )
                 
                 # Send enhanced notification based on action type
-                await self._send_enhanced_trade_notification(symbol, side, amount, price, action_type, timestamp)
+                # For notification, we can use the iso_timestamp_from_trade_dict or original_timestamp_for_notification based on desired format
+                await self._send_enhanced_trade_notification(symbol, side, amount, price, action_type, iso_timestamp_from_trade_dict)
                 
-                logger.info(f"📋 Processed external trade: {side} {amount} {symbol} @ ${price} ({action_type})")
+                logger.info(f"📋 Processed external trade: {side} {amount} {symbol} @ ${price} ({action_type}) using timestamp {iso_timestamp_from_trade_dict}")
             
         except Exception as e:
             logger.error(f"❌ Error processing external trade: {e}")
     
     async def _send_enhanced_trade_notification(self, symbol: str, side: str, amount: float, 
-                                                price: float, action_type: str, timestamp: str):
+                                                price: float, action_type: str, timestamp: str): # timestamp here is expected to be ISO for consistency
         """Send enhanced notification for external trades."""
         try:
             # Send through notification manager if available
@@ -423,4 +621,405 @@ class MarketMonitor:
                 logger.info(f"📢 External trade notification: {action_type} for {symbol.split('/')[0]}")
             
         except Exception as e:
-            logger.error(f"❌ Error sending external trade notification: {e}") 
+            logger.error(f"❌ Error sending external trade notification: {e}")
+    
+    async def _check_pending_triggers(self):
+        """Check and process pending conditional triggers (e.g., SL/TP)."""
+        stats = self.trading_engine.get_stats()
+        if not stats:
+            logger.warning("⚠️ TradingStats not available in _check_pending_triggers.")
+            return
+
+        try:
+            # Fetch pending SL triggers (adjust type if TP triggers are different)
+            # For now, assuming 'STOP_LIMIT_TRIGGER' is the type used for SLs that become limit orders
+            pending_sl_triggers = stats.get_orders_by_status(status='pending_trigger', order_type_filter='stop_limit_trigger')
+            
+            if not pending_sl_triggers:
+                return
+
+            logger.debug(f"Found {len(pending_sl_triggers)} pending SL triggers to check.")
+
+            for trigger_order in pending_sl_triggers:
+                symbol = trigger_order['symbol']
+                trigger_price = trigger_order['price'] # This is the stop price
+                trigger_side = trigger_order['side'] # This is the side of the SL order (e.g., sell for a long position's SL)
+                order_db_id = trigger_order['id']
+                parent_ref_id = trigger_order.get('parent_bot_order_ref_id')
+
+                if not symbol or trigger_price is None:
+                    logger.warning(f"Invalid trigger order data for DB ID {order_db_id}, skipping: {trigger_order}")
+                    continue
+
+                market_data = self.trading_engine.get_market_data(symbol)
+                if not market_data or not market_data.get('ticker'):
+                    logger.warning(f"Could not fetch market data for {symbol} to check SL trigger {order_db_id}.")
+                    continue
+                
+                current_price = float(market_data['ticker'].get('last', 0))
+                if current_price <= 0:
+                    logger.warning(f"Invalid current price ({current_price}) for {symbol} checking SL trigger {order_db_id}.")
+                    continue
+
+                trigger_hit = False
+                if trigger_side.lower() == 'sell' and current_price <= trigger_price:
+                    trigger_hit = True
+                    logger.info(f"🔴 SL TRIGGER HIT (Sell): Order DB ID {order_db_id}, Symbol {symbol}, Trigger@ ${trigger_price:.4f}, Market@ ${current_price:.4f}")
+                elif trigger_side.lower() == 'buy' and current_price >= trigger_price:
+                    trigger_hit = True
+                    logger.info(f"🟢 SL TRIGGER HIT (Buy): Order DB ID {order_db_id}, Symbol {symbol}, Trigger@ ${trigger_price:.4f}, Market@ ${current_price:.4f}")
+                
+                if trigger_hit:
+                    logger.info(f"Attempting to execute actual stop order for triggered DB ID: {order_db_id} (Parent Bot Ref: {trigger_order.get('parent_bot_order_ref_id')})")
+                    
+                    execution_result = await self.trading_engine.execute_triggered_stop_order(original_trigger_order_db_id=order_db_id)
+                    
+                    notification_message_detail = ""
+
+                    if execution_result.get("success"):
+                        new_trigger_status = 'triggered_order_placed'
+                        placed_sl_details = execution_result.get("placed_sl_order_details", {})
+                        logger.info(f"Successfully placed actual SL order from trigger {order_db_id}. New SL Order DB ID: {placed_sl_details.get('order_db_id')}, Exchange ID: {placed_sl_details.get('exchange_order_id')}")
+                        notification_message_detail = f"Actual SL order placed (New DB ID: {placed_sl_details.get('order_db_id', 'N/A')})."
+                    else:
+                        new_trigger_status = 'trigger_execution_failed'
+                        error_msg = execution_result.get("error", "Unknown error during SL execution.")
+                        logger.error(f"Failed to execute actual SL order from trigger {order_db_id}: {error_msg}")
+                        notification_message_detail = f"Failed to place actual SL order: {error_msg}"
+
+                    stats.update_order_status(order_db_id=order_db_id, new_status=new_trigger_status)
+                    
+                    if self.notification_manager:
+                        await self.notification_manager.send_generic_notification(
+                            f"🔔 Stop-Loss Update!\nSymbol: {symbol}\nSide: {trigger_side.upper()}\nTrigger Price: ${trigger_price:.4f}\nMarket Price: ${current_price:.4f}\n(Original Trigger DB ID: {order_db_id}, Parent: {parent_ref_id or 'N/A'})\nStatus: {new_trigger_status.replace('_', ' ').title()}\nDetails: {notification_message_detail}"
+                        )
+
+        except Exception as e:
+            logger.error(f"❌ Error checking pending SL triggers: {e}", exc_info=True)
+
+    async def _check_automatic_risk_management(self):
+        """Check for automatic stop loss triggers based on Config.STOP_LOSS_PERCENTAGE as safety net."""
+        try:
+            # Skip if risk management is disabled or percentage is 0
+            if not getattr(Config, 'RISK_MANAGEMENT_ENABLED', True) or Config.STOP_LOSS_PERCENTAGE <= 0:
+                return
+
+            # Get current positions
+            positions = self.trading_engine.get_positions()
+            if not positions:
+                # If no positions exist, clean up any orphaned pending stop losses
+                await self._cleanup_orphaned_stop_losses()
+                return
+
+            for position in positions:
+                try:
+                    symbol = position.get('symbol', '')
+                    contracts = float(position.get('contracts', 0))
+                    entry_price = float(position.get('entryPx', 0))
+                    mark_price = float(position.get('markPx', 0))
+                    unrealized_pnl = float(position.get('unrealizedPnl', 0))
+                    
+                    # Skip if no position or missing data
+                    if contracts == 0 or entry_price <= 0 or mark_price <= 0:
+                        continue
+
+                    # Calculate PnL percentage based on entry value
+                    entry_value = abs(contracts) * entry_price
+                    if entry_value <= 0:
+                        continue
+                        
+                    pnl_percentage = (unrealized_pnl / entry_value) * 100
+
+                    # Check if loss exceeds the safety threshold
+                    if pnl_percentage <= -Config.STOP_LOSS_PERCENTAGE:
+                        token = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
+                        position_side = "LONG" if contracts > 0 else "SHORT"
+                        
+                        logger.warning(f"🚨 AUTOMATIC STOP LOSS TRIGGERED: {token} {position_side} position has {pnl_percentage:.2f}% loss (threshold: -{Config.STOP_LOSS_PERCENTAGE}%)")
+                        
+                        # Send notification before attempting exit
+                        if self.notification_manager:
+                            await self.notification_manager.send_generic_notification(
+                                f"🚨 AUTOMATIC STOP LOSS TRIGGERED!\n"
+                                f"Token: {token}\n"
+                                f"Position: {position_side} {abs(contracts):.6f}\n"
+                                f"Entry Price: ${entry_price:.4f}\n"
+                                f"Current Price: ${mark_price:.4f}\n"
+                                f"Unrealized PnL: ${unrealized_pnl:.2f} ({pnl_percentage:.2f}%)\n"
+                                f"Safety Threshold: -{Config.STOP_LOSS_PERCENTAGE}%\n"
+                                f"Action: Executing emergency exit order..."
+                            )
+
+                        # Execute emergency exit order
+                        exit_result = await self.trading_engine.execute_exit_order(token)
+                        
+                        if exit_result.get('success'):
+                            logger.info(f"✅ Emergency exit order placed successfully for {token}. Order details: {exit_result.get('order_placed_details', {})}")
+                            
+                            # Cancel any pending stop losses for this symbol since position is now closed
+                            stats = self.trading_engine.get_stats()
+                            if stats:
+                                cancelled_sl_count = stats.cancel_pending_stop_losses_by_symbol(
+                                    symbol=symbol,
+                                    new_status='cancelled_auto_exit'
+                                )
+                                if cancelled_sl_count > 0:
+                                    logger.info(f"🛑 Cancelled {cancelled_sl_count} pending stop losses for {symbol} after automatic exit")
+                            
+                            if self.notification_manager:
+                                await self.notification_manager.send_generic_notification(
+                                    f"✅ <b>Emergency Exit Completed</b>\n\n"
+                                    f"📊 <b>Position:</b> {token} {position_side}\n"
+                                    f"📉 <b>Loss:</b> {pnl_percentage:.2f}% (${unrealized_pnl:.2f})\n"
+                                    f"⚠️ <b>Threshold:</b> -{Config.STOP_LOSS_PERCENTAGE}%\n"
+                                    f"✅ <b>Action:</b> Position automatically closed\n"
+                                    f"💰 <b>Exit Price:</b> ~${mark_price:.2f}\n"
+                                    f"🆔 <b>Order ID:</b> {exit_result.get('order_placed_details', {}).get('exchange_order_id', 'N/A')}\n"
+                                    f"{f'🛑 <b>Cleanup:</b> Cancelled {cancelled_sl_count} pending stop losses' if cancelled_sl_count > 0 else ''}\n\n"
+                                    f"🛡️ This was an automatic safety stop triggered by the risk management system."
+                                )
+                        else:
+                            error_msg = exit_result.get('error', 'Unknown error')
+                            logger.error(f"❌ Failed to execute emergency exit order for {token}: {error_msg}")
+                            
+                            if self.notification_manager:
+                                await self.notification_manager.send_generic_notification(
+                                    f"❌ <b>CRITICAL: Emergency Exit Failed!</b>\n\n"
+                                    f"📊 <b>Position:</b> {token} {position_side}\n"
+                                    f"📉 <b>Loss:</b> {pnl_percentage:.2f}%\n"
+                                    f"❌ <b>Error:</b> {error_msg}\n\n"
+                                    f"⚠️ <b>MANUAL INTERVENTION REQUIRED</b>\n"
+                                    f"Please close this position manually via /exit {token}"
+                                )
+
+                except Exception as pos_error:
+                    logger.error(f"Error processing position for automatic stop loss: {pos_error}")
+                    continue
+
+        except Exception as e:
+            logger.error(f"❌ Error in automatic risk management check: {e}", exc_info=True)
+
+    async def _cleanup_orphaned_stop_losses(self):
+        """Clean up pending stop losses that no longer have corresponding positions."""
+        try:
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                return
+
+            # Get all pending stop loss triggers
+            pending_stop_losses = stats.get_orders_by_status('pending_trigger', 'stop_limit_trigger')
+            
+            if not pending_stop_losses:
+                return
+
+            logger.debug(f"Checking {len(pending_stop_losses)} pending stop losses for orphaned orders")
+
+            # Get current positions to check against
+            current_positions = self.trading_engine.get_positions()
+            position_symbols = set()
+            
+            if current_positions:
+                for pos in current_positions:
+                    symbol = pos.get('symbol')
+                    contracts = float(pos.get('contracts', 0))
+                    if symbol and contracts != 0:
+                        position_symbols.add(symbol)
+
+            # Check each pending stop loss
+            orphaned_count = 0
+            for sl_order in pending_stop_losses:
+                symbol = sl_order.get('symbol')
+                order_db_id = sl_order.get('id')
+                
+                if symbol not in position_symbols:
+                    # This stop loss has no corresponding position - cancel it
+                    success = stats.update_order_status(
+                        order_db_id=order_db_id,
+                        new_status='cancelled_orphaned_no_position'
+                    )
+                    
+                    if success:
+                        orphaned_count += 1
+                        token = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
+                        logger.info(f"🧹 Cancelled orphaned stop loss for {token} (Order DB ID: {order_db_id}) - no position exists")
+
+            if orphaned_count > 0:
+                logger.info(f"🧹 Cleanup completed: Cancelled {orphaned_count} orphaned stop loss orders")
+                
+                if self.notification_manager:
+                    await self.notification_manager.send_generic_notification(
+                        f"🧹 <b>Cleanup Completed</b>\n\n"
+                        f"Cancelled {orphaned_count} orphaned stop loss order(s)\n"
+                        f"Reason: No corresponding positions found\n"
+                        f"Time: {datetime.now().strftime('%H:%M:%S')}\n\n"
+                        f"💡 This automatic cleanup ensures stop losses stay synchronized with actual positions."
+                    )
+
+        except Exception as e:
+            logger.error(f"❌ Error cleaning up orphaned stop losses: {e}", exc_info=True)
+
+    async def _activate_pending_stop_losses(self, order_in_db, stats):
+        """Activate pending stop losses for a filled order, checking current price for immediate execution."""
+        try:
+            # Fetch pending stop losses for this order
+            pending_stop_losses = stats.get_orders_by_status('pending_trigger', 'stop_limit_trigger', order_in_db['bot_order_ref_id'])
+            
+            if not pending_stop_losses:
+                return
+
+            symbol = order_in_db.get('symbol')
+            token = symbol.split('/')[0] if '/' in symbol and symbol else 'Unknown'
+            
+            logger.debug(f"Found {len(pending_stop_losses)} pending stop loss(es) for filled order {order_in_db.get('exchange_order_id', 'N/A')}")
+            
+            # Get current market price for the symbol
+            current_price = None
+            try:
+                market_data = self.trading_engine.get_market_data(symbol)
+                if market_data and market_data.get('ticker'):
+                    current_price = float(market_data['ticker'].get('last', 0))
+                    if current_price <= 0:
+                        current_price = None
+            except Exception as price_error:
+                logger.warning(f"Could not fetch current price for {symbol}: {price_error}")
+                current_price = None
+            
+            # Check if we still have a position for this symbol after the fill
+            if symbol:
+                # Try to get current position
+                try:
+                    position = self.trading_engine.find_position(token)
+                    
+                    if position and float(position.get('contracts', 0)) != 0:
+                        # Position exists - check each stop loss
+                        activated_count = 0
+                        immediately_executed_count = 0
+                        
+                        for sl_order in pending_stop_losses:
+                            sl_trigger_price = float(sl_order.get('price', 0))
+                            sl_side = sl_order.get('side', '').lower()  # 'sell' for long SL, 'buy' for short SL
+                            sl_db_id = sl_order.get('id')
+                            sl_amount = sl_order.get('amount_requested', 0)
+                            
+                            if not sl_trigger_price or not sl_side or not sl_db_id:
+                                logger.warning(f"Invalid stop loss data for DB ID {sl_db_id}, skipping")
+                                continue
+                            
+                            # Check if trigger condition is already met
+                            trigger_already_hit = False
+                            trigger_reason = ""
+                            
+                            if current_price and current_price > 0:
+                                if sl_side == 'sell' and current_price <= sl_trigger_price:
+                                    # LONG position stop loss - price has fallen below trigger
+                                    # Long SL = SELL order that triggers when price drops below stop price
+                                    trigger_already_hit = True
+                                    trigger_reason = f"LONG SL: Current price ${current_price:.4f} ≤ Stop price ${sl_trigger_price:.4f}"
+                                elif sl_side == 'buy' and current_price >= sl_trigger_price:
+                                    # SHORT position stop loss - price has risen above trigger  
+                                    # Short SL = BUY order that triggers when price rises above stop price
+                                    trigger_already_hit = True
+                                    trigger_reason = f"SHORT SL: Current price ${current_price:.4f} ≥ Stop price ${sl_trigger_price:.4f}"
+                            
+                            if trigger_already_hit:
+                                # Execute immediate market close instead of activating stop loss
+                                logger.warning(f"🚨 IMMEDIATE SL EXECUTION: {token} - {trigger_reason}")
+                                
+                                # Update the stop loss status to show it was immediately executed
+                                stats.update_order_status(order_db_id=sl_db_id, new_status='immediately_executed_on_activation')
+                                
+                                # Execute market order to close position
+                                try:
+                                    exit_result = await self.trading_engine.execute_exit_order(token)
+                                    
+                                    if exit_result.get('success'):
+                                        immediately_executed_count += 1
+                                        position_side = "LONG" if sl_side == 'sell' else "SHORT"
+                                        logger.info(f"✅ Immediate {position_side} SL execution successful for {token}. Market order placed: {exit_result.get('order_placed_details', {}).get('exchange_order_id', 'N/A')}")
+                                        
+                                        if self.notification_manager:
+                                            await self.notification_manager.send_generic_notification(
+                                                f"🚨 <b>Immediate Stop Loss Execution</b>\n\n"
+                                                f"Token: {token}\n"
+                                                f"Position Type: {position_side}\n"
+                                                f"SL Trigger Price: ${sl_trigger_price:.4f}\n"
+                                                f"Current Market Price: ${current_price:.4f}\n"
+                                                f"Trigger Logic: {trigger_reason}\n"
+                                                f"Action: Market close order placed immediately\n"
+                                                f"Reason: Trigger condition already met when activating\n"
+                                                f"Order ID: {exit_result.get('order_placed_details', {}).get('exchange_order_id', 'N/A')}\n"
+                                                f"Time: {datetime.now().strftime('%H:%M:%S')}\n\n"
+                                                f"⚡ This prevents waiting for a trigger that's already passed"
+                                            )
+                                    else:
+                                        logger.error(f"❌ Failed to execute immediate SL for {token}: {exit_result.get('error', 'Unknown error')}")
+                                        
+                                        if self.notification_manager:
+                                            await self.notification_manager.send_generic_notification(
+                                                f"❌ <b>Immediate SL Execution Failed</b>\n\n"
+                                                f"Token: {token}\n"
+                                                f"SL Price: ${sl_trigger_price:.4f}\n"
+                                                f"Current Price: ${current_price:.4f}\n"
+                                                f"Trigger Logic: {trigger_reason}\n"
+                                                f"Error: {exit_result.get('error', 'Unknown error')}\n\n"
+                                                f"⚠️ Manual intervention may be required"
+                                            )
+                                        
+                                        # Revert status since execution failed
+                                        stats.update_order_status(order_db_id=sl_db_id, new_status='activation_execution_failed')
+                                
+                                except Exception as exec_error:
+                                    logger.error(f"❌ Exception during immediate SL execution for {token}: {exec_error}")
+                                    stats.update_order_status(order_db_id=sl_db_id, new_status='activation_execution_error')
+                            else:
+                                # Normal activation - trigger condition not yet met
+                                activated_count += 1
+                                position_side = "LONG" if sl_side == 'sell' else "SHORT"
+                                logger.info(f"✅ Activating {position_side} stop loss for {token}: SL price ${sl_trigger_price:.4f} (Current: ${current_price:.4f if current_price else 'Unknown'})")
+                        
+                        # Send summary notification for normal activations
+                        if activated_count > 0 and self.notification_manager:
+                            await self.notification_manager.send_generic_notification(
+                                f"🛑 <b>Stop Losses Activated</b>\n\n"
+                                f"Symbol: {token}\n"
+                                f"Activated: {activated_count} stop loss(es)\n"
+                                f"Current Price: ${current_price:.4f if current_price else 'Unknown'}\n"
+                                f"Status: Monitoring for trigger conditions"
+                                f"{f'\\n\\n⚡ Additionally executed {immediately_executed_count} stop loss(es) immediately due to current market conditions' if immediately_executed_count > 0 else ''}"
+                            )
+                        elif immediately_executed_count > 0 and activated_count == 0:
+                            # All stop losses were immediately executed
+                            if self.notification_manager:
+                                await self.notification_manager.send_generic_notification(
+                                    f"⚡ <b>All Stop Losses Executed Immediately</b>\n\n"
+                                    f"Symbol: {token}\n"
+                                    f"Executed: {immediately_executed_count} stop loss(es)\n"
+                                    f"Reason: Market price already beyond trigger levels\n"
+                                    f"Current Price: ${current_price:.4f if current_price else 'Unknown'}\n\n"
+                                    f"🚀 Position(s) closed at market to prevent further losses"
+                                )
+                                
+                    else:
+                        # No position exists (might have been closed immediately) - cancel the stop losses
+                        cancelled_count = stats.cancel_linked_orders(
+                            parent_bot_order_ref_id=order_in_db['bot_order_ref_id'],
+                            new_status='cancelled_no_position'
+                        )
+                        
+                        if cancelled_count > 0:
+                            logger.info(f"❌ Cancelled {cancelled_count} pending stop losses for {symbol} - no position found")
+                            
+                            if self.notification_manager:
+                                await self.notification_manager.send_generic_notification(
+                                    f"🛑 <b>Stop Losses Cancelled</b>\n\n"
+                                    f"Symbol: {token}\n"
+                                    f"Cancelled: {cancelled_count} stop loss(es)\n"
+                                    f"Reason: No open position found"
+                                )
+                                
+                except Exception as pos_check_error:
+                    logger.warning(f"Could not check position for {symbol} during SL activation: {pos_check_error}")
+                    # In case of error, still try to activate (safer to have redundant SLs than none)
+            
+        except Exception as e:
+            logger.error(f"Error in _activate_pending_stop_losses: {e}", exc_info=True) 

+ 52 - 9
src/notifications/notification_manager.py

@@ -43,7 +43,7 @@ class NotificationManager:
 • Status: FILLED ✅
 • Time: {datetime.now().strftime('%H:%M:%S')}
 
-{f"🛑 Stop Loss: ${stop_loss_price:,.2f} (will be set automatically)" if stop_loss_price else "💡 Consider setting a stop loss with /sl {token} [price]"}
+{f"🛑 <b>Pending Stop Loss:</b> ${stop_loss_price:,.2f}\n• Status: Will activate when order fills\n• Protection: Automatic position closure" if stop_loss_price else "💡 Consider setting a stop loss with /sl {token} [price]"}
 
 📊 Use /positions to view your open positions.
         """
@@ -74,7 +74,7 @@ class NotificationManager:
 • Status: FILLED ✅
 • Time: {datetime.now().strftime('%H:%M:%S')}
 
-{f"🛑 Stop Loss: ${stop_loss_price:,.2f} (will be set automatically)" if stop_loss_price else "💡 Consider setting a stop loss with /sl {token} [price]"}
+{f"🛑 <b>Pending Stop Loss:</b> ${stop_loss_price:,.2f}\n• Status: Will activate when order fills\n• Protection: Automatic position closure" if stop_loss_price else "💡 Consider setting a stop loss with /sl {token} [price]"}
 
 📊 Use /positions to view your open positions.
         """
@@ -90,6 +90,9 @@ class NotificationManager:
         pnl_emoji = "🟢" if pnl >= 0 else "🔴"
         action = "SELL" if position_type == "LONG" else "BUY"
         
+        # Check if stop losses were cancelled
+        cancelled_sls = order.get('cancelled_stop_losses', 0)
+        
         success_message = f"""
 ✅ <b>{position_type} Position Closed Successfully!</b>
 
@@ -105,14 +108,23 @@ class NotificationManager:
 • Exit Value: ${contracts * actual_price:,.2f}
 • {pnl_emoji} Realized P&L: ${pnl:,.2f}
 • Status: FILLED ✅
-• Time: {datetime.now().strftime('%H:%M:%S')}
+• Time: {datetime.now().strftime('%H:%M:%S')}"""
+        
+        if cancelled_sls > 0:
+            success_message += f"""
+
+🛑 <b>Cleanup:</b>
+• Cancelled {cancelled_sls} pending stop loss order(s)
+• All protective orders removed"""
+        
+        success_message += f"""
 
 📊 <b>Result:</b> Position fully closed
 💡 Use /stats to view updated performance metrics.
         """
         
         await query.edit_message_text(success_message, parse_mode='HTML')
-        logger.info(f"Exit order executed: {contracts:.6f} {token} @ ${actual_price:,.2f} (P&L: ${pnl:,.2f})")
+        logger.info(f"Exit order executed: {contracts:.6f} {token} @ ${actual_price:,.2f} (P&L: ${pnl:,.2f}){f' | Cancelled {cancelled_sls} SLs' if cancelled_sls > 0 else ''}")
     
     async def send_sl_success_notification(self, query, token: str, position_type: str, 
                                          contracts: float, stop_price: float, 
@@ -181,7 +193,7 @@ class NotificationManager:
         logger.info(f"Take profit set: {token} @ ${tp_price:,.2f}")
     
     async def send_coo_success_notification(self, query, token: str, cancelled_count: int, 
-                                          failed_count: int):
+                                          failed_count: int, cancelled_linked_sls: int = 0):
         """Send notification for successful cancel all orders operation."""
         
         success_message = f"""
@@ -190,20 +202,33 @@ class NotificationManager:
 📊 <b>Results for {token}:</b>
 • Orders Cancelled: {cancelled_count}
 • Failed Cancellations: {failed_count}
-• Total Processed: {cancelled_count + failed_count}
+• Total Processed: {cancelled_count + failed_count}"""
+        
+        if cancelled_linked_sls > 0:
+            success_message += f"""
+• 🛑 Linked Stop Losses Cancelled: {cancelled_linked_sls}"""
+        
+        success_message += f"""
 
 {f"⚠️ {failed_count} orders could not be cancelled" if failed_count > 0 else "🎯 All orders successfully cancelled"}
 
 📋 <b>Summary:</b>
 • Token: {token}
 • Status: {'PARTIAL SUCCESS' if failed_count > 0 else 'SUCCESS'} ✅
-• Time: {datetime.now().strftime('%H:%M:%S')}
+• Time: {datetime.now().strftime('%H:%M:%S')}"""
+        
+        if cancelled_linked_sls > 0:
+            success_message += f"""
+
+🛑 <b>Cleanup:</b> Automatically cancelled {cancelled_linked_sls} pending stop loss(es) linked to cancelled orders"""
+        
+        success_message += f"""
 
 💡 Use /orders to verify no pending orders remain.
         """
         
         await query.edit_message_text(success_message, parse_mode='HTML')
-        logger.info(f"Cancel orders complete: {token} - {cancelled_count} cancelled, {failed_count} failed")
+        logger.info(f"Cancel orders complete: {token} - {cancelled_count} cancelled, {failed_count} failed{f', {cancelled_linked_sls} linked SLs cancelled' if cancelled_linked_sls > 0 else ''}")
     
     async def send_alarm_triggered_notification(self, token: str, target_price: float, 
                                               current_price: float, direction: str):
@@ -340,4 +365,22 @@ class NotificationManager:
                 )
                 logger.info(f"External trade notification sent: {action_type} for {token}")
         except Exception as e:
-            logger.error(f"Failed to send external trade notification: {e}") 
+            logger.error(f"Failed to send external trade notification: {e}")
+    
+    async def send_generic_notification(self, message: str):
+        """Send a generic notification message."""
+        if not self.bot_application:
+            logger.warning("Bot application not set, cannot send generic notification")
+            return
+        
+        try:
+            from src.config.config import Config
+            if Config.TELEGRAM_CHAT_ID:
+                await self.bot_application.bot.send_message(
+                    chat_id=Config.TELEGRAM_CHAT_ID,
+                    text=message,
+                    parse_mode='HTML'
+                )
+                logger.info("Generic notification sent")
+        except Exception as e:
+            logger.error(f"Failed to send generic notification: {e}") 

+ 475 - 188
src/trading/trading_engine.py

@@ -8,6 +8,7 @@ import json
 import logging
 from typing import Dict, Any, Optional, Tuple, List
 from datetime import datetime
+import uuid # For generating unique bot_order_ref_ids
 
 from src.config.config import Config
 from src.clients.hyperliquid_client import HyperliquidClient
@@ -23,47 +24,14 @@ class TradingEngine:
         self.client = HyperliquidClient()
         self.stats = None
         
-        # State persistence
-        self.state_file = "trading_engine_state.json"
+        # State persistence (Removed - state is now in DB)
+        # self.state_file = "data/trading_engine_state.json"
         
-        # Position and order tracking
-        self.bot_trade_ids = set()  # Track bot-generated trades
-        self.pending_stop_losses = {}  # Pending stop loss orders
+        # Position and order tracking (All main state moved to DB via TradingStats)
         
-        # Load state and initialize stats
-        self._load_state()
+        # Initialize stats (this will connect to/create the DB)
         self._initialize_stats()
         
-    def _load_state(self):
-        """Load trading engine state from disk."""
-        try:
-            if os.path.exists(self.state_file):
-                with open(self.state_file, 'r') as f:
-                    state_data = json.load(f)
-                
-                self.bot_trade_ids = set(state_data.get('bot_trade_ids', []))
-                self.pending_stop_losses = state_data.get('pending_stop_losses', {})
-                
-                logger.info(f"🔄 Loaded trading engine state: {len(self.bot_trade_ids)} tracked trades")
-        except Exception as e:
-            logger.error(f"Error loading trading engine state: {e}")
-            self.bot_trade_ids = set()
-            self.pending_stop_losses = {}
-    
-    def _save_state(self):
-        """Save trading engine state to disk."""
-        try:
-            state_data = {
-                'bot_trade_ids': list(self.bot_trade_ids),
-                'pending_stop_losses': self.pending_stop_losses,
-                'last_updated': datetime.now().isoformat()
-            }
-            
-            with open(self.state_file, 'w') as f:
-                json.dump(state_data, f, indent=2, default=str)
-        except Exception as e:
-            logger.error(f"Error saving trading engine state: {e}")
-    
     def _initialize_stats(self):
         """Initialize trading statistics."""
         try:
@@ -145,71 +113,111 @@ class TradingEngine:
                 if not (limit_price_arg and limit_price_arg > 0):
                     return {"success": False, "error": f"Invalid current price ({current_price}) for {token} and no valid limit price provided."}
             
+            order_type_for_stats = 'limit' if limit_price_arg is not None else 'market'
             order_placement_price: float
             token_amount: float
 
+            # Determine price and amount for order placement
             if limit_price_arg is not None:
-                # Limit order intent
                 if limit_price_arg <= 0:
                     return {"success": False, "error": "Limit price must be positive."}
                 order_placement_price = limit_price_arg
                 token_amount = usdc_amount / order_placement_price
-                logger.info(f"Placing LIMIT BUY order for {token_amount:.6f} {symbol} at ${order_placement_price:,.2f}")
-                order_data, error_msg = self.client.place_limit_order(symbol, 'buy', token_amount, order_placement_price)
-            else:
-                # Market order intent
-                if current_price <= 0: # Re-check specifically for market order if current_price was initially 0
+            else: # Market order
+                if current_price <= 0:
                     return {"success": False, "error": f"Cannot place market order for {token} due to invalid current price: {current_price}"}
-                order_placement_price = current_price 
+                order_placement_price = current_price
                 token_amount = usdc_amount / order_placement_price
-                logger.info(f"Placing MARKET BUY order for {token_amount:.6f} {symbol} (approx. price ${order_placement_price:,.2f})")
-                order_data, error_msg = self.client.place_market_order(symbol, 'buy', token_amount)
+
+            # 1. Generate bot_order_ref_id and record order placement intent
+            bot_order_ref_id = uuid.uuid4().hex
+            order_db_id = self.stats.record_order_placed(
+                symbol=symbol, side='buy', order_type=order_type_for_stats,
+                amount_requested=token_amount, price=order_placement_price if order_type_for_stats == 'limit' else None,
+                bot_order_ref_id=bot_order_ref_id, status='pending_submission'
+            )
+
+            if not order_db_id:
+                logger.error(f"Failed to record order intent in DB for {symbol} with bot_ref_id {bot_order_ref_id}")
+                return {"success": False, "error": "Failed to record order intent in database."}
+
+            # 2. Place the order with the exchange
+            if order_type_for_stats == 'limit':
+                logger.info(f"Placing LIMIT BUY order ({bot_order_ref_id}) for {token_amount:.6f} {symbol} at ${order_placement_price:,.2f}")
+                exchange_order_data, error_msg = self.client.place_limit_order(symbol, 'buy', token_amount, order_placement_price)
+            else: # Market order
+                logger.info(f"Placing MARKET BUY order ({bot_order_ref_id}) for {token_amount:.6f} {symbol} (approx. price ${order_placement_price:,.2f})")
+                exchange_order_data, error_msg = self.client.place_market_order(symbol, 'buy', token_amount)
             
             if error_msg:
-                logger.error(f"Order placement failed for {symbol}: {error_msg}")
+                logger.error(f"Order placement failed for {symbol} ({bot_order_ref_id}): {error_msg}")
+                self.stats.update_order_status(order_db_id=order_db_id, new_status='failed_submission', bot_order_ref_id=bot_order_ref_id)
                 return {"success": False, "error": f"Order placement failed: {error_msg}"}
-            if not order_data:
-                logger.error(f"Order placement call failed for {symbol}. Client returned no data and no error.")
+            if not exchange_order_data:
+                logger.error(f"Order placement call failed for {symbol} ({bot_order_ref_id}). Client returned no data and no error.")
+                self.stats.update_order_status(order_db_id=order_db_id, new_status='failed_submission_no_data', bot_order_ref_id=bot_order_ref_id)
                 return {"success": False, "error": "Order placement failed at client level (no order object or error)."}
             
-            order_id = order_data.get('id', 'N/A')
-            order_avg_fill_price = order_data.get('average') 
-
-            # For stats, use average fill price if available, otherwise the price order was placed at.
-            final_price_for_stats = order_avg_fill_price if order_avg_fill_price is not None else order_placement_price
-
-            if final_price_for_stats is None: 
-                logger.critical(f"CRITICAL: final_price_for_stats is None for order {order_id}. Order: {order_data}, Placement Price: {order_placement_price}, Avg Fill: {order_avg_fill_price}")
-                return {"success": False, "error": "Critical: Price for stats recording became None."}
+            exchange_oid = exchange_order_data.get('id')
+            
+            # 3. Update order in DB with exchange_order_id and status
+            if exchange_oid:
+                # If it's a market order that might have filled, client response might indicate status.
+                # For Hyperliquid, a successful market order usually means it's filled or being filled. 
+                # Limit orders will be 'open'.
+                # We will rely on MarketMonitor to confirm fills for market orders too via fill data.
+                new_status_after_placement = 'open' # Default for limit, or submitted for market
+                if order_type_for_stats == 'market':
+                    # Market orders might be considered 'submitted' until fill is confirmed by MarketMonitor
+                    # Or, if API indicates immediate fill, could be 'filled' - for now, let's use 'open' or 'submitted'
+                    new_status_after_placement = 'submitted' # More accurate for market until fill is seen
+                
+                self.stats.update_order_status(
+                    order_db_id=order_db_id, 
+                    exchange_order_id=exchange_oid, 
+                    new_status=new_status_after_placement,
+                    bot_order_ref_id=bot_order_ref_id
+                )
+            else:
+                logger.warning(f"No exchange_order_id received for order {order_db_id} ({bot_order_ref_id}). Status remains pending_submission or requires manual check.")
+                # Potentially update status to 'submission_no_exch_id'
 
-            logger.info(f"Order {order_id} for {symbol} processing. AvgFill: {order_avg_fill_price}, PlacementPrice: {order_placement_price}, StatsPrice: {final_price_for_stats}")
-            
-            if order_id != 'N/A':
-                self.bot_trade_ids.add(order_id)
-            
-            # Record in stats
-            action_type = self.stats.record_trade_with_enhanced_tracking(
-                symbol, 'buy', token_amount, final_price_for_stats, order_id, "bot"
-            )
-            
-            # Handle stop loss if specified
-            if stop_loss_price and order_id != 'N/A':
-                self.pending_stop_losses[order_id] = {
-                    'token': token,
-                    'stop_price': stop_loss_price,
-                    'side': 'sell', 
-                    'amount': token_amount,
-                    'order_type': 'stop_loss'
-                }
-            
-            self._save_state()
+            # DO NOT record trade here. MarketMonitor will handle fills.
+            # action_type = self.stats.record_trade_with_enhanced_tracking(...)
+            
+            if stop_loss_price and exchange_oid and exchange_oid != 'N/A':
+                # Record the pending SL order in the orders table
+                sl_bot_order_ref_id = uuid.uuid4().hex
+                sl_order_db_id = self.stats.record_order_placed(
+                    symbol=symbol,
+                    side='sell', # SL for a long is a sell
+                    order_type='STOP_LIMIT_TRIGGER', # Indicates a conditional order that will become a limit order
+                    amount_requested=token_amount,
+                    price=stop_loss_price, # This is the trigger price, and also the limit price for the SL order
+                    bot_order_ref_id=sl_bot_order_ref_id,
+                    status='pending_trigger',
+                    parent_bot_order_ref_id=bot_order_ref_id # Link to the main buy order
+                )
+                if sl_order_db_id:
+                    logger.info(f"Pending Stop Loss order recorded in DB: ID {sl_order_db_id}, BotRef {sl_bot_order_ref_id}, ParentBotRef {bot_order_ref_id}")
+                else:
+                    logger.error(f"Failed to record pending SL order in DB for parent BotRef {bot_order_ref_id}")
             
             return {
                 "success": True,
-                "order": order_data,
-                "action_type": action_type,
+                "order_placed_details": {
+                    "bot_order_ref_id": bot_order_ref_id,
+                    "exchange_order_id": exchange_oid,
+                    "order_db_id": order_db_id,
+                    "symbol": symbol,
+                    "side": "buy",
+                    "type": order_type_for_stats,
+                    "amount_requested": token_amount,
+                    "price_requested": order_placement_price if order_type_for_stats == 'limit' else None
+                },
+                # "action_type": action_type, # Removed as trade is not recorded here
                 "token_amount": token_amount,
-                "actual_price": final_price_for_stats,
+                # "actual_price": final_price_for_stats, # Removed as fill is not processed here
                 "stop_loss_pending": stop_loss_price is not None
             }
         except ZeroDivisionError as e:
@@ -237,6 +245,7 @@ class TradingEngine:
                 if not (limit_price_arg and limit_price_arg > 0):
                     return {"success": False, "error": f"Invalid current price ({current_price}) for {token} and no valid limit price provided."}
 
+            order_type_for_stats = 'limit' if limit_price_arg is not None else 'market'
             order_placement_price: float
             token_amount: float
 
@@ -245,58 +254,92 @@ class TradingEngine:
                     return {"success": False, "error": "Limit price must be positive."}
                 order_placement_price = limit_price_arg
                 token_amount = usdc_amount / order_placement_price
-                logger.info(f"Placing LIMIT SELL order for {token_amount:.6f} {symbol} at ${order_placement_price:,.2f}")
-                order_data, error_msg = self.client.place_limit_order(symbol, 'sell', token_amount, order_placement_price)
-            else:
+            else: # Market order
                 if current_price <= 0:
                      return {"success": False, "error": f"Cannot place market order for {token} due to invalid current price: {current_price}"}
                 order_placement_price = current_price
                 token_amount = usdc_amount / order_placement_price
-                logger.info(f"Placing MARKET SELL order for {token_amount:.6f} {symbol} (approx. price ${order_placement_price:,.2f})")
-                order_data, error_msg = self.client.place_market_order(symbol, 'sell', token_amount)
+
+            # 1. Generate bot_order_ref_id and record order placement intent
+            bot_order_ref_id = uuid.uuid4().hex
+            order_db_id = self.stats.record_order_placed(
+                symbol=symbol, side='sell', order_type=order_type_for_stats,
+                amount_requested=token_amount, price=order_placement_price if order_type_for_stats == 'limit' else None,
+                bot_order_ref_id=bot_order_ref_id, status='pending_submission'
+            )
+
+            if not order_db_id:
+                logger.error(f"Failed to record order intent in DB for {symbol} with bot_ref_id {bot_order_ref_id}")
+                return {"success": False, "error": "Failed to record order intent in database."}
+
+            # 2. Place the order with the exchange
+            if order_type_for_stats == 'limit':
+                logger.info(f"Placing LIMIT SELL order ({bot_order_ref_id}) for {token_amount:.6f} {symbol} at ${order_placement_price:,.2f}")
+                exchange_order_data, error_msg = self.client.place_limit_order(symbol, 'sell', token_amount, order_placement_price)
+            else: # Market order
+                logger.info(f"Placing MARKET SELL order ({bot_order_ref_id}) for {token_amount:.6f} {symbol} (approx. price ${order_placement_price:,.2f})")
+                exchange_order_data, error_msg = self.client.place_market_order(symbol, 'sell', token_amount)
             
             if error_msg:
-                logger.error(f"Order placement failed for {symbol}: {error_msg}")
+                logger.error(f"Order placement failed for {symbol} ({bot_order_ref_id}): {error_msg}")
+                self.stats.update_order_status(order_db_id=order_db_id, new_status='failed_submission', bot_order_ref_id=bot_order_ref_id)
                 return {"success": False, "error": f"Order placement failed: {error_msg}"}
-            if not order_data:
-                logger.error(f"Order placement call failed for {symbol}. Client returned no data and no error.")
+            if not exchange_order_data:
+                logger.error(f"Order placement call failed for {symbol} ({bot_order_ref_id}). Client returned no data and no error.")
+                self.stats.update_order_status(order_db_id=order_db_id, new_status='failed_submission_no_data', bot_order_ref_id=bot_order_ref_id)
                 return {"success": False, "error": "Order placement failed at client level (no order object or error)."}
 
-            order_id = order_data.get('id', 'N/A')
-            order_avg_fill_price = order_data.get('average')
+            exchange_oid = exchange_order_data.get('id')
             
-            final_price_for_stats = order_avg_fill_price if order_avg_fill_price is not None else order_placement_price
-
-            if final_price_for_stats is None:
-                logger.critical(f"CRITICAL: final_price_for_stats is None for order {order_id}. Order: {order_data}, Placement Price: {order_placement_price}, Avg Fill: {order_avg_fill_price}")
-                return {"success": False, "error": "Critical: Price for stats recording became None."}
-
-            logger.info(f"Order {order_id} for {symbol} processing. AvgFill: {order_avg_fill_price}, PlacementPrice: {order_placement_price}, StatsPrice: {final_price_for_stats}")
+            # 3. Update order in DB with exchange_order_id and status
+            if exchange_oid:
+                new_status_after_placement = 'open' # Default for limit, or submitted for market
+                if order_type_for_stats == 'market':
+                    new_status_after_placement = 'submitted' # Market orders are submitted, fills come via monitor
+                
+                self.stats.update_order_status(
+                    order_db_id=order_db_id, 
+                    exchange_order_id=exchange_oid, 
+                    new_status=new_status_after_placement,
+                    bot_order_ref_id=bot_order_ref_id
+                )
+            else:
+                logger.warning(f"No exchange_order_id received for order {order_db_id} ({bot_order_ref_id}).")
 
-            if order_id != 'N/A':
-                self.bot_trade_ids.add(order_id)
-            
-            action_type = self.stats.record_trade_with_enhanced_tracking(
-                symbol, 'sell', token_amount, final_price_for_stats, order_id, "bot"
-            )
-            
-            if stop_loss_price and order_id != 'N/A':
-                self.pending_stop_losses[order_id] = {
-                    'token': token,
-                    'stop_price': stop_loss_price,
-                    'side': 'buy', # Exit side for short
-                    'amount': token_amount,
-                    'order_type': 'stop_loss'
-                }
-            
-            self._save_state()
+            # DO NOT record trade here. MarketMonitor will handle fills.
+            # action_type = self.stats.record_trade_with_enhanced_tracking(...)
+            
+            if stop_loss_price and exchange_oid and exchange_oid != 'N/A':
+                # Record the pending SL order in the orders table
+                sl_bot_order_ref_id = uuid.uuid4().hex
+                sl_order_db_id = self.stats.record_order_placed(
+                    symbol=symbol,
+                    side='buy', # SL for a short is a buy
+                    order_type='STOP_LIMIT_TRIGGER', 
+                    amount_requested=token_amount,
+                    price=stop_loss_price, 
+                    bot_order_ref_id=sl_bot_order_ref_id,
+                    status='pending_trigger',
+                    parent_bot_order_ref_id=bot_order_ref_id # Link to the main sell order
+                )
+                if sl_order_db_id:
+                    logger.info(f"Pending Stop Loss order recorded in DB: ID {sl_order_db_id}, BotRef {sl_bot_order_ref_id}, ParentBotRef {bot_order_ref_id}")
+                else:
+                    logger.error(f"Failed to record pending SL order in DB for parent BotRef {bot_order_ref_id}")
             
             return {
                 "success": True,
-                "order": order_data,
-                "action_type": action_type,
+                "order_placed_details": {
+                    "bot_order_ref_id": bot_order_ref_id,
+                    "exchange_order_id": exchange_oid,
+                    "order_db_id": order_db_id,
+                    "symbol": symbol,
+                    "side": "sell",
+                    "type": order_type_for_stats,
+                    "amount_requested": token_amount,
+                    "price_requested": order_placement_price if order_type_for_stats == 'limit' else None
+                },
                 "token_amount": token_amount,
-                "actual_price": final_price_for_stats,
                 "stop_loss_pending": stop_loss_price is not None
             }
         except ZeroDivisionError as e:
@@ -314,44 +357,81 @@ class TradingEngine:
         
         try:
             symbol = f"{token}/USDC:USDC"
-            position_type, exit_side, contracts = self.get_position_direction(position)
-            
-            # Execute market order to close position
-            order_data, error_msg = self.client.place_market_order(symbol, exit_side, contracts)
+            position_type, exit_side, contracts_to_close = self.get_position_direction(position)
+            order_type_for_stats = 'market' # Exit orders are typically market
+
+            # 1. Generate bot_order_ref_id and record order placement intent
+            bot_order_ref_id = uuid.uuid4().hex
+            # Price for a market order is not specified at placement for stats recording, will be determined by fill.
+            order_db_id = self.stats.record_order_placed(
+                symbol=symbol, side=exit_side, order_type=order_type_for_stats,
+                amount_requested=contracts_to_close, price=None, # Market order, price determined by fill
+                bot_order_ref_id=bot_order_ref_id, status='pending_submission'
+            )
+
+            if not order_db_id:
+                logger.error(f"Failed to record exit order intent in DB for {symbol} with bot_ref_id {bot_order_ref_id}")
+                return {"success": False, "error": "Failed to record exit order intent in database."}
+
+            # 2. Execute market order to close position
+            logger.info(f"Placing MARKET {exit_side.upper()} order ({bot_order_ref_id}) to close {contracts_to_close:.6f} {symbol}")
+            exchange_order_data, error_msg = self.client.place_market_order(symbol, exit_side, contracts_to_close)
             
             if error_msg:
-                logger.error(f"Exit order execution failed for {symbol}: {error_msg}")
+                logger.error(f"Exit order execution failed for {symbol} ({bot_order_ref_id}): {error_msg}")
+                self.stats.update_order_status(order_db_id=order_db_id, new_status='failed_submission', bot_order_ref_id=bot_order_ref_id)
                 return {"success": False, "error": f"Exit order execution failed: {error_msg}"}
-            if not order_data:
-                logger.error(f"Exit order execution call failed for {symbol}. Client returned no data and no error.")
+            if not exchange_order_data:
+                logger.error(f"Exit order execution call failed for {symbol} ({bot_order_ref_id}). Client returned no data and no error.")
+                self.stats.update_order_status(order_db_id=order_db_id, new_status='failed_submission_no_data', bot_order_ref_id=bot_order_ref_id)
                 return {"success": False, "error": "Exit order execution failed (no order object or error)."}
             
-            # If we reached here, order_data is not None
-            order_id = order_data.get('id', 'N/A')
-            actual_price = order_data.get('average', 0) # Fallback to 0 if 'average' is missing
-            
-            if actual_price is None: # Explicitly check for None if 'average' can return it
-                ticker_data = self.get_market_data(symbol)
-                current_market_price = float(ticker_data['ticker'].get('last', 0.0) or 0.0) if ticker_data and ticker_data.get('ticker') else 0.0
-                actual_price = current_market_price # Use current market price as a fallback
-                logger.warning(f"Order {order_id} for {symbol} had no average fill price. Using current market price ${actual_price:.2f} for stats.")
-            
-            if order_id != 'N/A':
-                self.bot_trade_ids.add(order_id)
-            
-            action_type = self.stats.record_trade_with_enhanced_tracking(
-                symbol, exit_side, contracts, actual_price, order_id, "bot"
-            )
-            
-            self._save_state()
+            exchange_oid = exchange_order_data.get('id')
+            
+            # 3. Update order in DB with exchange_order_id and status
+            if exchange_oid:
+                # Market orders are submitted; MarketMonitor will confirm fills.
+                self.stats.update_order_status(
+                    order_db_id=order_db_id, 
+                    exchange_order_id=exchange_oid, 
+                    new_status='submitted',
+                    bot_order_ref_id=bot_order_ref_id
+                )
+            else:
+                logger.warning(f"No exchange_order_id received for exit order {order_db_id} ({bot_order_ref_id}).")
+
+            # DO NOT record trade here. MarketMonitor will handle fills.
+            # The old code below is removed:
+            # order_id = order_data.get('id', 'N/A')
+            # actual_price = order_data.get('average', 0)
+            # ... logic for actual_price fallback ...
+            # if order_id != 'N/A':
+            #     self.bot_trade_ids.add(order_id)
+            # action_type = self.stats.record_trade_with_enhanced_tracking(...)
+            
+            # Cancel any pending stop losses for this symbol since position will be closed
+            if self.stats:
+                cancelled_sl_count = self.stats.cancel_pending_stop_losses_by_symbol(
+                    symbol=symbol,
+                    new_status='cancelled_manual_exit'
+                )
+                if cancelled_sl_count > 0:
+                    logger.info(f"🛑 Cancelled {cancelled_sl_count} pending stop losses for {symbol} due to manual exit order")
             
             return {
                 "success": True,
-                "order": order_data,
-                "action_type": action_type,
-                "position_type": position_type,
-                "contracts": contracts,
-                "actual_price": actual_price
+                "order_placed_details": {
+                    "bot_order_ref_id": bot_order_ref_id,
+                    "exchange_order_id": exchange_oid,
+                    "order_db_id": order_db_id,
+                    "symbol": symbol,
+                    "side": exit_side,
+                    "type": order_type_for_stats,
+                    "amount_requested": contracts_to_close
+                },
+                "position_type_closed": position_type, # Info about the position it intends to close
+                "contracts_intended_to_close": contracts_to_close,
+                "cancelled_stop_losses": cancelled_sl_count if self.stats else 0
             }
         except Exception as e:
             logger.error(f"Error executing exit order: {e}")
@@ -374,30 +454,61 @@ class TradingEngine:
             elif position_type == "SHORT" and stop_price <= entry_price:
                 return {"success": False, "error": "Stop loss price should be above entry price for short positions"}
             
-            # Place limit order at stop loss price
-            order_data, error_msg = self.client.place_limit_order(symbol, exit_side, contracts, stop_price)
+            order_type_for_stats = 'limit' # Stop loss is a limit order at stop_price
+
+            # 1. Generate bot_order_ref_id and record order placement intent
+            bot_order_ref_id = uuid.uuid4().hex
+            order_db_id = self.stats.record_order_placed(
+                symbol=symbol, side=exit_side, order_type=order_type_for_stats,
+                amount_requested=contracts, price=stop_price, 
+                bot_order_ref_id=bot_order_ref_id, status='pending_submission'
+            )
+
+            if not order_db_id:
+                logger.error(f"Failed to record SL order intent in DB for {symbol} with bot_ref_id {bot_order_ref_id}")
+                return {"success": False, "error": "Failed to record SL order intent in database."}
+
+            # 2. Place limit order at stop loss price
+            logger.info(f"Placing STOP LOSS (LIMIT {exit_side.upper()}) order ({bot_order_ref_id}) for {contracts:.6f} {symbol} at ${stop_price:,.2f}")
+            exchange_order_data, error_msg = self.client.place_limit_order(symbol, exit_side, contracts, stop_price)
             
             if error_msg:
-                logger.error(f"Stop loss order placement failed for {symbol}: {error_msg}")
+                logger.error(f"Stop loss order placement failed for {symbol} ({bot_order_ref_id}): {error_msg}")
+                self.stats.update_order_status(order_db_id=order_db_id, new_status='failed_submission', bot_order_ref_id=bot_order_ref_id)
                 return {"success": False, "error": f"Stop loss order placement failed: {error_msg}"}
-            if not order_data:
-                logger.error(f"Stop loss order placement call failed for {symbol}. Client returned no data and no error.")
+            if not exchange_order_data:
+                logger.error(f"Stop loss order placement call failed for {symbol} ({bot_order_ref_id}). Client returned no data and no error.")
+                self.stats.update_order_status(order_db_id=order_db_id, new_status='failed_submission_no_data', bot_order_ref_id=bot_order_ref_id)
                 return {"success": False, "error": "Stop loss order placement failed (no order object or error)."}
 
-            # If we reached here, order_data is not None
-            order_id = order_data.get('id', 'N/A')
-            
-            if order_id != 'N/A':
-                self.bot_trade_ids.add(order_id)
-            
-            self._save_state()
+            exchange_oid = exchange_order_data.get('id')
+            
+            # 3. Update order in DB with exchange_order_id and status
+            if exchange_oid:
+                self.stats.update_order_status(
+                    order_db_id=order_db_id, 
+                    exchange_order_id=exchange_oid, 
+                    new_status='open', # SL/TP limit orders are 'open' until triggered/filled
+                    bot_order_ref_id=bot_order_ref_id
+                )
+            else:
+                logger.warning(f"No exchange_order_id received for SL order {order_db_id} ({bot_order_ref_id}).")
             
             return {
                 "success": True,
-                "order": order_data,
-                "position_type": position_type,
-                "contracts": contracts,
-                "stop_price": stop_price
+                "order_placed_details": {
+                    "bot_order_ref_id": bot_order_ref_id,
+                    "exchange_order_id": exchange_oid,
+                    "order_db_id": order_db_id,
+                    "symbol": symbol,
+                    "side": exit_side,
+                    "type": order_type_for_stats,
+                    "amount_requested": contracts,
+                    "price_requested": stop_price
+                },
+                "position_type_for_sl": position_type, # Info about the position it's protecting
+                "contracts_for_sl": contracts,
+                "stop_price_set": stop_price
             }
         except Exception as e:
             logger.error(f"Error executing stop loss order: {e}")
@@ -420,30 +531,61 @@ class TradingEngine:
             elif position_type == "SHORT" and profit_price >= entry_price:
                 return {"success": False, "error": "Take profit price should be below entry price for short positions"}
             
-            # Place limit order at take profit price
-            order_data, error_msg = self.client.place_limit_order(symbol, exit_side, contracts, profit_price)
+            order_type_for_stats = 'limit' # Take profit is a limit order at profit_price
+
+            # 1. Generate bot_order_ref_id and record order placement intent
+            bot_order_ref_id = uuid.uuid4().hex
+            order_db_id = self.stats.record_order_placed(
+                symbol=symbol, side=exit_side, order_type=order_type_for_stats,
+                amount_requested=contracts, price=profit_price, 
+                bot_order_ref_id=bot_order_ref_id, status='pending_submission'
+            )
+
+            if not order_db_id:
+                logger.error(f"Failed to record TP order intent in DB for {symbol} with bot_ref_id {bot_order_ref_id}")
+                return {"success": False, "error": "Failed to record TP order intent in database."}
+
+            # 2. Place limit order at take profit price
+            logger.info(f"Placing TAKE PROFIT (LIMIT {exit_side.upper()}) order ({bot_order_ref_id}) for {contracts:.6f} {symbol} at ${profit_price:,.2f}")
+            exchange_order_data, error_msg = self.client.place_limit_order(symbol, exit_side, contracts, profit_price)
             
             if error_msg:
-                logger.error(f"Take profit order placement failed for {symbol}: {error_msg}")
+                logger.error(f"Take profit order placement failed for {symbol} ({bot_order_ref_id}): {error_msg}")
+                self.stats.update_order_status(order_db_id=order_db_id, new_status='failed_submission', bot_order_ref_id=bot_order_ref_id)
                 return {"success": False, "error": f"Take profit order placement failed: {error_msg}"}
-            if not order_data:
-                logger.error(f"Take profit order placement call failed for {symbol}. Client returned no data and no error.")
+            if not exchange_order_data:
+                logger.error(f"Take profit order placement call failed for {symbol} ({bot_order_ref_id}). Client returned no data and no error.")
+                self.stats.update_order_status(order_db_id=order_db_id, new_status='failed_submission_no_data', bot_order_ref_id=bot_order_ref_id)
                 return {"success": False, "error": "Take profit order placement failed (no order object or error)."}
 
-            # If we reached here, order_data is not None
-            order_id = order_data.get('id', 'N/A')
-            
-            if order_id != 'N/A':
-                self.bot_trade_ids.add(order_id)
-            
-            self._save_state()
+            exchange_oid = exchange_order_data.get('id')
+            
+            # 3. Update order in DB with exchange_order_id and status
+            if exchange_oid:
+                self.stats.update_order_status(
+                    order_db_id=order_db_id, 
+                    exchange_order_id=exchange_oid, 
+                    new_status='open', # SL/TP limit orders are 'open' until triggered/filled
+                    bot_order_ref_id=bot_order_ref_id
+                )
+            else:
+                logger.warning(f"No exchange_order_id received for TP order {order_db_id} ({bot_order_ref_id}).")
             
             return {
                 "success": True,
-                "order": order_data,
-                "position_type": position_type,
-                "contracts": contracts,
-                "profit_price": profit_price
+                "order_placed_details": {
+                    "bot_order_ref_id": bot_order_ref_id,
+                    "exchange_order_id": exchange_oid,
+                    "order_db_id": order_db_id,
+                    "symbol": symbol,
+                    "side": exit_side,
+                    "type": order_type_for_stats,
+                    "amount_requested": contracts,
+                    "price_requested": profit_price
+                },
+                "position_type_for_tp": position_type, # Info about the position it's for
+                "contracts_for_tp": contracts,
+                "profit_price_set": profit_price
             }
         except Exception as e:
             logger.error(f"Error executing take profit order: {e}")
@@ -468,15 +610,70 @@ class TradingEngine:
             # return {"success": True, "result": data}
             
             # Sticking to original assumption based on typical CCXT cancel_all_orders:
-            result = self.client.cancel_all_orders(symbol)
+            # result = self.client.cancel_all_orders(symbol)
             # CCXT cancel_all_orders often returns a list of cancelled order structures or similar.
             # If it fails, it typically raises an exception handled by the generic catch block below.
             
-            logger.info(f"Attempted to cancel all orders for {symbol}. Result: {result}")
+            # logger.info(f"Attempted to cancel all orders for {symbol}. Result: {result}")
+            # return {
+            #     "success": True,
+            #     "result": result # This might be a list of order dicts, or specific response from API
+            # }
+
+            # New approach: Assume client.cancel_all_orders returns a list of dicts for cancelled orders, or raises error.
+            # Each dict should have an 'id' field representing the exchange_order_id.
+            cancelled_orders_info, error_msg = self.client.cancel_all_orders(symbol)
+
+            if error_msg:
+                # If the client method returns an error message, it means a general failure, 
+                # not specific order cancellation statuses.
+                logger.error(f"Error cancelling all orders for {symbol}: {error_msg}")
+                return {"success": False, "error": f"Failed to cancel orders: {error_msg}"}
+            
+            if cancelled_orders_info is None: # Should ideally be an empty list if no orders, not None
+                cancelled_orders_info = []
+                logger.info(f"No orders found or reported as cancelled for {symbol} by the client.")
+
+            successful_cancellations_db = []
+            failed_cancellations_db_update = []
+            
+            for order_info in cancelled_orders_info:
+                exchange_oid_to_update = order_info.get('id')
+                if exchange_oid_to_update:
+                    success = self.stats.update_order_status(exchange_order_id=exchange_oid_to_update, new_status='cancelled')
+                    if success:
+                        successful_cancellations_db.append(exchange_oid_to_update)
+                    else:
+                        failed_cancellations_db_update.append(exchange_oid_to_update)
+            
+            # Cancel any pending stop losses linked to the cancelled orders
+            total_cancelled_linked = 0
+            if self.stats and successful_cancellations_db:
+                for exchange_oid in successful_cancellations_db:
+                    # Get the order from DB to find its bot_order_ref_id
+                    order_in_db = self.stats.get_order_by_exchange_id(exchange_oid)
+                    if order_in_db and order_in_db.get('bot_order_ref_id'):
+                        cancelled_linked = self.stats.cancel_linked_orders(
+                            parent_bot_order_ref_id=order_in_db['bot_order_ref_id'],
+                            new_status='cancelled_parent_cancelled'
+                        )
+                        total_cancelled_linked += cancelled_linked
+                        if cancelled_linked > 0:
+                            logger.info(f"🛑 Cancelled {cancelled_linked} linked stop losses for order {exchange_oid}")
+            
+            # self.bot_trade_ids might need to be pruned of these OIDs if they are fully cancelled.
+            # However, relying on the 'orders' table status is better long-term.
+            # For now, _save_state() is not called here as bot_trade_ids interaction is complex with cancellations.
+
             return {
                 "success": True,
-                "result": result # This might be a list of order dicts, or specific response from API
+                "message": f"Cancellation request processed for {symbol}. {len(successful_cancellations_db)} marked in DB.",
+                "cancelled_on_exchange_ids": [info.get('id') for info in cancelled_orders_info if info.get('id')],
+                "db_updates_successful_ids": successful_cancellations_db,
+                "db_updates_failed_ids": failed_cancellations_db_update,
+                "cancelled_linked_stop_losses": total_cancelled_linked
             }
+
         except Exception as e:
             # If client.cancel_all_orders raises an Exception that is caught here,
             # we can use the _extract_error_message if it's available from client.
@@ -499,10 +696,100 @@ class TradingEngine:
         """Alias for cancel_all_orders."""
         return await self.cancel_all_orders(token)
     
-    def is_bot_trade(self, trade_id: str) -> bool:
-        """Check if a trade was generated by this bot."""
-        return trade_id in self.bot_trade_ids
+    def is_bot_trade(self, exchange_order_id: str) -> bool:
+        """Check if an order (by its exchange ID) was recorded by this bot in the orders table."""
+        if not self.stats:
+            return False
+        order_data = self.stats.get_order_by_exchange_id(exchange_order_id)
+        return order_data is not None # If found, it was a bot-managed order
     
     def get_stats(self) -> TradingStats:
         """Get trading statistics object."""
-        return self.stats 
+        return self.stats 
+
+    async def execute_triggered_stop_order(self, original_trigger_order_db_id: int) -> Dict[str, Any]:
+        """Executes an actual stop order on the exchange after its trigger condition was met."""
+        if not self.stats:
+            return {"success": False, "error": "TradingStats not available."}
+
+        trigger_order_details = self.stats.get_order_by_db_id(original_trigger_order_db_id)
+        if not trigger_order_details:
+            return {"success": False, "error": f"Original trigger order DB ID {original_trigger_order_db_id} not found."}
+
+        logger.info(f"Executing triggered stop order based on original trigger DB ID: {original_trigger_order_db_id}, details: {trigger_order_details}")
+
+        symbol = trigger_order_details.get('symbol')
+        # Side of the actual SL order to be placed (e.g., if trigger was 'sell', actual order is 'sell')
+        sl_order_side = trigger_order_details.get('side') 
+        amount = trigger_order_details.get('amount_requested')
+        stop_price = trigger_order_details.get('price') # This was the trigger price, now becomes the limit price for the SL
+        parent_bot_ref_id_of_trigger = trigger_order_details.get('bot_order_ref_id') # The ref ID of the trigger order itself
+
+        if not all([symbol, sl_order_side, amount, stop_price]):
+            msg = f"Missing critical details from trigger order DB ID {original_trigger_order_db_id} to place actual SL order."
+            logger.error(msg)
+            return {"success": False, "error": msg}
+
+        # This logic is very similar to execute_stop_loss_order or execute_take_profit_order
+        # It places a new limit order that acts as the Stop Loss.
+        order_type_for_actual_sl = 'limit' 
+
+        # 1. Generate a new bot_order_ref_id for this actual SL limit order
+        actual_sl_bot_order_ref_id = uuid.uuid4().hex
+        # We can link this actual SL order back to the trigger order that spawned it.
+        actual_sl_order_db_id = self.stats.record_order_placed(
+            symbol=symbol, 
+            side=sl_order_side, 
+            order_type=order_type_for_actual_sl,
+            amount_requested=amount, 
+            price=stop_price, 
+            bot_order_ref_id=actual_sl_bot_order_ref_id, 
+            status='pending_submission',
+            parent_bot_order_ref_id=parent_bot_ref_id_of_trigger # Linking actual SL to its trigger order
+        )
+
+        if not actual_sl_order_db_id:
+            msg = f"Failed to record actual SL order intent in DB (spawned from trigger {original_trigger_order_db_id}). BotRef: {actual_sl_bot_order_ref_id}"
+            logger.error(msg)
+            return {"success": False, "error": msg}
+
+        # 2. Place the actual SL limit order on the exchange
+        logger.info(f"Placing ACTUAL SL ORDER (LIMIT {sl_order_side.upper()}) from trigger {original_trigger_order_db_id}. New BotRef: {actual_sl_bot_order_ref_id}, Amount: {amount}, Price: ${stop_price}")
+        exchange_order_data, error_msg = self.client.place_limit_order(symbol, sl_order_side, amount, stop_price)
+
+        if error_msg:
+            logger.error(f"Actual SL order placement failed for {symbol} (BotRef {actual_sl_bot_order_ref_id}): {error_msg}")
+            self.stats.update_order_status(order_db_id=actual_sl_order_db_id, new_status='failed_submission', bot_order_ref_id=actual_sl_bot_order_ref_id)
+            return {"success": False, "error": f"Actual SL order placement failed: {error_msg}"}
+        if not exchange_order_data:
+            logger.error(f"Actual SL order placement call failed for {symbol} (BotRef {actual_sl_bot_order_ref_id}). No data/error from client.")
+            self.stats.update_order_status(order_db_id=actual_sl_order_db_id, new_status='failed_submission_no_data', bot_order_ref_id=actual_sl_bot_order_ref_id)
+            return {"success": False, "error": "Actual SL order placement failed at client level."}
+
+        exchange_oid = exchange_order_data.get('id')
+
+        # 3. Update the actual SL order in DB with exchange_order_id and status 'open'
+        if exchange_oid:
+            self.stats.update_order_status(
+                order_db_id=actual_sl_order_db_id, 
+                exchange_order_id=exchange_oid, 
+                new_status='open',
+                bot_order_ref_id=actual_sl_bot_order_ref_id
+            )
+        else:
+            logger.warning(f"No exchange_order_id received for actual SL order (BotRef {actual_sl_bot_order_ref_id}).")
+
+        return {
+            "success": True,
+            "message": "Actual Stop Loss order placed successfully.",
+            "placed_sl_order_details": {
+                "bot_order_ref_id": actual_sl_bot_order_ref_id,
+                "exchange_order_id": exchange_oid,
+                "order_db_id": actual_sl_order_db_id,
+                "symbol": symbol,
+                "side": sl_order_side,
+                "type": order_type_for_actual_sl,
+                "amount_requested": amount,
+                "price_requested": stop_price
+            }
+        } 

+ 463 - 102
src/trading/trading_stats.py

@@ -110,6 +110,41 @@ class TradingStats:
                 amount REAL NOT NULL, -- Always positive, type indicates direction
                 description TEXT
             )
+            """,
+            """
+            CREATE TABLE IF NOT EXISTS orders (
+                id INTEGER PRIMARY KEY AUTOINCREMENT,
+                bot_order_ref_id TEXT UNIQUE,
+                exchange_order_id TEXT UNIQUE,
+                symbol TEXT NOT NULL,
+                side TEXT NOT NULL,
+                type TEXT NOT NULL,
+                amount_requested REAL NOT NULL,
+                amount_filled REAL DEFAULT 0.0,
+                price REAL, -- For limit, stop, etc.
+                status TEXT NOT NULL, -- e.g., 'open', 'partially_filled', 'filled', 'cancelled', 'rejected', 'expired', 'pending_trigger'
+                timestamp_created TEXT NOT NULL,
+                timestamp_updated TEXT NOT NULL,
+                parent_bot_order_ref_id TEXT NULLABLE -- To link conditional orders (like SL triggers) to their parent order
+            )
+            """,
+            """
+            CREATE INDEX IF NOT EXISTS idx_orders_bot_order_ref_id ON orders (bot_order_ref_id);
+            """,
+            """
+            CREATE INDEX IF NOT EXISTS idx_orders_exchange_order_id ON orders (exchange_order_id);
+            """,
+            """
+            CREATE INDEX IF NOT EXISTS idx_trades_exchange_fill_id ON trades (exchange_fill_id);
+            """,
+            """
+            CREATE INDEX IF NOT EXISTS idx_trades_linked_order_table_id ON trades (linked_order_table_id);
+            """,
+            """
+            CREATE INDEX IF NOT EXISTS idx_orders_parent_bot_order_ref_id ON orders (parent_bot_order_ref_id);
+            """,
+            """
+            CREATE INDEX IF NOT EXISTS idx_orders_status_type ON orders (status, type);
             """
         ]
         for query in queries:
@@ -170,22 +205,26 @@ class TradingStats:
         # logger.debug(f"Recorded balance for {today_iso}: ${balance:.2f}") # Potentially too verbose
 
     def record_trade(self, symbol: str, side: str, amount: float, price: float,
-                     order_id: Optional[str] = None, trade_type: str = "manual"):
-        """Record a trade in the database."""
-        ts = datetime.now(timezone.utc).isoformat()
+                     exchange_fill_id: Optional[str] = None, trade_type: str = "manual",
+                     pnl: Optional[float] = None, timestamp: Optional[str] = None,
+                     linked_order_table_id_to_link: Optional[int] = None):
+        """Record a trade (fill) in the database, including optional PNL, specific timestamp, and link to an orders table entry."""
+        ts = timestamp if timestamp else datetime.now(timezone.utc).isoformat()
         value = amount * price
         
+        db_pnl = pnl if pnl is not None else 0.0
+
         query = """
-            INSERT INTO trades (order_id, timestamp, symbol, side, amount, price, value, trade_type)
-            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+            INSERT INTO trades (exchange_fill_id, timestamp, symbol, side, amount, price, value, trade_type, pnl, linked_order_table_id)
+            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
         """
-        params = (order_id, ts, symbol, side.lower(), amount, price, value, trade_type)
+        params = (exchange_fill_id, ts, symbol, side.lower(), amount, price, value, trade_type, db_pnl, linked_order_table_id_to_link)
         
         try:
             self._execute_query(query, params)
-            logger.info(f"Recorded trade: {side.upper()} {amount} {symbol} @ ${price:.2f} (Order ID: {order_id or 'N/A'})")
-        except sqlite3.IntegrityError as e: # Handles UNIQUE constraint for order_id
-             logger.warning(f"Failed to record trade due to IntegrityError (likely duplicate order_id {order_id}): {e}")
+            logger.info(f"Recorded trade: {side.upper()} {amount} {symbol} @ ${price:.2f} (Fill ID: {exchange_fill_id or 'N/A'}, PNL: ${db_pnl:.2f}, Linked Order ID: {linked_order_table_id_to_link or 'N/A'})")
+        except sqlite3.IntegrityError as e: 
+             logger.warning(f"Failed to record trade due to IntegrityError (likely duplicate exchange_fill_id {exchange_fill_id}): {e}")
 
 
     def get_enhanced_position_state(self, symbol: str) -> Optional[Dict[str, Any]]:
@@ -194,13 +233,14 @@ class TradingStats:
         return self._fetchone_query(query, (symbol,))
 
     def update_enhanced_position_state(self, symbol: str, side: str, amount: float, price: float, 
-                                     timestamp: Optional[str] = None) -> str:
-        """Update enhanced position state with a new trade and return action type."""
+                                     timestamp: Optional[str] = None) -> Tuple[str, float]:
+        """Update enhanced position state with a new trade and return action type and realized PNL for this trade."""
         if timestamp is None:
             timestamp = datetime.now(timezone.utc).isoformat()
             
         position = self.get_enhanced_position_state(symbol)
         action_type = "unknown"
+        realized_pnl_for_this_trade = 0.0
 
         current_contracts = position['contracts'] if position else 0.0
         current_avg_entry = position['avg_entry_price'] if position else 0.0
@@ -221,7 +261,7 @@ class TradingStats:
                 new_entry_count += 1
             else: # Reducing short position
                 reduction = min(amount, abs(current_contracts))
-                realized_pnl_on_reduction = reduction * (current_avg_entry - price) # PNL from short covering
+                realized_pnl_for_this_trade = reduction * (current_avg_entry - price) # PNL from short covering
                 
                 new_contracts += reduction
                 # Cost basis for shorts is tricky; avg_entry_price is more key for shorts.
@@ -231,15 +271,15 @@ class TradingStats:
                 if new_contracts == 0: # Short fully closed
                     action_type = 'short_closed'
                     self._reset_enhanced_position_state(symbol) # Clears the row
-                    logger.info(f"📉 Enhanced position reset (closed): {symbol}. Realized PNL from this reduction: ${realized_pnl_on_reduction:.2f}")
-                    return action_type 
+                    logger.info(f"📉 Enhanced position reset (closed): {symbol}. Realized PNL from this reduction: ${realized_pnl_for_this_trade:.2f}")
+                    return action_type, realized_pnl_for_this_trade
                 elif new_contracts > 0: # Flipped to long
                     action_type = 'short_closed_and_long_opened'
                     # Reset for the new long position part
                     new_cost_basis = new_contracts * price # Cost basis for the new long part
                     new_avg_entry = price
                     new_entry_count = 1
-                    logger.info(f"⚖️ Enhanced position flipped SHORT to LONG: {symbol}. Realized PNL from short part: ${realized_pnl_on_reduction:.2f}")
+                    logger.info(f"⚖️ Enhanced position flipped SHORT to LONG: {symbol}. Realized PNL from short part: ${realized_pnl_for_this_trade:.2f}")
                 else: # Short reduced
                     action_type = 'short_reduced'
         
@@ -261,7 +301,7 @@ class TradingStats:
 
             else: # Reducing long position
                 reduction = min(amount, current_contracts)
-                realized_pnl_on_reduction = reduction * (price - current_avg_entry) # PNL from long selling
+                realized_pnl_for_this_trade = reduction * (price - current_avg_entry) # PNL from long selling
                 
                 new_contracts -= reduction
                 if new_contracts > 0: # Long reduced
@@ -274,15 +314,15 @@ class TradingStats:
                     if new_contracts == 0: # Long fully closed
                         action_type = 'long_closed'
                         self._reset_enhanced_position_state(symbol)
-                        logger.info(f"📈 Enhanced position reset (closed): {symbol}. Realized PNL from this reduction: ${realized_pnl_on_reduction:.2f}")
-                        return action_type
+                        logger.info(f"📈 Enhanced position reset (closed): {symbol}. Realized PNL from this reduction: ${realized_pnl_for_this_trade:.2f}")
+                        return action_type, realized_pnl_for_this_trade
                     else: # Flipped to short
                         action_type = 'long_closed_and_short_opened'
                         # Reset for new short part
                         new_avg_entry = price # Avg price of this opening short leg
                         new_entry_count = 1
                         new_cost_basis = 0 # Not directly applicable for short open
-                        logger.info(f"⚖️ Enhanced position flipped LONG to SHORT: {symbol}. Realized PNL from long part: ${realized_pnl_on_reduction:.2f}")
+                        logger.info(f"⚖️ Enhanced position flipped LONG to SHORT: {symbol}. Realized PNL from long part: ${realized_pnl_for_this_trade:.2f}")
         
         # Save updated state to DB
         upsert_query = """
@@ -301,29 +341,28 @@ class TradingStats:
         if new_contracts != 0:
             logger.info(f"📊 Enhanced position ({action_type}): {symbol} {side_log} {abs(new_contracts):.6f} @ avg ${(new_avg_entry if new_avg_entry else 0.0):.2f}")
         
-        return action_type
+        return action_type, realized_pnl_for_this_trade
 
     def _reset_enhanced_position_state(self, symbol: str):
         """Reset enhanced position state when position is fully closed by deleting from DB."""
         self._execute_query("DELETE FROM enhanced_positions WHERE symbol = ?", (symbol,))
 
     def record_trade_with_enhanced_tracking(self, symbol: str, side: str, amount: float, price: float,
-                                          order_id: Optional[str] = None, trade_type: str = "manual", 
-                                          timestamp: Optional[str] = None) -> str: # Added timestamp pass-through
-        """Record a trade and update enhanced position tracking."""
-        # Timestamp for the trade record itself
-        trade_timestamp = datetime.now(timezone.utc).isoformat() if timestamp is None else timestamp
-        
-        # Record the trade normally (this uses its own internal timestamping unless one is passed for the trade record)
-        # For consistency, use the provided timestamp if available, otherwise now()
-        self.record_trade(symbol, side, amount, price, order_id, trade_type) 
-        # The `record_trade` method as modified will use its own `datetime.now()`
-        # If we want `record_trade` to use the *passed* timestamp, it needs modification.
-        # For now, `update_enhanced_position_state` uses the passed (or new) timestamp.
-
-        # Update enhanced position state and return action type
-        # Pass the original trade timestamp to `update_enhanced_position_state`
-        return self.update_enhanced_position_state(symbol, side, amount, price, timestamp=trade_timestamp)
+                                          exchange_fill_id: Optional[str] = None, trade_type: str = "manual", 
+                                          timestamp: Optional[str] = None,
+                                          linked_order_table_id_to_link: Optional[int] = None) -> str:
+        """Record a trade and update enhanced position tracking. 
+           The linked_order_table_id_to_link should be passed if this fill corresponds to a known order in the 'orders' table.
+        """
+        trade_timestamp_to_use = timestamp if timestamp else datetime.now(timezone.utc).isoformat()
+        
+        action_type, realized_pnl = self.update_enhanced_position_state(symbol, side, amount, price, timestamp=trade_timestamp_to_use)
+
+        self.record_trade(symbol, side, amount, price, exchange_fill_id, trade_type, 
+                          pnl=realized_pnl, timestamp=trade_timestamp_to_use, 
+                          linked_order_table_id_to_link=linked_order_table_id_to_link)
+        
+        return action_type
 
     def get_all_trades(self) -> List[Dict[str, Any]]:
         """Fetch all trades from the database, ordered by timestamp."""
@@ -332,101 +371,173 @@ class TradingStats:
     def calculate_completed_trade_cycles(self) -> List[Dict[str, Any]]:
         """
         Calculate completed trade cycles (full position open to close) using FIFO method from DB trades.
-        This implementation closely mirrors the original list-based logic.
+        Handles both long and short cycles. PNL is summed from individual trade records.
         """
         completed_cycles = []
-        open_positions_fifo = {} # Tracks open positions by symbol for FIFO cycle calculation
-
-        all_trades = self.get_all_trades()
+        # symbol -> {
+        #   'open_legs': [{'side', 'amount_remaining', 'price', 'timestamp', 'value', 'pnl_contribution'}], # Holds fills of the current open leg
+        #   'cycle_trades_details': [trade_dict_from_db], # All trades part of the current forming cycle
+        #   'cycle_start_ts': timestamp_str,
+        #   'current_leg_type': 'long' | 'short' | None
+        # }
+        open_positions_data = defaultdict(lambda: {
+            'open_legs': [],
+            'cycle_trades_details': [],
+            'cycle_start_ts': None,
+            'current_leg_type': None
+        })
+
+        all_trades = self.get_all_trades() # Trades now include their 'pnl' contribution
 
         for trade in all_trades:
             symbol = trade['symbol']
-            side = trade['side']
+            side = trade['side'].lower() # Ensure lowercase
             amount = trade['amount']
             price = trade['price']
-            timestamp = trade['timestamp'] # This is a string, convert to datetime if needed for duration.
-                                          # For FIFO logic, string comparison might be okay if ISO format.
-
-            if symbol not in open_positions_fifo:
-                open_positions_fifo[symbol] = {
-                    'buys': [], # Stores (amount, price, timestamp) tuples for buys
-                    'cycle_trades_details': [], # Store details of trades in the current cycle
-                    'cycle_start_ts': None
-                }
-            
-            pos_fifo = open_positions_fifo[symbol]
+            timestamp = trade['timestamp']
+            trade_pnl = trade.get('pnl', 0.0) # PNL from this specific fill
+
+            pos_data = open_positions_data[symbol]
 
-            if side == 'buy':
-                if not pos_fifo['buys']: # First buy of a potential new cycle
-                    pos_fifo['cycle_start_ts'] = timestamp
-                    pos_fifo['cycle_trades_details'] = [] # Reset for new cycle
+            current_trade_detail = {**trade} # Copy trade details
 
-                pos_fifo['buys'].append({'amount': amount, 'price': price, 'timestamp': timestamp, 'value': amount * price})
-                pos_fifo['cycle_trades_details'].append({
-                    'side': side, 'amount': amount, 'price': price, 
-                    'timestamp': timestamp, 'value': amount * price
+            if pos_data['current_leg_type'] is None: # Starting a new potential cycle
+                pos_data['current_leg_type'] = 'long' if side == 'buy' else 'short'
+                pos_data['cycle_start_ts'] = timestamp
+                pos_data['open_legs'].append({
+                    'side': side, 'amount_remaining': amount, 'price': price, 
+                    'timestamp': timestamp, 'value': amount * price, 
+                    'pnl_contribution': trade_pnl # PNL of opening trade usually 0
                 })
+                pos_data['cycle_trades_details'] = [current_trade_detail]
+            
+            elif (side == 'buy' and pos_data['current_leg_type'] == 'long') or \
+                 (side == 'sell' and pos_data['current_leg_type'] == 'short'):
+                # Increasing an existing long or short position
+                pos_data['open_legs'].append({
+                    'side': side, 'amount_remaining': amount, 'price': price, 
+                    'timestamp': timestamp, 'value': amount * price,
+                    'pnl_contribution': trade_pnl
+                })
+                pos_data['cycle_trades_details'].append(current_trade_detail)
 
-            elif side == 'sell':
+            elif (side == 'sell' and pos_data['current_leg_type'] == 'long'): # Selling to reduce/close long
+                pos_data['cycle_trades_details'].append(current_trade_detail)
                 sell_amount_remaining = amount
-                sell_value_total = amount * price
-                pnl_for_this_sell_event = 0
                 
-                current_sell_trade_detail = {
-                    'side': side, 'amount': amount, 'price': price, 
-                    'timestamp': timestamp, 'value': sell_value_total, 'pnl': 0 # Temp PNL for this sell
-                }
-                pos_fifo['cycle_trades_details'].append(current_sell_trade_detail)
-
-                while sell_amount_remaining > 0 and pos_fifo['buys']:
-                    oldest_buy = pos_fifo['buys'][0]
-                    
-                    match_amount = min(sell_amount_remaining, oldest_buy['amount'])
+                while sell_amount_remaining > 0 and pos_data['open_legs']:
+                    oldest_leg_fill = pos_data['open_legs'][0] # FIFO
                     
-                    pnl_for_this_match = match_amount * (price - oldest_buy['price'])
-                    pnl_for_this_sell_event += pnl_for_this_match
+                    match_amount = min(sell_amount_remaining, oldest_leg_fill['amount_remaining'])
                     
-                    oldest_buy['amount'] -= match_amount
+                    oldest_leg_fill['amount_remaining'] -= match_amount
                     sell_amount_remaining -= match_amount
                     
-                    if oldest_buy['amount'] <= 1e-9: # Effectively zero, remove
-                        pos_fifo['buys'].pop(0)
+                    if oldest_leg_fill['amount_remaining'] <= 1e-9:
+                        pos_data['open_legs'].pop(0)
                 
-                current_sell_trade_detail['pnl'] = pnl_for_this_sell_event # Update PNL for this sell in cycle details
-
-                if not pos_fifo['buys'] and pos_fifo['cycle_start_ts'] is not None: # Cycle closed
-                    # Calculate cycle totals from cycle_trades_details
-                    cycle_buys_details = [t for t in pos_fifo['cycle_trades_details'] if t['side'] == 'buy']
-                    cycle_sells_details = [t for t in pos_fifo['cycle_trades_details'] if t['side'] == 'sell']
-
-                    total_buy_value = sum(t['value'] for t in cycle_buys_details)
-                    total_sell_value = sum(t['value'] for t in cycle_sells_details)
-                    total_cycle_pnl = sum(t.get('pnl', 0) for t in cycle_sells_details) # PNL is on sells
-                    total_amount_bought = sum(t['amount'] for t in cycle_buys_details)
-                    total_amount_sold = sum(t['amount'] for t in cycle_sells_details)
-
+                if not pos_data['open_legs']: # Long cycle closed
+                    # Compile cycle
+                    cycle_pnl = sum(t.get('pnl', 0.0) for t in pos_data['cycle_trades_details'])
+                    
+                    cycle_buys = [t for t in pos_data['cycle_trades_details'] if t['side'] == 'buy']
+                    cycle_sells = [t for t in pos_data['cycle_trades_details'] if t['side'] == 'sell']
+                    total_amount_bought = sum(t['amount'] for t in cycle_buys)
+                    total_buy_value = sum(t['value'] for t in cycle_buys)
+                    total_amount_sold = sum(t['amount'] for t in cycle_sells) # Should match total_amount_bought
+                    total_sell_value = sum(t['value'] for t in cycle_sells)
 
                     completed_cycle = {
                         'symbol': symbol,
                         'token': symbol.split('/')[0] if '/' in symbol else symbol,
-                        'cycle_start': pos_fifo['cycle_start_ts'],
-                        'cycle_end': timestamp, # End time is the timestamp of the closing sell trade
-                        'buy_orders': len(cycle_buys_details),
-                        'sell_orders': len(cycle_sells_details),
-                        'total_orders': len(pos_fifo['cycle_trades_details']),
-                        'total_amount': total_amount_bought, # Total amount that formed the cost basis
+                        'cycle_start': pos_data['cycle_start_ts'],
+                        'cycle_end': timestamp, # End time is the timestamp of the closing trade
+                        'cycle_type': 'long',
+                        'buy_orders': len(cycle_buys),
+                        'sell_orders': len(cycle_sells),
+                        'total_orders': len(pos_data['cycle_trades_details']),
+                        'total_amount': total_amount_bought,
                         'avg_entry_price': total_buy_value / total_amount_bought if total_amount_bought > 0 else 0,
                         'avg_exit_price': total_sell_value / total_amount_sold if total_amount_sold > 0 else 0,
-                        'total_pnl': total_cycle_pnl,
+                        'total_pnl': cycle_pnl,
                         'buy_value': total_buy_value,
                         'sell_value': total_sell_value,
-                        'cycle_trades': pos_fifo['cycle_trades_details'].copy() # Copy of trades in this cycle
+                        'cycle_trades': pos_data['cycle_trades_details'].copy()
                     }
                     completed_cycles.append(completed_cycle)
                     
-                    # Reset for next cycle for this symbol
-                    pos_fifo['cycle_start_ts'] = None
-                    pos_fifo['cycle_trades_details'] = []
+                    # Reset for next cycle, potentially flip if sell_amount_remaining > 0
+                    pos_data['cycle_trades_details'] = []
+                    pos_data['cycle_start_ts'] = None
+                    pos_data['current_leg_type'] = None
+                    if sell_amount_remaining > 1e-9: # Flipped to short
+                        pos_data['current_leg_type'] = 'short'
+                        pos_data['cycle_start_ts'] = timestamp
+                        pos_data['open_legs'].append({
+                            'side': 'sell', 'amount_remaining': sell_amount_remaining, 'price': price,
+                            'timestamp': timestamp, 'value': sell_amount_remaining * price,
+                            'pnl_contribution': trade_pnl # PNL of this fill if it was part of closing previous and opening this
+                        })
+                        pos_data['cycle_trades_details'] = [current_trade_detail] # Start new details list with current trade
+
+            elif (side == 'buy' and pos_data['current_leg_type'] == 'short'): # Buying to reduce/close short
+                pos_data['cycle_trades_details'].append(current_trade_detail)
+                buy_amount_remaining = amount
+
+                while buy_amount_remaining > 0 and pos_data['open_legs']:
+                    oldest_leg_fill = pos_data['open_legs'][0] # FIFO
+                    
+                    match_amount = min(buy_amount_remaining, oldest_leg_fill['amount_remaining'])
+                    
+                    oldest_leg_fill['amount_remaining'] -= match_amount
+                    buy_amount_remaining -= match_amount
+                    
+                    if oldest_leg_fill['amount_remaining'] <= 1e-9:
+                        pos_data['open_legs'].pop(0)
+
+                if not pos_data['open_legs']: # Short cycle closed
+                    # Compile cycle
+                    cycle_pnl = sum(t.get('pnl', 0.0) for t in pos_data['cycle_trades_details'])
+
+                    cycle_sells = [t for t in pos_data['cycle_trades_details'] if t['side'] == 'sell'] # Entry for short
+                    cycle_buys = [t for t in pos_data['cycle_trades_details'] if t['side'] == 'buy']   # Exit for short
+                    total_amount_sold = sum(t['amount'] for t in cycle_sells)
+                    total_sell_value = sum(t['value'] for t in cycle_sells)
+                    total_amount_bought = sum(t['amount'] for t in cycle_buys) # Should match total_amount_sold
+                    total_buy_value = sum(t['value'] for t in cycle_buys)
+
+                    completed_cycle = {
+                        'symbol': symbol,
+                        'token': symbol.split('/')[0] if '/' in symbol else symbol,
+                        'cycle_start': pos_data['cycle_start_ts'],
+                        'cycle_end': timestamp,
+                        'cycle_type': 'short',
+                        'sell_orders': len(cycle_sells), # Entry orders for short
+                        'buy_orders': len(cycle_buys),   # Exit orders for short
+                        'total_orders': len(pos_data['cycle_trades_details']),
+                        'total_amount': total_amount_sold, # Amount that formed the basis of the short
+                        'avg_entry_price': total_sell_value / total_amount_sold if total_amount_sold > 0 else 0, # Avg sell price
+                        'avg_exit_price': total_buy_value / total_amount_bought if total_amount_bought > 0 else 0,   # Avg buy price
+                        'total_pnl': cycle_pnl,
+                        'sell_value': total_sell_value, # Entry value for short
+                        'buy_value': total_buy_value,   # Exit value for short
+                        'cycle_trades': pos_data['cycle_trades_details'].copy()
+                    }
+                    completed_cycles.append(completed_cycle)
+
+                    # Reset for next cycle, potentially flip if buy_amount_remaining > 0
+                    pos_data['cycle_trades_details'] = []
+                    pos_data['cycle_start_ts'] = None
+                    pos_data['current_leg_type'] = None
+                    if buy_amount_remaining > 1e-9: # Flipped to long
+                        pos_data['current_leg_type'] = 'long'
+                        pos_data['cycle_start_ts'] = timestamp
+                        pos_data['open_legs'].append({
+                            'side': 'buy', 'amount_remaining': buy_amount_remaining, 'price': price,
+                            'timestamp': timestamp, 'value': buy_amount_remaining * price,
+                            'pnl_contribution': trade_pnl
+                        })
+                        pos_data['cycle_trades_details'] = [current_trade_detail]
         
         return completed_cycles
 
@@ -967,3 +1078,253 @@ class TradingStats:
     def __del__(self):
         """Ensure connection is closed when object is deleted."""
         self.close_connection()
+
+    # --- Order Table Management --- 
+
+    def record_order_placed(self, symbol: str, side: str, order_type: str, 
+                            amount_requested: float, price: Optional[float] = None, 
+                            bot_order_ref_id: Optional[str] = None, 
+                            exchange_order_id: Optional[str] = None, 
+                            status: str = 'open',
+                            parent_bot_order_ref_id: Optional[str] = None) -> Optional[int]:
+        """Record a newly placed order in the 'orders' table. Returns the ID of the inserted order or None on failure."""
+        now_iso = datetime.now(timezone.utc).isoformat()
+        query = """
+            INSERT INTO orders (bot_order_ref_id, exchange_order_id, symbol, side, type, 
+                                amount_requested, price, status, timestamp_created, timestamp_updated, parent_bot_order_ref_id)
+            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+        """
+        params = (bot_order_ref_id, exchange_order_id, symbol, side.lower(), order_type.lower(), 
+                  amount_requested, price, status.lower(), now_iso, now_iso, parent_bot_order_ref_id)
+        try:
+            cur = self.conn.cursor()
+            cur.execute(query, params)
+            self.conn.commit()
+            order_db_id = cur.lastrowid
+            logger.info(f"Recorded order placed: ID {order_db_id}, Symbol {symbol}, Side {side}, Type {order_type}, Amount {amount_requested}, BotRef {bot_order_ref_id}, ExchID {exchange_order_id}")
+            return order_db_id
+        except sqlite3.IntegrityError as e:
+            logger.error(f"Failed to record order due to IntegrityError (likely duplicate bot_order_ref_id '{bot_order_ref_id}' or exchange_order_id '{exchange_order_id}'): {e}")
+            return None
+        except Exception as e:
+            logger.error(f"Failed to record order: {e}")
+            return None
+
+    def update_order_status(self, order_db_id: Optional[int] = None, bot_order_ref_id: Optional[str] = None, exchange_order_id: Optional[str] = None,
+                            new_status: Optional[str] = None, amount_filled_increment: Optional[float] = None) -> bool:
+        """Update an existing order's status and/or amount_filled. Identify order by order_db_id, bot_order_ref_id, or exchange_order_id."""
+        if not any([order_db_id, bot_order_ref_id, exchange_order_id]):
+            logger.error("Must provide one of order_db_id, bot_order_ref_id, or exchange_order_id to update order.")
+            return False
+
+        now_iso = datetime.now(timezone.utc).isoformat()
+        set_clauses = []
+        params = []
+
+        if new_status:
+            set_clauses.append("status = ?")
+            params.append(new_status.lower())
+        
+        current_amount_filled = 0.0
+        identifier_clause = ""
+        identifier_param = None
+
+        if order_db_id:
+            identifier_clause = "id = ?"
+            identifier_param = order_db_id
+        elif bot_order_ref_id:
+            identifier_clause = "bot_order_ref_id = ?"
+            identifier_param = bot_order_ref_id
+        elif exchange_order_id:
+            identifier_clause = "exchange_order_id = ?"
+            identifier_param = exchange_order_id
+
+        if amount_filled_increment is not None and amount_filled_increment > 0:
+            # To correctly increment, we might need to fetch current filled amount first if DB doesn't support direct increment easily or atomically with other updates.
+            # For simplicity here, assuming we can use SQL's increment if other fields are not changing, or we do it in two steps.
+            # Let's assume we fetch first then update to be safe and clear.
+            order_data = self._fetchone_query(f"SELECT amount_filled FROM orders WHERE {identifier_clause}", (identifier_param,))
+            if order_data:
+                current_amount_filled = order_data.get('amount_filled', 0.0)
+            else:
+                logger.warning(f"Order not found by {identifier_clause}={identifier_param} when trying to increment amount_filled.")
+                # Potentially still update status if new_status is provided, but amount_filled won't be right.
+                # For now, let's proceed with update if status is there.
+
+            set_clauses.append("amount_filled = ?")
+            params.append(current_amount_filled + amount_filled_increment)
+
+        if not set_clauses:
+            logger.info("No fields to update for order.")
+            return True # Or False if an update was expected
+
+        set_clauses.append("timestamp_updated = ?")
+        params.append(now_iso)
+        
+        params.append(identifier_param) # Add identifier param at the end for WHERE clause
+
+        query = f"UPDATE orders SET { ', '.join(set_clauses) } WHERE {identifier_clause}"
+        
+        try:
+            self._execute_query(query, params)
+            logger.info(f"Updated order ({identifier_clause}={identifier_param}): Status to '{new_status or 'N/A'}', Filled increment {amount_filled_increment or 0.0}")
+            return True
+        except Exception as e:
+            logger.error(f"Failed to update order ({identifier_clause}={identifier_param}): {e}")
+            return False
+
+    def get_order_by_db_id(self, order_db_id: int) -> Optional[Dict[str, Any]]:
+        """Fetch an order by its database primary key ID."""
+        return self._fetchone_query("SELECT * FROM orders WHERE id = ?", (order_db_id,))
+
+    def get_order_by_bot_ref_id(self, bot_order_ref_id: str) -> Optional[Dict[str, Any]]:
+        """Fetch an order by the bot's internal reference ID."""
+        return self._fetchone_query("SELECT * FROM orders WHERE bot_order_ref_id = ?", (bot_order_ref_id,))
+
+    def get_order_by_exchange_id(self, exchange_order_id: str) -> Optional[Dict[str, Any]]:
+        """Fetch an order by the exchange's order ID."""
+        return self._fetchone_query("SELECT * FROM orders WHERE exchange_order_id = ?", (exchange_order_id,))
+
+    def get_orders_by_status(self, status: str, order_type_filter: Optional[str] = None, parent_bot_order_ref_id: Optional[str] = None) -> List[Dict[str, Any]]:
+        """Fetch all orders with a specific status, optionally filtering by order_type and parent_bot_order_ref_id."""
+        query = "SELECT * FROM orders WHERE status = ?"
+        params = [status.lower()]
+        if order_type_filter:
+            query += " AND type = ?"
+            params.append(order_type_filter.lower())
+        if parent_bot_order_ref_id:
+            query += " AND parent_bot_order_ref_id = ?"
+            params.append(parent_bot_order_ref_id)
+        query += " ORDER BY timestamp_created ASC"
+        return self._fetch_query(query, tuple(params))
+
+    def cancel_linked_orders(self, parent_bot_order_ref_id: str, new_status: str = 'cancelled_parent_filled') -> int:
+        """Cancel all orders linked to a parent order (e.g., pending stop losses when parent order fills or gets cancelled).
+        Returns the number of orders that were cancelled."""
+        linked_orders = self.get_orders_by_status('pending_trigger', parent_bot_order_ref_id=parent_bot_order_ref_id)
+        cancelled_count = 0
+        
+        for order in linked_orders:
+            order_db_id = order.get('id')
+            if order_db_id:
+                success = self.update_order_status(order_db_id=order_db_id, new_status=new_status)
+                if success:
+                    cancelled_count += 1
+                    logger.info(f"Cancelled linked order ID {order_db_id} (parent: {parent_bot_order_ref_id}) -> status: {new_status}")
+        
+        return cancelled_count
+
+    def cancel_pending_stop_losses_by_symbol(self, symbol: str, new_status: str = 'cancelled_position_closed') -> int:
+        """Cancel all pending stop loss orders for a specific symbol (when position is closed).
+        Returns the number of stop loss orders that were cancelled."""
+        query = "SELECT * FROM orders WHERE symbol = ? AND status = 'pending_trigger' AND type = 'stop_limit_trigger'"
+        pending_stop_losses = self._fetch_query(query, (symbol,))
+        cancelled_count = 0
+        
+        for order in pending_stop_losses:
+            order_db_id = order.get('id')
+            if order_db_id:
+                success = self.update_order_status(order_db_id=order_db_id, new_status=new_status)
+                if success:
+                    cancelled_count += 1
+                    logger.info(f"Cancelled pending SL order ID {order_db_id} for {symbol} -> status: {new_status}")
+        
+        return cancelled_count
+
+    def get_order_cleanup_summary(self) -> Dict[str, Any]:
+        """Get summary of order cleanup actions for monitoring and debugging."""
+        try:
+            # Get counts of different cancellation types
+            cleanup_stats = {}
+            
+            cancellation_types = [
+                'cancelled_parent_cancelled',
+                'cancelled_parent_disappeared', 
+                'cancelled_manual_exit',
+                'cancelled_auto_exit',
+                'cancelled_no_position',
+                'cancelled_external_position_close',
+                'cancelled_orphaned_no_position',
+                'cancelled_externally',
+                'immediately_executed_on_activation',
+                'activation_execution_failed',
+                'activation_execution_error'
+            ]
+            
+            for cancel_type in cancellation_types:
+                count_result = self._fetchone_query(
+                    "SELECT COUNT(*) as count FROM orders WHERE status = ?", 
+                    (cancel_type,)
+                )
+                cleanup_stats[cancel_type] = count_result['count'] if count_result else 0
+            
+            # Get currently pending stop losses
+            pending_sls = self.get_orders_by_status('pending_trigger', 'stop_limit_trigger')
+            cleanup_stats['currently_pending_stop_losses'] = len(pending_sls)
+            
+            # Get total orders in various states
+            active_orders = self._fetchone_query(
+                "SELECT COUNT(*) as count FROM orders WHERE status IN ('open', 'submitted', 'partially_filled')",
+                ()
+            )
+            cleanup_stats['currently_active_orders'] = active_orders['count'] if active_orders else 0
+            
+            return cleanup_stats
+            
+        except Exception as e:
+            logger.error(f"Error getting order cleanup summary: {e}")
+            return {}
+
+    def get_external_activity_summary(self, days: int = 7) -> Dict[str, Any]:
+        """Get summary of external activity (trades and cancellations) over the last N days."""
+        try:
+            from datetime import timedelta
+            cutoff_date = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
+            
+            # External trades
+            external_trades = self._fetch_query(
+                "SELECT COUNT(*) as count, side FROM trades WHERE trade_type = 'external' AND timestamp >= ? GROUP BY side",
+                (cutoff_date,)
+            )
+            
+            external_trade_summary = {
+                'external_buy_trades': 0,
+                'external_sell_trades': 0,
+                'total_external_trades': 0
+            }
+            
+            for trade_group in external_trades:
+                side = trade_group['side']
+                count = trade_group['count']
+                external_trade_summary['total_external_trades'] += count
+                if side == 'buy':
+                    external_trade_summary['external_buy_trades'] = count
+                elif side == 'sell':
+                    external_trade_summary['external_sell_trades'] = count
+            
+            # External cancellations
+            external_cancellations = self._fetchone_query(
+                "SELECT COUNT(*) as count FROM orders WHERE status = 'cancelled_externally' AND timestamp_updated >= ?",
+                (cutoff_date,)
+            )
+            external_trade_summary['external_cancellations'] = external_cancellations['count'] if external_cancellations else 0
+            
+            # Cleanup actions
+            cleanup_cancellations = self._fetchone_query(
+                """SELECT COUNT(*) as count FROM orders 
+                   WHERE status LIKE 'cancelled_%' 
+                   AND status != 'cancelled_externally' 
+                   AND timestamp_updated >= ?""",
+                (cutoff_date,)
+            )
+            external_trade_summary['cleanup_cancellations'] = cleanup_cancellations['count'] if cleanup_cancellations else 0
+            
+            external_trade_summary['period_days'] = days
+            
+            return external_trade_summary
+            
+        except Exception as e:
+            logger.error(f"Error getting external activity summary: {e}")
+            return {'period_days': days, 'total_external_trades': 0, 'external_cancellations': 0}
+
+    # --- End Order Table Management ---