Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IB Fix for hanging issue #643

Merged
merged 2 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions lumibot/brokers/interactive_brokers_rest.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import logging
from termcolor import colored
from lumibot.brokers import Broker
from lumibot.entities import Order, Asset, Position
from lumibot.data_sources import InteractiveBrokersRESTData
from ..brokers import Broker
from ..entities import Order, Asset, Position
from ..data_sources import InteractiveBrokersRESTData
import datetime
from decimal import Decimal
from math import gcd
import re
import ssl
import time
import json
import traceback
from lumibot.trading_builtins import PollingStream
from ..trading_builtins import PollingStream

TYPE_MAP = dict(
stock="STK",
Expand Down Expand Up @@ -1193,4 +1190,4 @@ def _get_broker_id_from_raw_orders(self, raw_orders):
for leg in o["leg"]:
if "orderId" in leg:
ids.append(str(leg["orderId"]))
return ids
return ids
100 changes: 51 additions & 49 deletions lumibot/data_sources/interactive_brokers_rest_data.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import logging
from termcolor import colored
from lumibot.entities import Asset, Bars
from ..entities import Asset, Bars

from .data_source import DataSource
import subprocess
import os
import time
import requests
import urllib3
from datetime import datetime, timedelta
from datetime import datetime
import pandas as pd

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
Expand Down Expand Up @@ -101,6 +101,8 @@ def start(self, ib_username, ib_password):
"-d",
"--name",
"lumibot-client-portal",
"--restart",
"always",
*env_args,
"-p",
f"{self.port}:{self.port}",
Expand Down Expand Up @@ -246,7 +248,7 @@ def get_account_balances(self):

return response

def handle_http_errors(self, response, description):
def handle_http_errors(self, response, silent, retries, description):
to_return = None
re_msg = None
is_error = False
Expand Down Expand Up @@ -288,29 +290,29 @@ def handle_http_errors(self, response, description):
status_code = 200
response_json = orders

if 'xcredserv comm failed during getEvents due to Connection refused':
if 'xcredserv comm failed during getEvents due to Connection refused' in error_message:
retrying = True
re_msg = f"Task {description} failed: The server is undergoing maintenance. Should fix itself soon"
re_msg = "The server is undergoing maintenance. Should fix itself soon"

elif 'Please query /accounts first' in error_message:
self.ping_iserver()
retrying = True
re_msg = f"Task {description} failed: Lumibot got Deauthenticated"
re_msg = "Lumibot got Deauthenticated"

elif "no bridge" in error_message.lower() or "not authenticated" in error_message.lower():
retrying = True
re_msg = f"Task {description} failed: Not Authenticated"
re_msg = "Not Authenticated"

elif 200 <= status_code < 300:
to_return = response_json
retrying = False

elif status_code == 429:
retrying = True
re_msg = f"Task {description} failed: You got rate limited"
re_msg = "You got rate limited"

elif status_code == 503:
re_msg = f"Task {description} failed: Internal server error. Should fix itself soon"
re_msg = "Internal server error. Should fix itself soon"
retrying = True

elif status_code == 500:
Expand All @@ -320,7 +322,7 @@ def handle_http_errors(self, response, description):

elif status_code == 410:
retrying = True
re_msg = f"Task {description} failed: The bridge blew up"
re_msg = "The bridge blew up"

elif 400 <= status_code < 500:
to_return = response_json
Expand All @@ -329,9 +331,27 @@ def handle_http_errors(self, response, description):

else:
retrying = False


return (retrying, re_msg, is_error, to_return)
if re_msg is not None:
if not silent and retries == 0:
logging.warning(colored(f"Task {description} failed: {re_msg}. Retrying...", "yellow"))
elif retries >= 20:
logging.info(colored(f"Task {description} failed: {re_msg}. Retrying...", "yellow"))
else:
logging.debug(colored(f"Task {description} failed: {re_msg}. Retrying...", "yellow"))

elif is_error:
if not silent and retries == 0:
logging.error(colored(f"Task {description} failed: {to_return}", "red"))
else:
logging.debug(colored(f"Task {description} failed: {to_return}", "red"))

if re_msg is not None:
time.sleep(1)

return (retrying, re_msg, is_error, to_return)

def get_from_endpoint(self, url, description="", silent=False, allow_fail=True):
to_return = None
retries = 0
Expand All @@ -340,25 +360,19 @@ 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, description)
retrying, re_msg, is_error, to_return = self.handle_http_errors(response, silent, retries, description)

if re_msg is not None:
if not silent and retries == 0:
logging.warning(colored(f'{re_msg}. Retrying...', "yellow"))

elif is_error:
if not silent and retries == 0:
logging.error(colored(f"Task {description} failed: {to_return}", "red"))
if re_msg is None and not is_error:
break

else:
allow_fail = True

retries+=1

except requests.exceptions.RequestException as e:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

category Error Handling severity potentially major

Use logging.exception for full stack trace.

Tell me more

In the get_from_endpoint, post_to_endpoint, and delete_to_endpoint methods, you're catching requests.exceptions.RequestException, which is good. However, you're not logging the full exception traceback. Consider using logging.exception(message) instead of logging.error(colored(message, "red")) to log the full stack trace. This will provide more detailed information for debugging purposes.

Chat with Korbit by mentioning @korbit-ai, and give a 👍 or 👎 to help Korbit improve your reviews.

message = f"Error: {description}. Exception: {e}"
if not silent:
logging.error(colored(message, "red"))
else:
logging.debug(colored(message), "red")
to_return = {"error": message}

return to_return
Expand All @@ -371,25 +385,19 @@ 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, description)
retrying, re_msg, is_error, to_return = self.handle_http_errors(response, silent, retries, description)

if re_msg is not None:
if not silent and retries == 0:
logging.warning(colored(f'{re_msg}. Retrying...', "yellow"))

elif is_error:
if not silent and retries == 0:
logging.error(colored(f"Task {description} failed: {to_return}", "red"))

else:
allow_fail = True

retries += 1
if re_msg is None and not is_error:
break

retries+=1

except requests.exceptions.RequestException as e:
message = f"Error: {description}. Exception: {e}"
if not silent:
logging.error(colored(message, "red"))
else:
logging.debug(colored(message), "red")
to_return = {"error": message}

return to_return
Expand All @@ -402,25 +410,19 @@ 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, description)
retrying, re_msg, is_error, to_return = self.handle_http_errors(response, silent, retries, description)

if re_msg is not None:
if not silent and retries == 0:
logging.warning(colored(f'{re_msg}. Retrying...', "yellow"))

elif is_error:
if not silent and retries == 0:
logging.error(colored(f"Task {description} failed: {to_return}", "red"))

else:
allow_fail = True

retries += 1
if re_msg is None and not is_error:
break

retries+=1

except requests.exceptions.RequestException as e:
message = f"Error: {description}. Exception: {e}"
if not silent:
logging.error(colored(message, "red"))
else:
logging.debug(colored(message), "red")
to_return = {"error": message}

return to_return
Expand Down Expand Up @@ -1069,4 +1071,4 @@ def get_quote(self, asset, quote=None, exchange=None):
else:
result["ask"] = None

return result
return result
Loading