Skip to content

Commit

Permalink
Merge pull request #1429 from Drakkar-Software/dev
Browse files Browse the repository at this point in the history
Dev merge
  • Loading branch information
GuillaumeDSM authored Jan 27, 2025
2 parents 7e23494 + 3df16bf commit 25e6571
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 98 deletions.
17 changes: 9 additions & 8 deletions Trading/Exchange/binance/binance_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,15 @@ def get_adapter_class(self):

async def get_account_id(self, **kwargs: dict) -> str:
try:
if self.exchange_manager.is_future:
raw_binance_balance = await self.connector.client.fapiPrivateV3GetBalance()
# accountAlias = unique account code
# from https://binance-docs.github.io/apidocs/futures/en/#futures-account-balance-v3-user_data
return raw_binance_balance[0]["accountAlias"]
else:
raw_balance = await self.connector.client.fetch_balance()
return raw_balance[ccxt_constants.CCXT_INFO]["uid"]
with self.connector.error_describer():
if self.exchange_manager.is_future:
raw_binance_balance = await self.connector.client.fapiPrivateV3GetBalance()
# accountAlias = unique account code
# from https://binance-docs.github.io/apidocs/futures/en/#futures-account-balance-v3-user_data
return raw_binance_balance[0]["accountAlias"]
else:
raw_balance = await self.connector.client.fetch_balance()
return raw_balance[ccxt_constants.CCXT_INFO]["uid"]
except (KeyError, IndexError):
# should not happen
raise
Expand Down
5 changes: 3 additions & 2 deletions Trading/Exchange/bingx/bingx_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ def get_name(cls) -> str:
return 'bingx'

async def get_account_id(self, **kwargs: dict) -> str:
resp = await self.connector.client.accountV1PrivateGetUid()
return resp["data"]["uid"]
with self.connector.error_describer():
resp = await self.connector.client.accountV1PrivateGetUid()
return resp["data"]["uid"]

