Skip to content

Commit

Permalink
refactored IB client tracker
Browse files Browse the repository at this point in the history
  • Loading branch information
robcarver17 committed Dec 7, 2020
1 parent 24f3df8 commit a37b763
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 158 deletions.
120 changes: 7 additions & 113 deletions sysbrokers/IB/ib_client_id.py
Original file line number Diff line number Diff line change
@@ -1,131 +1,25 @@
from sysbrokers.IB.ib_connection import ib_defaults
from syscore.objects import arg_not_supplied
from sysdata.base_data import baseData

from syslogdiag.log import logtoscreen

from sysbrokers.IB.ib_connection import ib_defaults
from sysdata.production.broker_client_id import brokerClientIdData

class ibClientIdData(baseData):
class ibBrokerClientIdData(brokerClientIdData):
"""
Read and write data class to get next used client id
"""

def __init__(
self,
idoffset=arg_not_supplied,
log=logtoscreen("mongoIDTracker"),
idoffset = arg_not_supplied,
log=logtoscreen("brokerClientIdTracker"),
):

super().__init__(log=log)

if idoffset is arg_not_supplied:
_notused_ipaddress, _notused_port, idoffset = ib_defaults()

self._idoffset = idoffset

@property
def idoffset(self):
return self._idoffset
super().__init__(log=log, idoffset=idoffset)

def __repr__(self):
return "Tracking IB client IDs"

def return_valid_client_id(self, clientid_to_try: int=arg_not_supplied) -> int:
"""
If clientid_to_try is None, return the next free ID
If clientid_to_try is being used, return the next free ID, otherwise allow that to be used
:param clientid_to_try: int or None
:return: int
"""
if clientid_to_try is arg_not_supplied:
clientid_to_use = self._get_and_lock_next_clientid()
return clientid_to_use

if self._is_clientid_used(clientid_to_try):
# being used, get another one
# this will also lock it
clientid_to_use = self._get_and_lock_next_clientid()
return clientid_to_use

self._lock_clientid(clientid_to_try) # lock

return clientid_to_try


def _is_clientid_used(self, clientid):
"""
Checks if a clientis is in use
:param clientid: int
:return: bool
"""
current_ids = self._get_list_of_clientids()
if clientid in current_ids:
return True
else:
return False


def _get_and_lock_next_clientid(self) -> int:
"""
Returns a client id which will be locked so no other use can use it
The clientid in question is the lowest available unused value
:return: clientid
"""

current_list_of_ids = self._get_list_of_clientids()
next_id = get_next_id_from_current_list(current_list_of_ids, id_offset=self.idoffset)

# lock
self._lock_clientid(next_id)

return next_id

def _get_list_of_clientids(self) -> list:
raise NotImplementedError("Need to implement in child class")

def _lock_clientid(self, next_id):
raise NotImplementedError("Need to implement in child class")

def clear_all_clientids(self):
"""
Clear all the client ids
Should be done on machine startup
:return:
"""
client_id_list = self._get_list_of_clientids()
self.log.critical("Clearing all IB client IDs: if anything still running will probably break!")
for client_id in client_id_list:
self.release_clientid(client_id)

def release_clientid(self, clientid: int):
"""
Delete a client id lock
:param clientid:
:return: None
"""

raise NotImplementedError("Need to implement in child class")


def get_next_id_from_current_list(current_list_of_ids: list, id_offset: int = 0) -> int:
if len(current_list_of_ids) == 0:
# no IDS in use
return id_offset

full_set_of_available_ids = set(
range(id_offset, max(current_list_of_ids) + 1)
)

next_id = get_next_id_from_current_list_and_full_set(current_list_of_ids, full_set_of_available_ids)

return next_id


def get_next_id_from_current_list_and_full_set(current_list_of_ids: list, full_set_of_available_ids: set) -> int:

unused_values = full_set_of_available_ids - set(current_list_of_ids)
if len(unused_values)==0:
# no gaps, return the higest number plus 1
return max(current_list_of_ids) + 1
else:
# there is a gap, use the lowest numbered one
return min(unused_values)
24 changes: 9 additions & 15 deletions sysbrokers/IB/ib_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ib_insync import IB

from sysbrokers.IB.ib_client import ibClient
from sysbrokers.IB.ib_client_id import mongoIBclientIdData
from sysdata.mongodb.mongo_IB_client_id import mongoIbClientIdData
from sysbrokers.IB.ib_server import ibServer
from syscore.genutils import get_safe_from_dict
from syscore.objects import arg_not_supplied, missing_data
Expand Down Expand Up @@ -77,22 +77,21 @@ class connectionIB(ibClient, ibServer):

