diff --git a/lumibot/brokers/interactive_brokers_rest.py b/lumibot/brokers/interactive_brokers_rest.py index deea18f64..fe3c4771b 100644 --- a/lumibot/brokers/interactive_brokers_rest.py +++ b/lumibot/brokers/interactive_brokers_rest.py @@ -192,6 +192,7 @@ def _parse_broker_order(self, response, strategy_name, strategy_object=None): order.quantity = totalQuantity order.asset = Asset(symbol=response['ticker'], asset_type="multileg") order.side = response['side'] + order.identifier = response['orderId'] order.child_orders = [] @@ -205,6 +206,7 @@ def _parse_broker_order(self, response, strategy_name, strategy_object=None): response=response, quantity=float(ratio) * totalQuantity, conId=leg, + parent_identifier=order.identifier ) order.child_orders.append(child_order) @@ -224,7 +226,7 @@ def _parse_broker_order(self, response, strategy_name, strategy_object=None): order.update_raw(response) return order - def _parse_order_object(self, strategy_name, response, quantity, conId): + def _parse_order_object(self, strategy_name, response, quantity, conId, parent_identifier=None): if quantity < 0: side = "SELL" quantity = -quantity @@ -294,6 +296,9 @@ def _parse_order_object(self, strategy_name, response, quantity, conId): avg_fill_price=response["avgPrice"] if "avgPrice" in response else None ) + if parent_identifier is not None: + order.parent_identifier=parent_identifier + return order def _pull_broker_all_orders(self): @@ -705,8 +710,10 @@ def submit_orders( order = Order(orders[0].strategy) order.order_class = Order.OrderClass.MULTILEG - order.child_orders = orders order.identifier = response[0]["order_id"] + order.child_orders = orders + for child_order in order.child_orders: + child_order.parent_identifier = order.identifier self._unprocessed_orders.append(order) self.stream.dispatch(self.NEW_ORDER, order=order) diff --git a/lumibot/data_sources/interactive_brokers_rest_data.py b/lumibot/data_sources/interactive_brokers_rest_data.py index f6ba9a772..b6690540c 100644 --- a/lumibot/data_sources/interactive_brokers_rest_data.py +++ b/lumibot/data_sources/interactive_brokers_rest_data.py @@ -140,7 +140,7 @@ def suppress_warnings(self): url = f"{self.base_url}/iserver/questions/suppress" json = {"messageIds": ["o451", "o383", "o354", "o163"]} - self.post_to_endpoint(url, json=json, allow_fail=False) + self.post_to_endpoint(url, json=json, description="Suppressing server warnings", allow_fail=False) def fetch_account_id(self): if self.account_id is not None: @@ -246,7 +246,7 @@ def get_account_balances(self): return response - def handle_http_errors(self, response): + def handle_http_errors(self, response, description): to_return = None re_msg = None is_error = False @@ -279,7 +279,7 @@ def handle_http_errors(self, response): confirm_response = self.post_to_endpoint( confirm_url, {"confirmed": True}, - "Confirming order", + description="Confirming Order", silent=True, allow_fail=True ) @@ -290,7 +290,7 @@ def handle_http_errors(self, response): if "no bridge" in error_message.lower() or "not authenticated" in error_message.lower(): retrying = True - re_msg = "Not Authenticated" + re_msg = f"Task {description} failed: Not Authenticated" elif 200 <= status_code < 300: to_return = response_json @@ -298,14 +298,14 @@ def handle_http_errors(self, response): elif status_code == 429: retrying = True - re_msg = "You got rate limited" + re_msg = f"Task {description} failed: You got rate limited" elif status_code == 503: if any("Please query /accounts first" in str(value) for value in response_json.values()): self.ping_iserver() - re_msg = "Lumibot got Deauthenticated" + re_msg = f"Task {description} failed: Lumibot got Deauthenticated" else: - re_msg = "Internal server error, should fix itself soon" + re_msg = f"Task {description} failed: Internal server error, should fix itself soon" retrying = True @@ -316,7 +316,7 @@ def handle_http_errors(self, response): elif status_code == 410: retrying = True - re_msg = "The bridge blew up" + re_msg = f"Task {description} failed: The bridge blew up" elif 400 <= status_code < 500: to_return = response_json @@ -336,7 +336,7 @@ def get_from_endpoint(self, url, description="", silent=False, allow_fail=True): try: while retrying or not allow_fail: response = requests.get(url, verify=False) - retrying, re_msg, is_error, to_return = self.handle_http_errors(response) + retrying, re_msg, is_error, to_return = self.handle_http_errors(response, description) if re_msg is not None: if not silent and retries == 0: @@ -367,7 +367,7 @@ def post_to_endpoint(self, url, json: dict, description="", silent=False, allow_ try: while retrying or not allow_fail: response = requests.post(url, json=json, verify=False) - retrying, re_msg, is_error, to_return = self.handle_http_errors(response) + retrying, re_msg, is_error, to_return = self.handle_http_errors(response, description) if re_msg is not None: if not silent and retries == 0: @@ -398,7 +398,7 @@ def delete_to_endpoint(self, url, description="", silent=False, allow_fail=True) try: while retrying or not allow_fail: response = requests.delete(url, verify=False) - retrying, re_msg, is_error, to_return = self.handle_http_errors(response) + retrying, re_msg, is_error, to_return = self.handle_http_errors(response, description) if re_msg is not None: if not silent and retries == 0: @@ -463,7 +463,10 @@ def get_broker_all_orders(self): url, "Getting open orders", allow_fail=False ) - return [order for order in response['orders'] if order.get('totalSize', 0) != 0] + if 'orders' in response and isinstance(response['orders'], list): + return [order for order in response['orders'] if order.get('totalSize', 0) != 0] + + return [] def get_order_info(self, orderid): self.ping_iserver() @@ -480,7 +483,7 @@ def execute_order(self, order_data): self.ping_iserver() url = f"{self.base_url}/iserver/account/{self.account_id}/orders" - response = self.post_to_endpoint(url, order_data) + response = self.post_to_endpoint(url, order_data, description="Executing order") if isinstance(response, list) and "order_id" in response[0]: # success @@ -505,7 +508,7 @@ def delete_order(self, order): self.ping_iserver() orderId = order.identifier url = f"{self.base_url}/iserver/account/{self.account_id}/order/{orderId}" - status = self.delete_to_endpoint(url) + status = self.delete_to_endpoint(url, description=f"Deleting order {orderId}") if status: logging.info( colored(f"Order with ID {orderId} canceled successfully.", "green")