async def get_my_recent_trades(self, symbol=None, since=None, limit=None, **kwargs):
# On SPOT Bingx, account recent trades is available under fetch_closed_orders
Expand Down
62 changes: 32 additions & 30 deletions Trading/Exchange/coinbase/coinbase_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,37 +172,39 @@ def get_adapter_class(self):

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
portfolio_id = None
accounts = await self.connector.client.fetch_accounts()
# use portfolio id when possible to enable "coinbase subaccounts" which are called "portfolios"
# note: oldest portfolio portfolio id == user id (from previous v2PrivateGetUser) when using master account
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=}"
with self.connector.error_describer():
# warning might become deprecated
# https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-users
portfolio_id = None
accounts = await self.connector.client.fetch_accounts()
# use portfolio id when possible to enable "coinbase subaccounts" which are called "portfolios"
# note: oldest portfolio portfolio id == user id (from previous v2PrivateGetUser) when
# using master account
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=}"
)
sorted_portfolios = sorted(
[
account[ccxt_constants.CCXT_INFO]
for account in accounts
],
key=lambda account: account["created_at"],
)
sorted_portfolios = sorted(
[
account[ccxt_constants.CCXT_INFO]
for account in accounts
],
key=lambda account: account["created_at"],
)
portfolio_id = sorted_portfolios[0]['retail_portfolio_id']
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"Using the oldest portfolio id to bind to main account: {portfolio_id=}."
)
else:
portfolio_id = next(iter(portfolio_ids))
return portfolio_id
except ccxt.BaseError as err:
portfolio_id = sorted_portfolios[0]['retail_portfolio_id']
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"Using the oldest portfolio id to bind to main account: {portfolio_id=}."
)
else:
portfolio_id = next(iter(portfolio_ids))
return portfolio_id
except (ccxt.BaseError, octobot_trading.errors.OctoBotExchangeError) as err:
self.logger.exception(
err, True,
f"Error when fetching {self.get_name()} account id: {err} ({err.__class__.__name__}). "
Expand Down
5 changes: 3 additions & 2 deletions Trading/Exchange/hollaex/hollaex_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ def is_configurable(cls):
return True

async def get_account_id(self, **kwargs: dict) -> str:
user_info = await self.connector.client.private_get_user()
return user_info["id"]
with self.connector.error_describer():
user_info = await self.connector.client.private_get_user()
return user_info["id"]

async def get_symbol_prices(self, symbol, time_frame, limit: int = None, **kwargs: dict):
# ohlcv without limit is not supported, replaced by a default max limit
Expand Down
93 changes: 46 additions & 47 deletions Trading/Exchange/kucoin/kucoin_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import octobot_trading.exchanges.connectors.ccxt.ccxt_connector as ccxt_connector
import octobot_trading.exchanges.connectors.ccxt.enums as ccxt_enums
import octobot_trading.exchanges.connectors.ccxt.constants as ccxt_constants
import octobot_trading.exchanges.connectors.ccxt.ccxt_client_util as ccxt_client_util
import octobot_commons.constants as commons_constants
import octobot_trading.constants as constants
import octobot_trading.enums as trading_enums
Expand Down Expand Up @@ -175,56 +176,54 @@ async def get_account_id(self, **kwargs: dict) -> str:
# It is currently impossible to fetch subaccounts account id, use a constant value to identify it.
# updated: 21/05/2024
try:
account_id = None
subaccount_id = None
sub_accounts = await self.connector.client.private_get_sub_accounts()
accounts = sub_accounts.get("data", {}).get("items", {})
has_subaccounts = bool(accounts)
if has_subaccounts:
if len(accounts) == 1:
# only 1 account: use its id or name
account = accounts[0]
# try using subUserId if available
# 'ex subUserId: 65d41ea409407d000160cc17 subName: octobot1'
account_id = account.get("subUserId") or account["subName"]
else:
# more than 1 account: consider other accounts
for account in accounts:
if account["subUserId"]:
subaccount_id = account["subName"]
else:
# only subaccounts have a subUserId: if this condition is True, we are on the main account
account_id = account["subName"]
if account_id and self.exchange_manager.is_future:
account_id = octobot.community.to_community_exchange_internal_name(
account_id, commons_constants.CONFIG_EXCHANGE_FUTURE
with self.connector.error_describer():
account_id = None
subaccount_id = None
sub_accounts = await self.connector.client.private_get_sub_accounts()
accounts = sub_accounts.get("data", {}).get("items", {})
has_subaccounts = bool(accounts)
if has_subaccounts:
if len(accounts) == 1:
# only 1 account: use its id or name
account = accounts[0]
# try using subUserId if available
# 'ex subUserId: 65d41ea409407d000160cc17 subName: octobot1'
account_id = account.get("subUserId") or account["subName"]
else:
# more than 1 account: consider other accounts
for account in accounts:
if account["subUserId"]:
subaccount_id = account["subName"]
else:
# only subaccounts have a subUserId: if this condition is True, we are on the main account
account_id = account["subName"]
if account_id and self.exchange_manager.is_future:
account_id = octobot.community.to_community_exchange_internal_name(
account_id, commons_constants.CONFIG_EXCHANGE_FUTURE
)
if subaccount_id:
# there is at least a subaccount: ensure the current account is the main account as there is no way
# to know the id of the current account (only a list of existing accounts)
subaccount_api_key_details = await self.connector.client.private_get_sub_api_key(
{"subName": subaccount_id}
)
if subaccount_id:
# there is at least a subaccount: ensure the current account is the main account as there is no way
# to know the id of the current account (only a list of existing accounts)
subaccount_api_key_details = await self.connector.client.private_get_sub_api_key(
{"subName": subaccount_id}
)
if "data" not in subaccount_api_key_details or "msg" in subaccount_api_key_details:
# subaccounts can't fetch other accounts data, if this is False, we are on a subaccount
if "data" not in subaccount_api_key_details or "msg" in subaccount_api_key_details:
# subaccounts can't fetch other accounts data, if this is False, we are on a subaccount
self.logger.error(
f"kucoin api changed: it is now possible to call private_get_sub_accounts on subaccounts. "
f"kucoin get_account_id has to be updated. "
f"sub_accounts={sub_accounts} subaccount_api_key_details={subaccount_api_key_details}"
)
return constants.DEFAULT_ACCOUNT_ID
if has_subaccounts and account_id is None:
self.logger.error(
f"kucoin api changed: it is now possible to call private_get_sub_accounts on subaccounts. "
f"kucoin get_account_id has to be updated. "
f"sub_accounts={sub_accounts} subaccount_api_key_details={subaccount_api_key_details}"
f"kucoin api changed: can't fetch master account account_id. "
f"kucoin get_account_id has to be updated."
f"sub_accounts={sub_accounts}"
)
return constants.DEFAULT_ACCOUNT_ID
if has_subaccounts and account_id is None:
self.logger.error(
f"kucoin api changed: can't fetch master account account_id. "
f"kucoin get_account_id has to be updated."
f"sub_accounts={sub_accounts}"
)
account_id = constants.DEFAULT_ACCOUNT_ID
# we are on the master account
return account_id or constants.DEFAULT_ACCOUNT_ID
except ccxt.AuthenticationError:
# when api key is wrong
raise
account_id = constants.DEFAULT_ACCOUNT_ID
# we are on the master account
return account_id or constants.DEFAULT_ACCOUNT_ID
except ccxt.ExchangeError as err:
# ExchangeError('kucoin This user is not a master user')
if "not a master user" not in str(err):
Expand Down
3 changes: 2 additions & 1 deletion Trading/Exchange/okx/okx_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ def _fix_limit(self, limit: int) -> int:
async def get_account_id(self, **kwargs: dict) -> str:
accounts = await self.connector.client.fetch_accounts()
try:
return accounts[0]["id"]
with self.connector.error_describer():
return accounts[0]["id"]
except IndexError:
# should never happen as at least one account should be available
return None
Expand Down
13 changes: 11 additions & 2 deletions Trading/Mode/grid_trading_mode/grid_trading.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ async def _generate_staggered_orders(self, current_price, ignore_available_funds
highest_buy = current_price
lowest_sell = current_price
origin_created_buy_orders_count, origin_created_sell_orders_count = self._get_origin_orders_count(
sorted_orders, recently_closed_trades
recently_closed_trades, sorted_orders
)

min_max_total_order_price_delta = (
Expand Down Expand Up @@ -485,6 +485,12 @@ async def _generate_staggered_orders(self, current_price, ignore_available_funds
self.logger.info(
f"{len(missing_orders)} missing {self.symbol} orders on {self.exchange_name}: {missing_orders}"
)
else:
self.logger.info(
f"All {len(sorted_orders)} out of {self.buy_orders_count + self.sell_orders_count} {self.symbol} "
f"target orders are in place on {self.exchange_name}"
)

await self._handle_missed_mirror_orders_fills(recently_closed_trades, missing_orders, current_price)
try:
# apply state and (re)create missing orders
Expand Down Expand Up @@ -515,10 +521,13 @@ def _get_origin_orders_count(self, recent_trades, open_orders):
origin_created_buy_orders_count = self.buy_orders_count
origin_created_sell_orders_count = self.sell_orders_count
if recent_trades:
# in case all initial orders didn't get created, try to infer the original value from open orders and trades
buy_orders_count = len([order for order in open_orders if order.side is trading_enums.TradeOrderSide.BUY])
buy_trades_count = len([trade for trade in recent_trades if trade.side is trading_enums.TradeOrderSide.BUY])
origin_created_buy_orders_count = buy_orders_count + buy_trades_count
origin_created_sell_orders_count = len(open_orders) + len(recent_trades) - origin_created_buy_orders_count
origin_created_sell_orders_count = (
len(open_orders) + len(recent_trades) - origin_created_buy_orders_count
)
return origin_created_buy_orders_count, origin_created_sell_orders_count

def _get_grid_trades_or_orders(self, trades_or_orders):
Expand Down
Loading

0 comments on commit 25e6571

Please sign in to comment.