From c8a67f36a76dd2e45cebd25fda2d0cbed199e2ad Mon Sep 17 00:00:00 2001 From: Erick Cantu Paz Date: Wed, 23 Jul 2025 08:47:09 +0000 Subject: [PATCH 1/3] adding support for positions for multiple accounts --- ib_async/ib.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ib_async/ib.py b/ib_async/ib.py index 4f73160..6113682 100644 --- a/ib_async/ib.py +++ b/ib_async/ib.py @@ -2053,7 +2053,6 @@ async def connectAsync( # prepare initializing requests # name -> request reqs: dict[str, Awaitable[Any]] = {} - reqs["positions"] = self.reqPositionsAsync() if not readonly: if fetchFields & StartupFetch.ORDERS_OPEN: reqs["open orders"] = self.reqOpenOrdersAsync() @@ -2063,10 +2062,15 @@ async def connectAsync( reqs["completed orders"] = self.reqCompletedOrdersAsync(False) if account: + reqs["positions"] = self.reqPositionsAsync() + if fetchFields & StartupFetch.ACCOUNT_UPDATES: reqs["account updates"] = self.reqAccountUpdatesAsync(account) if len(accounts) <= self.MaxSyncedSubAccounts: + for acc in accounts: + reqs[f"positions for {acc}"] = self.reqPositionsMultiAsync(acc) + if fetchFields & StartupFetch.SUB_ACCOUNT_UPDATES: for acc in accounts: reqs[f"account updates for {acc}"] = ( @@ -2289,6 +2293,14 @@ def reqPositionsAsync(self) -> Awaitable[list[Position]]: self.client.reqPositions() return future + def reqPositionsMultiAsync( + self, account: str = "", modelCode: str = "" + ) -> Awaitable[None]: + reqId = self.client.getReqId() + future = self.wrapper.startReq(reqId) + self.client.reqPositionsMulti(reqId, account, modelCode) + return future + def reqContractDetailsAsync( self, contract: Contract ) -> Awaitable[list[ContractDetails]]: From 64e057538fb478c564c17ca0e302aba78bb1ca67 Mon Sep 17 00:00:00 2001 From: Erick Cantu Paz Date: Wed, 23 Jul 2025 17:22:17 +0000 Subject: [PATCH 2/3] fix getting positions when we have multiple accounts --- ib_async/ib.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/ib_async/ib.py b/ib_async/ib.py index 6113682..3619493 100644 --- a/ib_async/ib.py +++ b/ib_async/ib.py @@ -2053,6 +2053,8 @@ async def connectAsync( # prepare initializing requests # name -> request reqs: dict[str, Awaitable[Any]] = {} + reqs["positions"] = self.reqPositionsAsync() + if not readonly: if fetchFields & StartupFetch.ORDERS_OPEN: reqs["open orders"] = self.reqOpenOrdersAsync() @@ -2062,20 +2064,18 @@ async def connectAsync( reqs["completed orders"] = self.reqCompletedOrdersAsync(False) if account: - reqs["positions"] = self.reqPositionsAsync() - if fetchFields & StartupFetch.ACCOUNT_UPDATES: reqs["account updates"] = self.reqAccountUpdatesAsync(account) - if len(accounts) <= self.MaxSyncedSubAccounts: - for acc in accounts: - reqs[f"positions for {acc}"] = self.reqPositionsMultiAsync(acc) + # if len(accounts) <= self.MaxSyncedSubAccounts: + # # for acc in accounts: + # # reqs[f"positions for {acc}"] = self.reqPositionsMultiAsync(acc) - if fetchFields & StartupFetch.SUB_ACCOUNT_UPDATES: - for acc in accounts: - reqs[f"account updates for {acc}"] = ( - self.reqAccountUpdatesMultiAsync(acc) - ) + # if fetchFields & StartupFetch.SUB_ACCOUNT_UPDATES: + # for acc in accounts: + # reqs[f"account updates for {acc}"] = ( + # self.reqAccountUpdatesMultiAsync(acc) + # ) # run initializing requests concurrently and log if any times out tasks = [asyncio.wait_for(req, timeout) for req in reqs.values()] @@ -2087,6 +2087,20 @@ async def connectAsync( errors.append(msg) self._logger.error(msg) + # For FA accounts, serially subscribe to each account and wait for + # the 'accountDownloadEnd' signal to ensure all data is loaded. + if len(accounts) <= self.MaxSyncedSubAccounts: + for acc in accounts: + try: + await asyncio.wait_for( + self.reqAccountUpdatesAsync(acc), timeout + ) + except asyncio.TimeoutError: + msg = f"reqAccountUpdatesAsync for {acc} timed out" + errors.append(msg) + self._logger.error(msg) + self._logger.info("Finished fetching all portfolio data.") + # the request for executions must come after all orders are in if fetchFields & StartupFetch.EXECUTIONS: try: From 4d5c806c76c7ca965e582286129b0b4e1cec8450 Mon Sep 17 00:00:00 2001 From: Erick Cantu Paz Date: Thu, 24 Jul 2025 08:04:53 +0000 Subject: [PATCH 3/3] cleaned up comments related to subscribing to multiple accounts to get their portfolios --- ib_async/ib.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/ib_async/ib.py b/ib_async/ib.py index 3619493..d85fcc5 100644 --- a/ib_async/ib.py +++ b/ib_async/ib.py @@ -2067,16 +2067,6 @@ async def connectAsync( if fetchFields & StartupFetch.ACCOUNT_UPDATES: reqs["account updates"] = self.reqAccountUpdatesAsync(account) - # if len(accounts) <= self.MaxSyncedSubAccounts: - # # for acc in accounts: - # # reqs[f"positions for {acc}"] = self.reqPositionsMultiAsync(acc) - - # if fetchFields & StartupFetch.SUB_ACCOUNT_UPDATES: - # for acc in accounts: - # reqs[f"account updates for {acc}"] = ( - # self.reqAccountUpdatesMultiAsync(acc) - # ) - # run initializing requests concurrently and log if any times out tasks = [asyncio.wait_for(req, timeout) for req in reqs.values()] errors = [] @@ -2087,8 +2077,11 @@ async def connectAsync( errors.append(msg) self._logger.error(msg) - # For FA accounts, serially subscribe to each account and wait for - # the 'accountDownloadEnd' signal to ensure all data is loaded. + # To get portfolios for multiple accounts we have to subscribe to each + # account serially to ensure all data is loaded. We have to do it serially + # because IB API sends back a generic accountDownloadEnd signal when it + # finishes sending the data for the first account, so we cannot subscribe + # to multiple accounts at once. if len(accounts) <= self.MaxSyncedSubAccounts: for acc in accounts: try: