|
@@ -155,7 +155,7 @@ class TradingEngine:
|
|
|
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 = self.client.place_limit_order(symbol, 'buy', token_amount, order_placement_price)
|
|
|
+ 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
|
|
@@ -163,20 +163,23 @@ class TradingEngine:
|
|
|
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 = self.client.place_market_order(symbol, 'buy', token_amount)
|
|
|
+ order_data, error_msg = self.client.place_market_order(symbol, 'buy', token_amount)
|
|
|
|
|
|
- if not order:
|
|
|
- logger.error(f"Order placement call failed for {symbol}. Client returned None or empty.")
|
|
|
- return {"success": False, "error": "Order placement failed at client level (no order object)."}
|
|
|
+ if error_msg:
|
|
|
+ logger.error(f"Order placement failed for {symbol}: {error_msg}")
|
|
|
+ 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.")
|
|
|
+ return {"success": False, "error": "Order placement failed at client level (no order object or error)."}
|
|
|
|
|
|
- order_id = order.get('id', 'N/A')
|
|
|
- order_avg_fill_price = order.get('average')
|
|
|
+ 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}, Placement Price: {order_placement_price}, Avg Fill: {order_avg_fill_price}")
|
|
|
+ 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}")
|
|
@@ -203,7 +206,7 @@ class TradingEngine:
|
|
|
|
|
|
return {
|
|
|
"success": True,
|
|
|
- "order": order,
|
|
|
+ "order": order_data,
|
|
|
"action_type": action_type,
|
|
|
"token_amount": token_amount,
|
|
|
"actual_price": final_price_for_stats,
|
|
@@ -243,26 +246,29 @@ class TradingEngine:
|
|
|
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 = self.client.place_limit_order(symbol, 'sell', token_amount, order_placement_price)
|
|
|
+ order_data, error_msg = self.client.place_limit_order(symbol, 'sell', token_amount, order_placement_price)
|
|
|
else:
|
|
|
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 = self.client.place_market_order(symbol, 'sell', token_amount)
|
|
|
+ order_data, error_msg = self.client.place_market_order(symbol, 'sell', token_amount)
|
|
|
|
|
|
- if not order:
|
|
|
- logger.error(f"Order placement call failed for {symbol}. Client returned None or empty.")
|
|
|
- return {"success": False, "error": "Order placement failed at client level (no order object)."}
|
|
|
+ if error_msg:
|
|
|
+ logger.error(f"Order placement failed for {symbol}: {error_msg}")
|
|
|
+ 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.")
|
|
|
+ return {"success": False, "error": "Order placement failed at client level (no order object or error)."}
|
|
|
|
|
|
- order_id = order.get('id', 'N/A')
|
|
|
- order_avg_fill_price = order.get('average')
|
|
|
+ order_id = order_data.get('id', 'N/A')
|
|
|
+ order_avg_fill_price = order_data.get('average')
|
|
|
|
|
|
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}, Placement Price: {order_placement_price}, Avg Fill: {order_avg_fill_price}")
|
|
|
+ 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}")
|
|
@@ -287,7 +293,7 @@ class TradingEngine:
|
|
|
|
|
|
return {
|
|
|
"success": True,
|
|
|
- "order": order,
|
|
|
+ "order": order_data,
|
|
|
"action_type": action_type,
|
|
|
"token_amount": token_amount,
|
|
|
"actual_price": final_price_for_stats,
|
|
@@ -311,32 +317,42 @@ class TradingEngine:
|
|
|
position_type, exit_side, contracts = self.get_position_direction(position)
|
|
|
|
|
|
# Execute market order to close position
|
|
|
- order = self.client.place_market_order(symbol, exit_side, contracts)
|
|
|
+ order_data, error_msg = self.client.place_market_order(symbol, exit_side, contracts)
|
|
|
|
|
|
- if order:
|
|
|
- order_id = order.get('id', 'N/A')
|
|
|
- actual_price = order.get('average', 0)
|
|
|
-
|
|
|
- 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()
|
|
|
-
|
|
|
- return {
|
|
|
- "success": True,
|
|
|
- "order": order,
|
|
|
- "action_type": action_type,
|
|
|
- "position_type": position_type,
|
|
|
- "contracts": contracts,
|
|
|
- "actual_price": actual_price
|
|
|
- }
|
|
|
- else:
|
|
|
- return {"success": False, "error": "Exit order execution failed"}
|
|
|
-
|
|
|
+ if error_msg:
|
|
|
+ logger.error(f"Exit order execution failed for {symbol}: {error_msg}")
|
|
|
+ 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.")
|
|
|
+ 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()
|
|
|
+
|
|
|
+ return {
|
|
|
+ "success": True,
|
|
|
+ "order": order_data,
|
|
|
+ "action_type": action_type,
|
|
|
+ "position_type": position_type,
|
|
|
+ "contracts": contracts,
|
|
|
+ "actual_price": actual_price
|
|
|
+ }
|
|
|
except Exception as e:
|
|
|
logger.error(f"Error executing exit order: {e}")
|
|
|
return {"success": False, "error": str(e)}
|
|
@@ -359,26 +375,30 @@ class TradingEngine:
|
|
|
return {"success": False, "error": "Stop loss price should be above entry price for short positions"}
|
|
|
|
|
|
# Place limit order at stop loss price
|
|
|
- order = self.client.place_limit_order(symbol, exit_side, contracts, stop_price)
|
|
|
+ order_data, error_msg = self.client.place_limit_order(symbol, exit_side, contracts, stop_price)
|
|
|
|
|
|
- if order:
|
|
|
- order_id = order.get('id', 'N/A')
|
|
|
-
|
|
|
- if order_id != 'N/A':
|
|
|
- self.bot_trade_ids.add(order_id)
|
|
|
-
|
|
|
- self._save_state()
|
|
|
-
|
|
|
- return {
|
|
|
- "success": True,
|
|
|
- "order": order,
|
|
|
- "position_type": position_type,
|
|
|
- "contracts": contracts,
|
|
|
- "stop_price": stop_price
|
|
|
- }
|
|
|
- else:
|
|
|
- return {"success": False, "error": "Stop loss order placement failed"}
|
|
|
-
|
|
|
+ if error_msg:
|
|
|
+ logger.error(f"Stop loss order placement failed for {symbol}: {error_msg}")
|
|
|
+ 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.")
|
|
|
+ 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()
|
|
|
+
|
|
|
+ return {
|
|
|
+ "success": True,
|
|
|
+ "order": order_data,
|
|
|
+ "position_type": position_type,
|
|
|
+ "contracts": contracts,
|
|
|
+ "stop_price": stop_price
|
|
|
+ }
|
|
|
except Exception as e:
|
|
|
logger.error(f"Error executing stop loss order: {e}")
|
|
|
return {"success": False, "error": str(e)}
|
|
@@ -401,26 +421,30 @@ class TradingEngine:
|
|
|
return {"success": False, "error": "Take profit price should be below entry price for short positions"}
|
|
|
|
|
|
# Place limit order at take profit price
|
|
|
- order = self.client.place_limit_order(symbol, exit_side, contracts, profit_price)
|
|
|
+ order_data, error_msg = self.client.place_limit_order(symbol, exit_side, contracts, profit_price)
|
|
|
|
|
|
- if order:
|
|
|
- order_id = order.get('id', 'N/A')
|
|
|
-
|
|
|
- if order_id != 'N/A':
|
|
|
- self.bot_trade_ids.add(order_id)
|
|
|
-
|
|
|
- self._save_state()
|
|
|
-
|
|
|
- return {
|
|
|
- "success": True,
|
|
|
- "order": order,
|
|
|
- "position_type": position_type,
|
|
|
- "contracts": contracts,
|
|
|
- "profit_price": profit_price
|
|
|
- }
|
|
|
- else:
|
|
|
- return {"success": False, "error": "Take profit order placement failed"}
|
|
|
-
|
|
|
+ if error_msg:
|
|
|
+ logger.error(f"Take profit order placement failed for {symbol}: {error_msg}")
|
|
|
+ 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.")
|
|
|
+ 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()
|
|
|
+
|
|
|
+ return {
|
|
|
+ "success": True,
|
|
|
+ "order": order_data,
|
|
|
+ "position_type": position_type,
|
|
|
+ "contracts": contracts,
|
|
|
+ "profit_price": profit_price
|
|
|
+ }
|
|
|
except Exception as e:
|
|
|
logger.error(f"Error executing take profit order: {e}")
|
|
|
return {"success": False, "error": str(e)}
|
|
@@ -429,15 +453,38 @@ class TradingEngine:
|
|
|
"""Cancel all orders for a token."""
|
|
|
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
|
|
|
+ "result": result # This might be a list of order dicts, or specific response from API
|
|
|
}
|
|
|
except Exception as e:
|
|
|
- logger.error(f"Error cancelling orders: {e}")
|
|
|
- return {"success": False, "error": str(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}"}
|
|
|
|
|
|
# Alias methods for consistency with command handlers
|
|
|
async def execute_sl_order(self, token: str, stop_price: float) -> Dict[str, Any]:
|