diff --git a/src/execution/manager.py b/src/execution/manager.py index 9fd848e..b806016 100644 --- a/src/execution/manager.py +++ b/src/execution/manager.py @@ -53,6 +53,17 @@ class ExecutionManager: logger.error(f"Failed to place entry order: {e}") return False + def _is_ticker_in_portfolio(self, ticker: str) -> bool: + """Helper to check if a ticker currently has an open position.""" + try: + positions = self.client.get_all_open_positions() + for pos in positions: + if pos.get('ticker') == ticker: + return True + except Exception as e: + logger.error(f"Error checking portfolio: {e}") + return False + def monitor_and_bracket(self, params: Dict[str, Any]): """Polls the entry order and places SL/TP once filled.""" if not self.current_order_id: @@ -87,6 +98,15 @@ class ExecutionManager: logger.warning(f"Entry order was {status}. Aborting.") return False except Exception as e: + # Trading212 404s if an order is no longer active (filled or cancelled) + if "404" in str(e): + if self._is_ticker_in_portfolio(ticker): + self.is_in_position = True + logger.info(f"Order {self.current_order_id} disappeared but position detected. Assuming filled.") + break + else: + logger.warning(f"Order {self.current_order_id} disappeared and no position found. Assuming cancelled/rejected.") + return False logger.error(f"Error checking order status: {e}") time.sleep(10) # Poll every 10 seconds @@ -118,20 +138,41 @@ class ExecutionManager: if not self.is_in_position: return False, "", 0.0 + ticker = self.params.get('ticker') + try: if self.tp_order_id: - tp_info = self.client.get_order_status(self.tp_order_id) - if tp_info.get('status') == "FILLED": - fill_price = tp_info.get('filledPrice', tp_info.get('limitPrice', self.params.get('target_price'))) - self.is_in_position = False - return True, "TP Hit", float(fill_price) + try: + tp_info = self.client.get_order_status(self.tp_order_id) + if tp_info.get('status') == "FILLED": + fill_price = tp_info.get('filledPrice', tp_info.get('limitPrice', self.params.get('target_price'))) + self.is_in_position = False + return True, "TP Hit", float(fill_price) + except Exception as e: + if "404" in str(e): + if not self._is_ticker_in_portfolio(ticker): + self.is_in_position = False + logger.info(f"TP order {self.tp_order_id} disappeared and position closed. Assuming TP hit.") + return True, "TP Hit", float(self.params.get('target_price')) + else: + raise e if self.sl_order_id: - sl_info = self.client.get_order_status(self.sl_order_id) - if sl_info.get('status') == "FILLED": - fill_price = sl_info.get('filledPrice', sl_info.get('stopPrice', self.params.get('stop_loss'))) - self.is_in_position = False - return True, "SL Hit", float(fill_price) + try: + sl_info = self.client.get_order_status(self.sl_order_id) + if sl_info.get('status') == "FILLED": + fill_price = sl_info.get('filledPrice', sl_info.get('stopPrice', self.params.get('stop_loss'))) + self.is_in_position = False + return True, "SL Hit", float(fill_price) + except Exception as e: + if "404" in str(e): + if not self._is_ticker_in_portfolio(ticker): + self.is_in_position = False + logger.info(f"SL order {self.sl_order_id} disappeared and position closed. Assuming SL hit.") + return True, "SL Hit", float(self.params.get('stop_loss')) + else: + raise e + except Exception as e: logger.error(f"Error checking exit status: {e}")