From 35b0ce5532afb8ea935cb6c8eada4987c7bb19e0 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin Date: Thu, 21 Nov 2024 11:25:57 +0100 Subject: [PATCH] [Coinbase] support portfolio id --- .../Exchange/coinbase/coinbase_exchange.py | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/Trading/Exchange/coinbase/coinbase_exchange.py b/Trading/Exchange/coinbase/coinbase_exchange.py index 0a0f379e9..4b24b6fb3 100644 --- a/Trading/Exchange/coinbase/coinbase_exchange.py +++ b/Trading/Exchange/coinbase/coinbase_exchange.py @@ -137,8 +137,37 @@ async def get_account_id(self, **kwargs: dict) -> str: try: # warning might become deprecated # https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-users - user_data = await self.connector.client.v2PrivateGetUser() - return user_data["data"]["id"] + portfolio_id = None + try: + # use portfolio id when possible to enable "coinbase subaccounts" which are called "portfolios" + # note: portfolio id == user id (from v2PrivateGetUser) when using master account + accounts = await self.connector.client.fetch_accounts() + portfolio_ids = set(account[ccxt_constants.CCXT_INFO]['retail_portfolio_id'] for account in accounts) + if len(portfolio_ids) != 1: + is_up_to_date_key = self._is_up_to_date_api_key() + if is_up_to_date_key: + self.logger.error( + f"Unexpected: failed to identify Coinbase portfolio id on up to date API keys: " + f"{portfolio_ids=}" + ) + self.logger.info( + f"{len(portfolio_ids)} portfolio found on Coinbase account. " + f"This can happen with non up-to-date API keys ({is_up_to_date_key=}). " + f"Falling back to v2PrivateGetUser() to get the current account id." + ) + else: + portfolio_id = next(iter(portfolio_ids)) + except (IndexError, KeyError): + pass + if portfolio_id is None: + # fallback to user id + user_data = await self.connector.client.v2PrivateGetUser() + portfolio_id = user_data["data"]["id"] + self.logger.warning( + f"No Coinbase portfolio id can be selected from fetch_accounts(), used v2PrivateGetUser() " + f"to identify user id and use it as portfolio id: {portfolio_id}" + ) + return portfolio_id except ccxt.BaseError as err: self.logger.exception( err, True, @@ -149,6 +178,13 @@ async def get_account_id(self, **kwargs: dict) -> str: ) return trading_constants.DEFAULT_ACCOUNT_ID + def _is_up_to_date_api_key(self) -> bool: + return ( + self.connector.client.apiKey.find('organizations/') >= 0 or + self.connector.client.apiKey.startswith('-----BEGIN') + ) + + @_coinbase_retrier async def get_symbol_prices(self, symbol: str, time_frame: commons_enums.TimeFrames, limit: int = None, **kwargs: dict) -> typing.Optional[list]: