From 29b332558137700eb86076589badc2583b3b1db8 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Wed, 25 Jun 2025 17:08:32 +0300 Subject: [PATCH] executor now can do shorty and long --- core/trading_executor.py | 266 +++++++++++++++++++++++++++++++++++---- 1 file changed, 243 insertions(+), 23 deletions(-) diff --git a/core/trading_executor.py b/core/trading_executor.py index c03f6db..d954c6c 100644 --- a/core/trading_executor.py +++ b/core/trading_executor.py @@ -158,7 +158,7 @@ class TradingExecutor: return False def execute_signal(self, symbol: str, action: str, confidence: float, - current_price: float = None) -> bool: + current_price: Optional[float] = None) -> bool: """Execute a trading signal Args: @@ -181,14 +181,17 @@ class TradingExecutor: if not self._check_safety_conditions(symbol, action): return False - # Get current price if not provided + # Get current price if not provided if current_price is None: ticker = self.exchange.get_ticker(symbol) if not ticker: logger.error(f"Failed to get current price for {symbol}") return False current_price = ticker['last'] - + + # Assert that current_price is not None for type checking + assert current_price is not None, "current_price should not be None at this point" + with self.lock: try: if action == 'BUY': @@ -244,10 +247,15 @@ class TradingExecutor: def _execute_buy(self, symbol: str, confidence: float, current_price: float) -> bool: """Execute a buy order""" - # Check if we already have a position + # Check if we have a short position to close if symbol in self.positions: - logger.info(f"Already have position in {symbol}") - return False + position = self.positions[symbol] + if position.side == 'SHORT': + logger.info(f"Closing SHORT position in {symbol}") + return self._close_short_position(symbol, confidence, current_price) + else: + logger.info(f"Already have LONG position in {symbol}") + return False # Calculate position size position_value = self._calculate_position_size(confidence, current_price) @@ -282,13 +290,23 @@ class TradingExecutor: limit_price = current_price * 1.001 # 0.1% above market # Place buy order - order = self.exchange.place_order( - symbol=symbol, - side='buy', - order_type=order_type, - quantity=quantity, - price=limit_price - ) + if order_type == 'market': + order = self.exchange.place_order( + symbol=symbol, + side='buy', + order_type=order_type, + quantity=quantity + ) + else: + # For limit orders, price is required + assert limit_price is not None, "limit_price required for limit orders" + order = self.exchange.place_order( + symbol=symbol, + side='buy', + order_type=order_type, + quantity=quantity, + price=limit_price + ) if order: # Create position record @@ -319,8 +337,7 @@ class TradingExecutor: # Check if we have a position to sell if symbol not in self.positions: logger.info(f"No position to sell in {symbol}. Opening short position") - # TODO: Open short position - + return self._execute_short(symbol, confidence, current_price) position = self.positions[symbol] @@ -368,13 +385,23 @@ class TradingExecutor: limit_price = current_price * 0.999 # 0.1% below market # Place sell order - order = self.exchange.place_order( - symbol=symbol, - side='sell', - order_type=order_type, - quantity=position.quantity, - price=limit_price - ) + if order_type == 'market': + order = self.exchange.place_order( + symbol=symbol, + side='sell', + order_type=order_type, + quantity=position.quantity + ) + else: + # For limit orders, price is required + assert limit_price is not None, "limit_price required for limit orders" + order = self.exchange.place_order( + symbol=symbol, + side='sell', + order_type=order_type, + quantity=position.quantity, + price=limit_price + ) if order: # Calculate P&L @@ -414,6 +441,199 @@ class TradingExecutor: logger.error(f"Error executing SELL order: {e}") return False + def _execute_short(self, symbol: str, confidence: float, current_price: float) -> bool: + """Execute a short position opening""" + # Check if we already have a position + if symbol in self.positions: + logger.info(f"Already have position in {symbol}") + return False + + # Calculate position size + position_value = self._calculate_position_size(confidence, current_price) + quantity = position_value / current_price + + logger.info(f"Executing SHORT: {quantity:.6f} {symbol} at ${current_price:.2f} " + f"(value: ${position_value:.2f}, confidence: {confidence:.2f})") + + if self.simulation_mode: + logger.info(f"SIMULATION MODE ({self.trading_mode.upper()}) - Short position logged but not executed") + # Create mock short position for tracking + self.positions[symbol] = Position( + symbol=symbol, + side='SHORT', + quantity=quantity, + entry_price=current_price, + entry_time=datetime.now(), + order_id=f"sim_short_{int(time.time())}" + ) + self.last_trade_time[symbol] = datetime.now() + self.daily_trades += 1 + return True + + try: + # Get order type from config + order_type = self.mexc_config.get('order_type', 'market').lower() + + # For limit orders, set price slightly below market for immediate execution + limit_price = None + if order_type == 'limit': + # Set short price slightly below market to ensure immediate execution + limit_price = current_price * 0.999 # 0.1% below market + + # Place short sell order + if order_type == 'market': + order = self.exchange.place_order( + symbol=symbol, + side='sell', # Short selling starts with a sell order + order_type=order_type, + quantity=quantity + ) + else: + # For limit orders, price is required + assert limit_price is not None, "limit_price required for limit orders" + order = self.exchange.place_order( + symbol=symbol, + side='sell', # Short selling starts with a sell order + order_type=order_type, + quantity=quantity, + price=limit_price + ) + + if order: + # Create short position record + self.positions[symbol] = Position( + symbol=symbol, + side='SHORT', + quantity=quantity, + entry_price=current_price, + entry_time=datetime.now(), + order_id=order.get('orderId', 'unknown') + ) + + self.last_trade_time[symbol] = datetime.now() + self.daily_trades += 1 + + logger.info(f"SHORT order executed: {order}") + return True + else: + logger.error("Failed to place SHORT order") + return False + + except Exception as e: + logger.error(f"Error executing SHORT order: {e}") + return False + + def _close_short_position(self, symbol: str, confidence: float, current_price: float) -> bool: + """Close a short position by buying back""" + if symbol not in self.positions: + logger.warning(f"No position to close in {symbol}") + return False + + position = self.positions[symbol] + if position.side != 'SHORT': + logger.warning(f"Position in {symbol} is not SHORT, cannot close with BUY") + return False + + logger.info(f"Closing SHORT position: {position.quantity:.6f} {symbol} at ${current_price:.2f} " + f"(confidence: {confidence:.2f})") + + if self.simulation_mode: + logger.info(f"SIMULATION MODE ({self.trading_mode.upper()}) - Short close logged but not executed") + # Calculate P&L for short position + pnl = position.calculate_pnl(current_price) + + # Create trade record + trade_record = TradeRecord( + symbol=symbol, + side='SHORT', + quantity=position.quantity, + entry_price=position.entry_price, + exit_price=current_price, + entry_time=position.entry_time, + exit_time=datetime.now(), + pnl=pnl, + fees=0.0, + confidence=confidence + ) + + self.trade_history.append(trade_record) + self.daily_loss += max(0, -pnl) # Add to daily loss if negative + + # Remove position + del self.positions[symbol] + self.last_trade_time[symbol] = datetime.now() + self.daily_trades += 1 + + logger.info(f"SHORT position closed - P&L: ${pnl:.2f}") + return True + + try: + # Get order type from config + order_type = self.mexc_config.get('order_type', 'market').lower() + + # For limit orders, set price slightly above market for immediate execution + limit_price = None + if order_type == 'limit': + # Set buy price slightly above market to ensure immediate execution + limit_price = current_price * 1.001 # 0.1% above market + + # Place buy order to close short + if order_type == 'market': + order = self.exchange.place_order( + symbol=symbol, + side='buy', # Buy to close short position + order_type=order_type, + quantity=position.quantity + ) + else: + # For limit orders, price is required + assert limit_price is not None, "limit_price required for limit orders" + order = self.exchange.place_order( + symbol=symbol, + side='buy', # Buy to close short position + order_type=order_type, + quantity=position.quantity, + price=limit_price + ) + + if order: + # Calculate P&L + pnl = position.calculate_pnl(current_price) + fees = self._calculate_trading_fee(order, symbol, position.quantity, current_price) + + # Create trade record + trade_record = TradeRecord( + symbol=symbol, + side='SHORT', + quantity=position.quantity, + entry_price=position.entry_price, + exit_price=current_price, + entry_time=position.entry_time, + exit_time=datetime.now(), + pnl=pnl - fees, + fees=fees, + confidence=confidence + ) + + self.trade_history.append(trade_record) + self.daily_loss += max(0, -(pnl - fees)) # Add to daily loss if negative + + # Remove position + del self.positions[symbol] + self.last_trade_time[symbol] = datetime.now() + self.daily_trades += 1 + + logger.info(f"SHORT close order executed: {order}") + logger.info(f"SHORT position closed - P&L: ${pnl - fees:.2f}") + return True + else: + logger.error("Failed to place SHORT close order") + return False + + except Exception as e: + logger.error(f"Error closing SHORT position: {e}") + return False + def _calculate_position_size(self, confidence: float, current_price: float) -> float: """Calculate position size based on configuration and confidence""" max_value = self.mexc_config.get('max_position_value_usd', 1.0) @@ -859,7 +1079,7 @@ class TradingExecutor: logger.error(f"Error getting closed trades: {e}") return [] - def get_current_position(self, symbol: str = None) -> Optional[Dict[str, Any]]: + def get_current_position(self, symbol: Optional[str] = None) -> Optional[Dict[str, Any]]: """Get current position for a symbol or all positions Args: