Forráskód Böngészése

Add initial price alarms JSON and trading stats SQLite file - Created a new JSON file for price alarms to manage alarm states and added a SQLite database for trading statistics, enhancing data persistence and management. Updated trading commands to reflect changes in order cancellation notifications, including detailed summaries of cancelled orders.

Carles Sentis 4 napja
szülő
commit
c6d9f2f62a

+ 6 - 0
data/price_alarms.json

@@ -0,0 +1,6 @@
+{
+  "next_id": 1,
+  "alarms": [],
+  "triggered_alarms": [],
+  "last_update": "2025-06-03T14:39:09.411363"
+}

BIN
data/trading_stats.sqlite


+ 4 - 3
src/commands/trading_commands.py

@@ -802,12 +802,13 @@ 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_count = result.get("cancelled_count", 0)
+            failed_count = result.get("failed_count", 0)
             cancelled_linked_sls = result.get("cancelled_linked_stop_losses", 0)
+            cancelled_orders = result.get("cancelled_orders", [])
             
             await self.notification_manager.send_coo_success_notification(
-                query, token, cancelled_count, failed_count, cancelled_linked_sls
+                query, token, cancelled_count, failed_count, cancelled_linked_sls, cancelled_orders
             )
         else:
             await query.edit_message_text(f"❌ Cancel orders failed: {result['error']}") 

+ 34 - 17
src/notifications/notification_manager.py

@@ -4,7 +4,7 @@ Notification Manager - Handles all bot notifications and messages.
 """
 
 import logging
-from typing import Optional, Dict, Any
+from typing import Optional, Dict, Any, List
 from datetime import datetime
 
 logger = logging.getLogger(__name__)
@@ -193,38 +193,55 @@ 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, cancelled_linked_sls: int = 0):
+                                          failed_count: int, cancelled_linked_sls: int = 0,
+                                          cancelled_orders: List[Dict[str, Any]] = None):
         """Send notification for successful cancel all orders operation."""
         
         success_message = f"""
-✅ <b>Cancel Orders Complete!</b>
+✅ <b>Cancel Orders Results</b>
 
-📊 <b>Results for {token}:</b>
-• Orders Cancelled: {cancelled_count}
-• Failed Cancellations: {failed_count}
-• Total Processed: {cancelled_count + failed_count}"""
+📊 <b>Summary:</b>
+• Token: {token}
+• Cancelled: {cancelled_count} orders
+• Failed: {failed_count} orders
+• Total Attempted: {cancelled_count + failed_count} orders"""
         
         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"}
+        # Show details of cancelled orders if available
+        if cancelled_orders and len(cancelled_orders) > 0:
+            success_message += f"""
 
-📋 <b>Summary:</b>
-• Token: {token}
-• Status: {'PARTIAL SUCCESS' if failed_count > 0 else 'SUCCESS'} ✅
-• Time: {datetime.now().strftime('%H:%M:%S')}"""
+🗑️ <b>Successfully Cancelled:</b>"""
+            for order in cancelled_orders:
+                side = order.get('side', 'Unknown')
+                amount = order.get('amount', 0)
+                price = order.get('price', 0)
+                side_emoji = "🟢" if side.lower() == 'buy' else "🔴"
+                success_message += f"""
+{side_emoji} {side.upper()} {amount} @ ${price:,.2f}"""
         
-        if cancelled_linked_sls > 0:
+        # Overall status
+        if cancelled_count == (cancelled_count + failed_count) and failed_count == 0:
             success_message += f"""
 
-🛑 <b>Cleanup:</b> Automatically cancelled {cancelled_linked_sls} pending stop loss(es) linked to cancelled orders"""
+🎉 All {token} orders successfully cancelled!"""
+        elif cancelled_count > 0:
+            success_message += f"""
+
+⚠️ Some orders cancelled. {failed_count} failed."""
+        else:
+            success_message += f"""
+
+❌ Could not cancel any {token} orders."""
         
         success_message += f"""
 
-💡 Use /orders to verify no pending orders remain.
+⏰ <b>Time:</b> {datetime.now().strftime('%H:%M:%S')}
+
+📊 Use /orders to verify no pending orders remain.
         """
         
         await query.edit_message_text(success_message, parse_mode='HTML')

+ 114 - 91
src/trading/trading_engine.py

@@ -591,97 +591,78 @@ class TradingEngine:
             logger.error(f"Error executing take profit order: {e}")
             return {"success": False, "error": str(e)}
     
-    async def cancel_all_orders(self, token: str) -> Dict[str, Any]:
-        """Cancel all orders for a token."""
+    def cancel_all_orders(self, symbol: str) -> Tuple[List[Dict[str, Any]], Optional[str]]:
+        """Cancel all open orders for a specific symbol. Returns (cancelled_orders, error_message)."""
         try:
-            symbol = f"{token}/USDC:USDC"
-            # Assuming self.client.cancel_all_orders either succeeds and returns data or raises an exception
-            # If it can return (None, error_msg) like other order methods, this would need adjustment
-            # For now, sticking to its likely current CCXT-like behavior.
-            # If it was changed in hyperliquid_client.py, this needs to be updated.
-            # Let's assume cancel_all_orders was NOT changed to return a tuple for now.
-            # It usually returns specific data from CCXT or raises an error.
-            
-            # If self.client.cancel_all_orders was modified to return (data, error_msg):
-            # data, error_msg = self.client.cancel_all_orders(symbol)
-            # if error_msg:
-            #     logger.error(f"Error cancelling orders for {symbol}: {error_msg}")
-            #     return {"success": False, "error": f"Failed to cancel orders: {error_msg}"}
-            # return {"success": True, "result": data}
-            
-            # Sticking to original assumption based on typical CCXT cancel_all_orders:
-            # 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}")
-            # 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,
-                "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
-            }
-
+            logger.info(f"Attempting to cancel all orders for {symbol}")
+            
+            # Get all open orders
+            all_orders = self.client.get_open_orders()
+            if all_orders is None:
+                error_msg = f"Could not fetch orders to cancel {symbol} orders"
+                logger.error(error_msg)
+                return [], error_msg
+            
+            # Filter orders for the specific symbol
+            symbol_orders = [order for order in all_orders if order.get('symbol') == symbol]
+            
+            if not symbol_orders:
+                logger.info(f"No open orders found for {symbol}")
+                return [], None  # No error, just no orders to cancel
+            
+            # Cancel each order individually
+            cancelled_orders = []
+            failed_orders = []
+            
+            for order in symbol_orders:
+                order_id = order.get('id')
+                if order_id:
+                    try:
+                        success = self.client.cancel_order(order_id, symbol)
+                        if success:
+                            cancelled_orders.append(order)
+                            logger.info(f"Successfully cancelled order {order_id} for {symbol}")
+                        else:
+                            failed_orders.append(order)
+                            logger.warning(f"Failed to cancel order {order_id} for {symbol}")
+                    except Exception as e:
+                        logger.error(f"Exception cancelling order {order_id}: {e}")
+                        failed_orders.append(order)
+            
+            # Update order status in database if we have stats
+            if self.stats:
+                for order in cancelled_orders:
+                    order_id = order.get('id')
+                    if order_id:
+                        # Try to find this order in our database and update its status
+                        db_order = self.stats.get_order_by_exchange_id(order_id)
+                        if db_order:
+                            self.stats.update_order_status(
+                                exchange_order_id=order_id, 
+                                new_status='cancelled_manually'
+                            )
+                
+                # Cancel any linked pending stop losses for this symbol
+                cleanup_count = self.stats.cancel_pending_stop_losses_by_symbol(
+                    symbol, 
+                    'cancelled_manual_exit'
+                )
+                if cleanup_count > 0:
+                    logger.info(f"Cleaned up {cleanup_count} pending stop losses for {symbol}")
+            
+            # Prepare result
+            if failed_orders:
+                error_msg = f"Cancelled {len(cancelled_orders)}/{len(symbol_orders)} orders. {len(failed_orders)} failed."
+                logger.warning(error_msg)
+                return cancelled_orders, error_msg
+            else:
+                logger.info(f"Successfully cancelled all {len(cancelled_orders)} orders for {symbol}")
+                return cancelled_orders, None
+                
         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.
-            error_message = str(e)
-            if hasattr(self.client, '_extract_error_message'):
-                error_message = self.client._extract_error_message(e)
-            logger.error(f"Error cancelling orders for {token}: {error_message}", exc_info=True)
-            return {"success": False, "error": f"Failed to cancel orders: {error_message}"}
+            error_msg = f"Error cancelling orders for {symbol}: {str(e)}"
+            logger.error(error_msg, exc_info=True)
+            return [], error_msg
     
     # Alias methods for consistency with command handlers
     async def execute_sl_order(self, token: str, stop_price: float) -> Dict[str, Any]:
@@ -693,8 +674,50 @@ class TradingEngine:
         return await self.execute_take_profit_order(token, profit_price)
     
     async def execute_coo_order(self, token: str) -> Dict[str, Any]:
-        """Alias for cancel_all_orders."""
-        return await self.cancel_all_orders(token)
+        """Cancel all orders for a token and format response like the old code expected."""
+        try:
+            symbol = f"{token}/USDC:USDC"
+            
+            # Call the synchronous cancel_all_orders method
+            cancelled_orders, error_msg = self.cancel_all_orders(symbol)
+            
+            if error_msg:
+                logger.error(f"Error cancelling all orders for {token}: {error_msg}")
+                return {"success": False, "error": error_msg}
+            
+            if not cancelled_orders:
+                logger.info(f"No orders found to cancel for {token}")
+                return {
+                    "success": True,
+                    "message": f"No orders found for {token}",
+                    "cancelled_orders": [],
+                    "cancelled_count": 0,
+                    "failed_count": 0,
+                    "cancelled_linked_stop_losses": 0
+                }
+            
+            # Get cleanup count from stats if available 
+            cleanup_count = 0
+            if self.stats:
+                cleanup_count = self.stats.cancel_pending_stop_losses_by_symbol(
+                    symbol, 
+                    'cancelled_manual_exit'
+                )
+            
+            logger.info(f"Successfully cancelled {len(cancelled_orders)} orders for {token}")
+            
+            return {
+                "success": True,
+                "message": f"Successfully cancelled {len(cancelled_orders)} orders for {token}",
+                "cancelled_orders": cancelled_orders,
+                "cancelled_count": len(cancelled_orders), 
+                "failed_count": 0,  # Failed orders are handled in the error case above
+                "cancelled_linked_stop_losses": cleanup_count
+            }
+            
+        except Exception as e:
+            logger.error(f"Error in execute_coo_order for {token}: {e}")
+            return {"success": False, "error": str(e)}
     
     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."""