def __init__(
self,
client=None,
client_id: int,
ipaddress=None,
port=None,
log=logtoscreen("connectionIB"),
mongo_db=arg_not_supplied,
log=logtoscreen("connectionIB")
):
"""
:param client: client id. If not passed then will get from database specified by mongo_db
:param client_id: client id
:param ipaddress: IP address of machine running IB Gateway or TWS. If not passed then will get from private config file, or defaults
:param port: Port listened to by IB Gateway or TWS
:param log: logging object
:param mongo_db: mongoDB connection
"""

# resolve defaults
ipaddress, port, idoffset = ib_defaults(ipaddress=ipaddress, port=port)
ipaddress, port, __ = ib_defaults(ipaddress=ipaddress, port=port)

# The client id is pulled from a mongo database
# If for example you want to use a different database you could do something like:
Expand All @@ -101,15 +100,10 @@ def __init__(

# You can pass a client id yourself, or let IB find one

self.db_id_tracker = mongoIBclientIdData(
mongo_db=mongo_db, log=log, idoffset=idoffset
)
client = self.db_id_tracker.return_valid_client_id(client)

# If you copy for another broker include this line
log.label(broker="IB", clientid=client)
log.label(broker="IB", clientid=client_id)
self._ib_connection_config = dict(
ipaddress=ipaddress, port=port, client=client)
ipaddress=ipaddress, port=port, client=client_id)

# if you copy for another broker, don't forget the logs
ibServer.__init__(self, log=log)
Expand All @@ -120,9 +114,9 @@ def __init__(
account = get_broker_account()
if account is missing_data:
self.log.error("Broker account ID not found in private config - may cause issues")
ib.connect(ipaddress, port, clientId=client, account=account)
ib.connect(ipaddress, port, clientId=client_id, account=account)
else:
ib.connect(ipaddress, port, clientId=client, account=account)
ib.connect(ipaddress, port, clientId=client_id, account=account)

# Attempt to fix connection bug
time.sleep(5)
Expand Down
7 changes: 6 additions & 1 deletion sysdata/data_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from sysdata.mongodb.mongo_log import logToMongod
from syslogdiag.log import logger

from sysdata.mongodb.mongo_IB_client_id import mongoIbBrokerClientIdData

class dataBlob(object):
def __init__(
Expand Down Expand Up @@ -235,7 +236,11 @@ def close(self):
def ib_conn(self):
ib_conn = getattr(self, "_ib_conn", arg_not_supplied)
if ib_conn is arg_not_supplied:
ib_conn = connectionIB(log=self.log, mongo_db=self.mongo_db)

## default to tracking ID through mongo change if required
self.add_class_object(mongoIbBrokerClientIdData)
client_id = self.db_ib_broker_client.return_valid_client_id()
ib_conn = connectionIB(client_id, log=self.log)
self._ib_conn = ib_conn

return ib_conn
Expand Down
30 changes: 3 additions & 27 deletions sysdata/mongodb/mongo_IB_client_id.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@

from sysbrokers.IB.ib_client_id import ibClientIdData
from sysbrokers.IB.ib_client_id import ibBrokerClientIdData
from syscore.objects import arg_not_supplied
from sysdata.mongodb.mongo_generic import mongoData
from syslogdiag.log import logtoscreen

IB_CLIENT_COLLECTION = "IBClientTracker"
IB_ID_REF = 'client id'

class mongoIbClientIdData(ibClientIdData):
class mongoIbBrokerClientIdData(ibBrokerClientIdData):
"""
Read and write data class to get next used client id
"""
Expand All @@ -27,7 +27,7 @@ def mongo_data(self):
return self._mongo_data

def _repr__(self):
return "Tracking IB client IDs, mongodb %" % (str(self.mongo_data))
return "Tracking IB client IDs, mongodb %s" % (str(self.mongo_data))

def _get_list_of_clientids(self) -> list:
return self.mongo_data.get_list_of_keys()
Expand All @@ -47,27 +47,3 @@ def release_clientid(self, clientid: int):
self.mongo_data.delete_data_without_any_warning(clientid)
self.log.msg("Released IB client ID %d" % clientid)


def get_next_id_from_current_list(current_list_of_ids: list, id_offset: int = 0) -> int:
if len(current_list_of_ids) == 0:
# no IDS in use
return id_offset

full_set_of_available_ids = set(
range(id_offset, max(current_list_of_ids) + 1)
)

next_id = get_next_id_from_current_list_and_full_set(current_list_of_ids, full_set_of_available_ids)

return next_id


def get_next_id_from_current_list_and_full_set(current_list_of_ids: list, full_set_of_available_ids: set) -> int:

unused_values = full_set_of_available_ids - set(current_list_of_ids)
if len(unused_values)==0:
# no gaps, return the higest number plus 1
return max(current_list_of_ids) + 1
else:
# there is a gap, use the lowest numbered one
return min(unused_values)
Loading

0 comments on commit a37b763

Please sign in to comment.