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