From ce54c1d75a9fd935b094fa06d8fbe1e5c96f36a5 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin Date: Wed, 20 Mar 2024 18:23:57 +0100 Subject: [PATCH 1/6] [Index] add index requirements --- octobot_trading/api/__init__.py | 2 ++ octobot_trading/api/modes.py | 4 ++++ octobot_trading/modes/channel/abstract_mode_consumer.py | 3 +++ 3 files changed, 9 insertions(+) diff --git a/octobot_trading/api/__init__.py b/octobot_trading/api/__init__.py index e9104a732..b93f85aba 100644 --- a/octobot_trading/api/__init__.py +++ b/octobot_trading/api/__init__.py @@ -126,6 +126,7 @@ from octobot_trading.api.modes import ( get_trading_modes, get_trading_mode_symbol, + is_trading_mode_symbol_wildcard, get_trading_mode_followed_strategy_signals_identifier, get_trading_mode_current_state, get_activated_trading_mode, @@ -301,6 +302,7 @@ "stop_exchange", "get_trading_modes", "get_trading_mode_symbol", + "is_trading_mode_symbol_wildcard", "get_trading_mode_followed_strategy_signals_identifier", "get_trading_mode_current_state", "get_activated_trading_mode", diff --git a/octobot_trading/api/modes.py b/octobot_trading/api/modes.py index e6d92d581..4f367d913 100644 --- a/octobot_trading/api/modes.py +++ b/octobot_trading/api/modes.py @@ -26,6 +26,10 @@ def get_trading_mode_symbol(trading_mode) -> list: return trading_mode.symbol +def is_trading_mode_symbol_wildcard(trading_mode) -> bool: + return trading_mode.get_is_symbol_wildcard() + + def get_trading_mode_followed_strategy_signals_identifier(trading_mode) -> str: if trading_mode.is_following_trading_signals(): return trading_mode.trading_config.get(commons_constants.CONFIG_TRADING_SIGNALS_STRATEGY, "") diff --git a/octobot_trading/modes/channel/abstract_mode_consumer.py b/octobot_trading/modes/channel/abstract_mode_consumer.py index 31d0ee755..c1e823401 100644 --- a/octobot_trading/modes/channel/abstract_mode_consumer.py +++ b/octobot_trading/modes/channel/abstract_mode_consumer.py @@ -135,6 +135,9 @@ async def create_order_if_possible(self, symbol, final_note, state, **kwargs) -> # Can be overwritten async def can_create_order(self, symbol, state): + if symbol is None: + # can't check + return True currency, market = symbol_util.parse_symbol(symbol).base_and_quote() portfolio = self.exchange_manager.exchange_personal_data.portfolio_manager.portfolio From e33f76bd4dc1fcad0cf62180ab9b16218dff7f84 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin Date: Thu, 21 Mar 2024 22:52:44 +0100 Subject: [PATCH 2/6] [TradingModes] add include_assets_in_open_orders in get_holdings_ratio --- .../modes/channel/abstract_mode_consumer.py | 23 ++++++- tests/modes/test_abstract_mode_consumer.py | 68 +++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/octobot_trading/modes/channel/abstract_mode_consumer.py b/octobot_trading/modes/channel/abstract_mode_consumer.py index c1e823401..17e78dc4f 100644 --- a/octobot_trading/modes/channel/abstract_mode_consumer.py +++ b/octobot_trading/modes/channel/abstract_mode_consumer.py @@ -194,9 +194,26 @@ async def can_create_order(self, symbol, state): self.logger.debug("can_create_order: return False") return False - def get_holdings_ratio(self, currency): - return self.exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder \ - .get_currency_holding_ratio(currency) + def get_holdings_ratio(self, currency, include_assets_in_open_orders=False): + portfolio_manager = self.exchange_manager.exchange_personal_data.portfolio_manager + if include_assets_in_open_orders: + total_holdings_value = portfolio_manager.portfolio_value_holder.portfolio_current_value + if not total_holdings_value: + return constants.ZERO + assets_in_open_orders = constants.ZERO + for order in self.exchange_manager.exchange_personal_data.orders_manager.get_open_orders(): + symbol = symbol_util.parse_symbol(order.symbol) + if order.side is enums.TradeOrderSide.BUY and symbol.base == currency: + assets_in_open_orders += order.origin_quantity + elif order.side is enums.TradeOrderSide.SELL and symbol.quote == currency: + assets_in_open_orders += order.total_cost + current_holdings_value = portfolio_manager.portfolio_value_holder.value_converter.evaluate_value( + currency, + portfolio_manager.portfolio.get_currency_portfolio(currency).total + assets_in_open_orders + ) + return current_holdings_value / total_holdings_value + # only take actual holdings into account + return portfolio_manager.portfolio_value_holder .get_currency_holding_ratio(currency) def get_number_of_traded_assets(self): return len(self.exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder diff --git a/tests/modes/test_abstract_mode_consumer.py b/tests/modes/test_abstract_mode_consumer.py index 2517c785e..697415a59 100644 --- a/tests/modes/test_abstract_mode_consumer.py +++ b/tests/modes/test_abstract_mode_consumer.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. import decimal +import mock import pytest import octobot_commons.constants as commons_constants @@ -24,6 +25,7 @@ from octobot_trading.enums import EvaluatorStates import octobot_trading.constants as constants import octobot_trading.errors as errors +import octobot_trading.enums as enums from octobot_trading.exchanges.exchange_manager import ExchangeManager from octobot_trading.modes import AbstractTradingMode import octobot_trading.personal_data.portfolios.assets as portfolio_assets @@ -147,6 +149,8 @@ async def test_get_holdings_ratio(): portfolio_assets.SpotAsset(name="USDT", available=decimal.Decimal("1000"), total=decimal.Decimal("1000")) assert consumer.get_holdings_ratio("BTC") == decimal.Decimal('0.9090909090909090909090909091') + assert consumer.get_holdings_ratio("BTC", include_assets_in_open_orders=False) \ + == decimal.Decimal('0.9090909090909090909090909091') assert consumer.get_holdings_ratio("USDT") == decimal.Decimal('0.09090909090909090909090909091') exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio.pop("USDT") @@ -169,6 +173,70 @@ async def test_get_holdings_ratio(): assert consumer.get_holdings_ratio("XYZ") == constants.ZERO +async def test_get_holdings_ratio_with_include_assets_in_open_orders(): + exchange_manager, symbol, consumer = await _get_tools() + exchange_manager.client_symbols = [symbol] + exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ + value_converter.last_prices_by_trading_pair[symbol] = decimal.Decimal("1000") + exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ + value_converter.last_prices_by_trading_pair["ETH/USDT"] = decimal.Decimal("100") + exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ + portfolio_current_value = decimal.Decimal("11") + exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio = {} + exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio["BTC"] = \ + portfolio_assets.SpotAsset(name="BTC", available=decimal.Decimal("10"), total=decimal.Decimal("10")) + exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio["USDT"] = \ + portfolio_assets.SpotAsset(name="USDT", available=decimal.Decimal("1000"), total=decimal.Decimal("1000")) + + assert consumer.get_holdings_ratio("BTC", include_assets_in_open_orders=False) \ + == decimal.Decimal('0.9090909090909090909090909091') + assert consumer.get_holdings_ratio("USDT", include_assets_in_open_orders=False) \ + == decimal.Decimal('0.09090909090909090909090909091') + + # no open order + assert consumer.get_holdings_ratio("BTC", include_assets_in_open_orders=True) \ + == decimal.Decimal('0.9090909090909090909090909091') + assert consumer.get_holdings_ratio("USDT", include_assets_in_open_orders=True) \ + == decimal.Decimal('0.09090909090909090909090909091') + + order_1 = mock.Mock( + order_id="1", symbol="BTC/USDT", origin_quantity=decimal.Decimal("1"), total_cost=decimal.Decimal('1000'), + status=enums.OrderStatus.OPEN, side=enums.TradeOrderSide.BUY + ) + order_2 = mock.Mock( + order_id="2", symbol="ETH/USDT", origin_quantity=decimal.Decimal("2"), total_cost=decimal.Decimal('100'), + status=enums.OrderStatus.OPEN, side=enums.TradeOrderSide.BUY + ) + order_3 = mock.Mock( + order_id="3", symbol="XRP/ETH", origin_quantity=decimal.Decimal("100"), total_cost=decimal.Decimal('0.001'), + status=enums.OrderStatus.OPEN, side=enums.TradeOrderSide.SELL + ) + # open orders + await exchange_manager.exchange_personal_data.orders_manager.upsert_order_instance(order_1) + await exchange_manager.exchange_personal_data.orders_manager.upsert_order_instance(order_2) + assert consumer.get_holdings_ratio("BTC", include_assets_in_open_orders=False) \ + == decimal.Decimal('0.9090909090909090909090909091') + assert consumer.get_holdings_ratio("ETH", include_assets_in_open_orders=False) \ + == decimal.Decimal('0') + assert consumer.get_holdings_ratio("USDT", include_assets_in_open_orders=False) \ + == decimal.Decimal('0.09090909090909090909090909091') + + assert consumer.get_holdings_ratio("BTC", include_assets_in_open_orders=True) \ + == decimal.Decimal('1') + assert consumer.get_holdings_ratio("ETH", include_assets_in_open_orders=True) \ + == decimal.Decimal('0.01818181818181818181818181818') + assert consumer.get_holdings_ratio("USDT", include_assets_in_open_orders=True) \ + == decimal.Decimal('0.09090909090909090909090909091') + + await exchange_manager.exchange_personal_data.orders_manager.upsert_order_instance(order_3) + assert consumer.get_holdings_ratio("BTC", include_assets_in_open_orders=True) \ + == decimal.Decimal('1') + assert consumer.get_holdings_ratio("ETH", include_assets_in_open_orders=True) \ + == decimal.Decimal('0.01819090909090909090909090909') + assert consumer.get_holdings_ratio("USDT", include_assets_in_open_orders=True) \ + == decimal.Decimal('0.09090909090909090909090909091') + + async def test_get_number_of_traded_assets(): exchange_manager, symbol, consumer = await _get_tools() exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ From f46398d8bf9d0b7e102fa790dcc0993e25e7dcf2 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin Date: Thu, 21 Mar 2024 22:53:07 +0100 Subject: [PATCH 3/6] [Profitability] add PROFITABILITY init event --- octobot_trading/constants.py | 1 + .../modes/channel/abstract_mode_producer.py | 10 ++++++++++ .../portfolios/portfolio_profitability.py | 13 +++++++++++++ 3 files changed, 24 insertions(+) diff --git a/octobot_trading/constants.py b/octobot_trading/constants.py index 9efca2c43..00abcca65 100644 --- a/octobot_trading/constants.py +++ b/octobot_trading/constants.py @@ -195,6 +195,7 @@ DEFAULT_INITIALIZATION_EVENT_TOPICS = [ commons_enums.InitializationEventExchangeTopics.BALANCE, + commons_enums.InitializationEventExchangeTopics.PROFITABILITY, commons_enums.InitializationEventExchangeTopics.ORDERS, commons_enums.InitializationEventExchangeTopics.TRADES, commons_enums.InitializationEventExchangeTopics.CANDLES, diff --git a/octobot_trading/modes/channel/abstract_mode_producer.py b/octobot_trading/modes/channel/abstract_mode_producer.py index 09e795c89..7d6785762 100644 --- a/octobot_trading/modes/channel/abstract_mode_producer.py +++ b/octobot_trading/modes/channel/abstract_mode_producer.py @@ -508,6 +508,16 @@ async def _apply_exchange_side_config(self, context): common_enums.InitializationEventExchangeTopics.CONTRACTS.value) await script_keywords.set_leverage(context, await script_keywords.user_select_leverage(context)) + async def _wait_for_symbol_prices_and_profitability_init(self, timeout) -> bool: + try: + await util.wait_for_topic_init(self.exchange_manager, timeout, + common_enums.InitializationEventExchangeTopics.PRICE.value) + await util.wait_for_topic_init(self.exchange_manager, timeout, + common_enums.InitializationEventExchangeTopics.PROFITABILITY.value) + except (asyncio.TimeoutError, concurrent.futures.TimeoutError): + self.logger.error(f"Symbol price initialization took more than {timeout} seconds") + return False + @classmethod def producer_exchange_wide_lock(cls, exchange_manager) -> asyncio_tools.RLock(): try: diff --git a/octobot_trading/personal_data/portfolios/portfolio_profitability.py b/octobot_trading/personal_data/portfolios/portfolio_profitability.py index 69a865f4b..68f2f3574 100644 --- a/octobot_trading/personal_data/portfolios/portfolio_profitability.py +++ b/octobot_trading/personal_data/portfolios/portfolio_profitability.py @@ -15,6 +15,8 @@ # License along with this library. import octobot_commons.logging as logging import octobot_commons.symbols as symbol_util +import octobot_commons.tree as commons_tree +import octobot_commons.enums as commons_enums import octobot_trading.util as util import octobot_trading.constants as constants @@ -66,14 +68,25 @@ def update_profitability(self, force_recompute_origin_portfolio=False): """ self._reset_before_profitability_calculation() try: + set_init_event = self.portfolio_manager.portfolio_value_holder.portfolio_current_value == constants.ZERO self.portfolio_manager.handle_profitability_recalculation(force_recompute_origin_portfolio) self._update_profitability_calculation() + if set_init_event: + self._set_initialized_event() return self.profitability_diff != constants.ZERO except KeyError as missing_data_exception: self.logger.warning(f"Missing {missing_data_exception} ticker data to calculate profitability") except Exception as missing_data_exception: self.logger.exception(missing_data_exception, True, str(missing_data_exception)) + def _set_initialized_event(self): + commons_tree.EventProvider.instance().trigger_event( + self.portfolio_manager.exchange_manager.bot_id, commons_tree.get_exchange_path( + self.portfolio_manager.exchange_manager.exchange_name, + commons_enums.InitializationEventExchangeTopics.PROFITABILITY.value + ) + ) + def _reset_before_profitability_calculation(self): """ Prepare profitability calculation From 2476dbdd125fb4f8230e30059803679115eb3815 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin Date: Sat, 23 Mar 2024 09:55:20 +0100 Subject: [PATCH 4/6] [TradingMode] handle None symbol error --- .../modes/channel/abstract_mode_consumer.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/octobot_trading/modes/channel/abstract_mode_consumer.py b/octobot_trading/modes/channel/abstract_mode_consumer.py index 17e78dc4f..970b8f2c1 100644 --- a/octobot_trading/modes/channel/abstract_mode_consumer.py +++ b/octobot_trading/modes/channel/abstract_mode_consumer.py @@ -69,17 +69,24 @@ async def internal_callback(self, trading_mode_name, cryptocurrency, symbol, tim f"the order being refused by the exchange.") def get_minimal_funds_error(self, symbol, final_note): - market_status = self.exchange_manager.exchange.get_market_status(symbol, price_example=None, with_fixer=False) - try: - base, quote = symbol_util.parse_symbol(symbol).base_and_quote() - portfolio = self.exchange_manager.exchange_personal_data.portfolio_manager.portfolio - funds = { - base: portfolio.get_currency_portfolio(base), - quote: portfolio.get_currency_portfolio(quote) - } - except Exception as err: - self.logger.error(f"Error when getting funds for {symbol}: {err}") - funds = {} + if symbol is None: + return ( + f"Not enough funds to create new orders after {final_note} evaluation: " + f"{self.exchange_manager.exchange_name} exchange minimal order " + f"volume has not been reached." + ) + else: + market_status = self.exchange_manager.exchange.get_market_status(symbol, price_example=None, with_fixer=False) + try: + base, quote = symbol_util.parse_symbol(symbol).base_and_quote() + portfolio = self.exchange_manager.exchange_personal_data.portfolio_manager.portfolio + funds = { + base: portfolio.get_currency_portfolio(base), + quote: portfolio.get_currency_portfolio(quote) + } + except Exception as err: + self.logger.error(f"Error when getting funds for {symbol}: {err}") + funds = {} return ( f"Not enough funds to create a new {symbol} order after {final_note} evaluation: " f"{self.exchange_manager.exchange_name} exchange minimal order " @@ -213,7 +220,7 @@ def get_holdings_ratio(self, currency, include_assets_in_open_orders=False): ) return current_holdings_value / total_holdings_value # only take actual holdings into account - return portfolio_manager.portfolio_value_holder .get_currency_holding_ratio(currency) + return portfolio_manager.portfolio_value_holder.get_currency_holding_ratio(currency) def get_number_of_traded_assets(self): return len(self.exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder From 56d579eabeda370d7f0a06dd8a97f593499eea15 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin Date: Sat, 23 Mar 2024 16:20:04 +0100 Subject: [PATCH 5/6] [Portfolio] move get_holdings_ratio to portfolio value holder --- .../modes/channel/abstract_mode_consumer.py | 21 --- .../basic_keywords/account_balance.py | 16 -- .../script_keywords/basic_keywords/amount.py | 12 +- .../portfolios/portfolio_value_holder.py | 55 +++++-- .../basic_keywords/test_account_balance.py | 18 --- .../basic_keywords/test_amount.py | 33 ++-- tests/modes/test_abstract_mode_consumer.py | 102 ------------ .../portfolios/test_portfolio_value_holder.py | 153 ++++++++++++++++++ 8 files changed, 226 insertions(+), 184 deletions(-) diff --git a/octobot_trading/modes/channel/abstract_mode_consumer.py b/octobot_trading/modes/channel/abstract_mode_consumer.py index 970b8f2c1..120366d02 100644 --- a/octobot_trading/modes/channel/abstract_mode_consumer.py +++ b/octobot_trading/modes/channel/abstract_mode_consumer.py @@ -201,27 +201,6 @@ async def can_create_order(self, symbol, state): self.logger.debug("can_create_order: return False") return False - def get_holdings_ratio(self, currency, include_assets_in_open_orders=False): - portfolio_manager = self.exchange_manager.exchange_personal_data.portfolio_manager - if include_assets_in_open_orders: - total_holdings_value = portfolio_manager.portfolio_value_holder.portfolio_current_value - if not total_holdings_value: - return constants.ZERO - assets_in_open_orders = constants.ZERO - for order in self.exchange_manager.exchange_personal_data.orders_manager.get_open_orders(): - symbol = symbol_util.parse_symbol(order.symbol) - if order.side is enums.TradeOrderSide.BUY and symbol.base == currency: - assets_in_open_orders += order.origin_quantity - elif order.side is enums.TradeOrderSide.SELL and symbol.quote == currency: - assets_in_open_orders += order.total_cost - current_holdings_value = portfolio_manager.portfolio_value_holder.value_converter.evaluate_value( - currency, - portfolio_manager.portfolio.get_currency_portfolio(currency).total + assets_in_open_orders - ) - return current_holdings_value / total_holdings_value - # only take actual holdings into account - return portfolio_manager.portfolio_value_holder.get_currency_holding_ratio(currency) - def get_number_of_traded_assets(self): return len(self.exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder .origin_crypto_currencies_values) diff --git a/octobot_trading/modes/script_keywords/basic_keywords/account_balance.py b/octobot_trading/modes/script_keywords/basic_keywords/account_balance.py index 20ba38563..161b58dc2 100644 --- a/octobot_trading/modes/script_keywords/basic_keywords/account_balance.py +++ b/octobot_trading/modes/script_keywords/basic_keywords/account_balance.py @@ -71,22 +71,6 @@ async def available_account_balance(context, side=trading_enums.TradeOrderSide.B - already_locked_amount -def get_holdings_value(context, assets, target_unit): - total_value = trading_constants.ZERO - portfolio = context.exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio - converter = context.exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.value_converter - for asset, asset_holdings in portfolio.items(): - if asset not in assets: - continue - try: - total_value += converter.evaluate_value( - asset, asset_holdings.total, raise_error=True, target_currency=target_unit, init_price_fetchers=False - ) - except trading_errors.MissingPriceDataError: - context.logger.info(f"Missing {asset} price conversion, ignoring {float(asset_holdings.total)} holdings.") - return total_value - - def _get_locked_amount_in_stop_orders(context, side): locked_amount = trading_constants.ZERO for order in context.exchange_manager.exchange_personal_data.orders_manager.get_open_orders(context.symbol): diff --git a/octobot_trading/modes/script_keywords/basic_keywords/amount.py b/octobot_trading/modes/script_keywords/basic_keywords/amount.py index 6917c2b06..b31305df6 100644 --- a/octobot_trading/modes/script_keywords/basic_keywords/amount.py +++ b/octobot_trading/modes/script_keywords/basic_keywords/amount.py @@ -59,7 +59,10 @@ async def get_amount_from_input_amount( if not context.symbol: raise trading_errors.InvalidArgumentError(f"{amount_type} input types requires context.symbol to be set") base, quote = commons_symbols.parse_symbol(context.symbol).base_and_quote() - total_symbol_assets_holdings_value = account_balance.get_holdings_value(context, (base, quote), base) + total_symbol_assets_holdings_value = context.exchange_manager.exchange_personal_data.portfolio_manager.\ + portfolio_value_holder.get_assets_holdings_value( + (base, quote), commons_symbols.parse_symbol(context.symbol).base + ) amount_value = total_symbol_assets_holdings_value * amount_value / trading_constants.ONE_HUNDRED elif amount_type is dsl.QuantityType.TRADED_SYMBOLS_ASSETS_PERCENT: if not context.symbol: @@ -68,9 +71,10 @@ async def get_amount_from_input_amount( for symbol in context.exchange_manager.exchange_config.traded_symbols: assets.add(symbol.base) assets.add(symbol.quote) - total_symbol_assets_holdings_value = account_balance.get_holdings_value( - context, assets, commons_symbols.parse_symbol(context.symbol).base - ) + total_symbol_assets_holdings_value = context.exchange_manager.exchange_personal_data.portfolio_manager.\ + portfolio_value_holder.get_assets_holdings_value( + assets, commons_symbols.parse_symbol(context.symbol).base + ) amount_value = total_symbol_assets_holdings_value * amount_value / trading_constants.ONE_HUNDRED elif amount_type is dsl.QuantityType.POSITION_PERCENT: raise NotImplementedError(amount_type) diff --git a/octobot_trading/personal_data/portfolios/portfolio_value_holder.py b/octobot_trading/personal_data/portfolios/portfolio_value_holder.py index 6531e4ccb..483b05705 100644 --- a/octobot_trading/personal_data/portfolios/portfolio_value_holder.py +++ b/octobot_trading/personal_data/portfolios/portfolio_value_holder.py @@ -20,6 +20,7 @@ import octobot_trading.constants as constants import octobot_trading.errors as errors +import octobot_trading.enums as enums import octobot_trading.personal_data.portfolios.value_converter as value_converter @@ -116,17 +117,49 @@ def get_current_holdings_values(self): for currency in holdings.keys() } - def get_currency_holding_ratio(self, currency): - """ - Return the holdings ratio for the specified currency - :return: the holdings ratio - """ - if self.portfolio_current_value: - return self.value_converter.evaluate_value( - currency, - self.portfolio_manager.portfolio.get_currency_portfolio(currency).total - ) / self.portfolio_current_value - return constants.ZERO + def get_assets_holdings_value(self, assets, target_unit, init_price_fetchers=False): + total_value = constants.ZERO + for asset, asset_holdings in self.portfolio_manager.portfolio.portfolio.items(): + if asset not in assets: + continue + try: + total_value += self.value_converter.evaluate_value( + asset, asset_holdings.total, raise_error=True, + target_currency=target_unit, init_price_fetchers=init_price_fetchers + ) + except errors.MissingPriceDataError: + self.logger.info(f"Missing {asset} price conversion, ignoring {float(asset_holdings.total)} holdings.") + return total_value + + def get_holdings_ratio(self, currency, traded_symbols_only=False, include_assets_in_open_orders=False): + if traded_symbols_only: + # only consider traded assets for total_holdings_value + assets = set() + for symbol in self.portfolio_manager.exchange_manager.exchange_config.traded_symbols: + assets.add(symbol.base) + assets.add(symbol.quote) + total_holdings_value = self.get_assets_holdings_value( + assets, self.portfolio_manager.reference_market + ) + else: + # consider all assets for total_holdings_value + total_holdings_value = self.portfolio_current_value + if not total_holdings_value: + return constants.ZERO + currency_holdings = self.portfolio_manager.portfolio.get_currency_portfolio(currency).total + if include_assets_in_open_orders: + # add assets in open orders to currency_holdings + assets_in_open_orders = constants.ZERO + for order in self.portfolio_manager.exchange_manager.exchange_personal_data.orders_manager.get_open_orders(): + symbol = symbol_util.parse_symbol(order.symbol) + if order.side is enums.TradeOrderSide.BUY and symbol.base == currency: + assets_in_open_orders += order.origin_quantity + elif order.side is enums.TradeOrderSide.SELL and symbol.quote == currency: + assets_in_open_orders += order.total_cost + currency_holdings += assets_in_open_orders + # compute ration + current_holdings_value = self.value_converter.evaluate_value(currency, currency_holdings) + return current_holdings_value / total_holdings_value def handle_profitability_recalculation(self, force_recompute_origin_portfolio): """ diff --git a/tests/modes/script_keywords/basic_keywords/test_account_balance.py b/tests/modes/script_keywords/basic_keywords/test_account_balance.py index 63b92d5c8..bc8bf56e4 100644 --- a/tests/modes/script_keywords/basic_keywords/test_account_balance.py +++ b/tests/modes/script_keywords/basic_keywords/test_account_balance.py @@ -92,24 +92,6 @@ async def test_available_account_balance(mock_context): _get_locked_amount_in_stop_orders_mock.reset_mock() -async def test_get_holdings_value(mock_context): - portfolio = mock_context.exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio - last_prices = mock_context.exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ - value_converter.last_prices_by_trading_pair - # no provided input asset - assert account_balance.get_holdings_value(mock_context, (), "USDT") == trading_constants.ZERO - # missing currency - assert account_balance.get_holdings_value(mock_context, {"BTC", "ETH"}, "PLOP") == trading_constants.ZERO - assert account_balance.get_holdings_value(mock_context, ("BTC"), "BTC") == portfolio["BTC"].total - assert account_balance.get_holdings_value(mock_context, ("BTC"), "BTC") != portfolio["BTC"].available - assert account_balance.get_holdings_value(mock_context, ("BTC"), "USDT") == \ - portfolio["BTC"].total * last_prices["BTC/USDT"] - assert account_balance.get_holdings_value(mock_context, {"BTC", "ETH"}, "USDT") == \ - portfolio["BTC"].total * last_prices["BTC/USDT"] + portfolio["ETH"].total * last_prices["ETH/USDT"] - assert account_balance.get_holdings_value(mock_context, {"BTC", "ETH"}, "ETH") == \ - portfolio["BTC"].total / last_prices["ETH/BTC"] + portfolio["ETH"].total - - async def test_adapt_amount_to_holdings(null_context): with mock.patch.object(account_balance, "available_account_balance", mock.AsyncMock(return_value=decimal.Decimal(1))) as available_account_balance_mock: diff --git a/tests/modes/script_keywords/basic_keywords/test_amount.py b/tests/modes/script_keywords/basic_keywords/test_amount.py index acc1b383f..ea13daf12 100644 --- a/tests/modes/script_keywords/basic_keywords/test_amount.py +++ b/tests/modes/script_keywords/basic_keywords/test_amount.py @@ -33,6 +33,13 @@ async def test_get_amount_from_input_amount(null_context): + null_context.exchange_manager = mock.Mock( + exchange_personal_data=mock.Mock( + portfolio_manager=mock.Mock( + portfolio_value_holder=mock.Mock() + ) + ) + ) with pytest.raises(errors.InvalidArgumentError): await script_keywords.get_amount_from_input_amount(null_context, "-1") @@ -175,10 +182,10 @@ async def test_get_amount_from_input_amount(null_context): with mock.patch.object(dsl, "parse_quantity", mock.Mock(return_value=( script_keywords.QuantityType.CURRENT_SYMBOL_ASSETS_PERCENT, decimal.Decimal(75)))) \ - as parse_quantity_mock, \ - mock.patch.object(account_balance, "get_holdings_value", - mock.Mock(return_value=decimal.Decimal(2))) \ - as get_holdings_value_mock: + as parse_quantity_mock, mock.patch.object( + null_context.exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder, + "get_assets_holdings_value", mock.Mock(return_value=decimal.Decimal(2)) + ) as get_holdings_value_mock: null_context.symbol = None with pytest.raises(errors.InvalidArgumentError): # context.symbol is None @@ -191,16 +198,16 @@ async def test_get_amount_from_input_amount(null_context): adapt_amount_to_holdings_mock.assert_called_once_with(null_context, decimal.Decimal("1.5"), "buy", False, True, False, target_price=None) parse_quantity_mock.assert_called_once_with("50") - get_holdings_value_mock.assert_called_once_with(null_context, ("BTC", "USDT"), "BTC") + get_holdings_value_mock.assert_called_once_with(("BTC", "USDT"), "BTC") adapt_amount_to_holdings_mock.reset_mock() with mock.patch.object(dsl, "parse_quantity", mock.Mock(return_value=( script_keywords.QuantityType.TRADED_SYMBOLS_ASSETS_PERCENT, decimal.Decimal(75)))) \ - as parse_quantity_mock, \ - mock.patch.object(account_balance, "get_holdings_value", - mock.Mock(return_value=decimal.Decimal(10))) \ - as get_holdings_value_mock: + as parse_quantity_mock, mock.patch.object( + null_context.exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder, + "get_assets_holdings_value", mock.Mock(return_value=decimal.Decimal(10)) + ) as get_holdings_value_mock: null_context.symbol = None with pytest.raises(errors.InvalidArgumentError): # context.symbol is None @@ -208,19 +215,21 @@ async def test_get_amount_from_input_amount(null_context): 1) parse_quantity_mock.reset_mock() null_context.symbol = "BTC/USDT" + exchange_personal_data = null_context.exchange_manager.exchange_personal_data null_context.exchange_manager = mock.Mock( exchange_config=mock.Mock( traded_symbols=[ commons_symbols.parse_symbol("BTC/USDT"), commons_symbols.parse_symbol("ETH/BTC"), commons_symbols.parse_symbol("SOL/USDT"), - ] - ) + ], + ), + exchange_personal_data=exchange_personal_data ) assert await script_keywords.get_amount_from_input_amount(null_context, "50", "buy") == decimal.Decimal(1) adapt_amount_to_holdings_mock.assert_called_once_with(null_context, decimal.Decimal("7.5"), "buy", False, True, False, target_price=None) parse_quantity_mock.assert_called_once_with("50") - get_holdings_value_mock.assert_called_once_with(null_context, {'BTC', 'ETH', 'SOL', 'USDT'}, "BTC") + get_holdings_value_mock.assert_called_once_with({'BTC', 'ETH', 'SOL', 'USDT'}, "BTC") adapt_amount_to_holdings_mock.reset_mock() diff --git a/tests/modes/test_abstract_mode_consumer.py b/tests/modes/test_abstract_mode_consumer.py index 697415a59..1e5a49886 100644 --- a/tests/modes/test_abstract_mode_consumer.py +++ b/tests/modes/test_abstract_mode_consumer.py @@ -135,108 +135,6 @@ async def test_valid_create_new_order(): await consumer.create_new_orders(symbol, -1, EvaluatorStates.LONG, xyz=1, aaa="bbb") -async def test_get_holdings_ratio(): - exchange_manager, symbol, consumer = await _get_tools() - exchange_manager.client_symbols = [symbol] - exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ - value_converter.last_prices_by_trading_pair[symbol] = decimal.Decimal("1000") - exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ - portfolio_current_value = decimal.Decimal("11") - exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio = {} - exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio["BTC"] = \ - portfolio_assets.SpotAsset(name="BTC", available=decimal.Decimal("10"), total=decimal.Decimal("10")) - exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio["USDT"] = \ - portfolio_assets.SpotAsset(name="USDT", available=decimal.Decimal("1000"), total=decimal.Decimal("1000")) - - assert consumer.get_holdings_ratio("BTC") == decimal.Decimal('0.9090909090909090909090909091') - assert consumer.get_holdings_ratio("BTC", include_assets_in_open_orders=False) \ - == decimal.Decimal('0.9090909090909090909090909091') - assert consumer.get_holdings_ratio("USDT") == decimal.Decimal('0.09090909090909090909090909091') - - exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio.pop("USDT") - exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ - portfolio_current_value = decimal.Decimal("10") - assert consumer.get_holdings_ratio("BTC") == constants.ONE - # add ETH and try to get ratio without symbol price - exchange_manager.exchange_personal_data.portfolio_manager.portfolio.\ - get_currency_portfolio("ETH").total = decimal.Decimal(10) - # force not backtesting mode - exchange_manager.is_backtesting = False - # force add symbol in exchange symbols - exchange_manager.client_symbols.append("ETH/BTC") - with pytest.raises(errors.MissingPriceDataError): - ratio = consumer.get_holdings_ratio("ETH") - # let channel register proceed - await wait_asyncio_next_cycle() - assert consumer.get_holdings_ratio("BTC") == constants.ONE - assert consumer.get_holdings_ratio("USDT") == constants.ZERO - assert consumer.get_holdings_ratio("XYZ") == constants.ZERO - - -async def test_get_holdings_ratio_with_include_assets_in_open_orders(): - exchange_manager, symbol, consumer = await _get_tools() - exchange_manager.client_symbols = [symbol] - exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ - value_converter.last_prices_by_trading_pair[symbol] = decimal.Decimal("1000") - exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ - value_converter.last_prices_by_trading_pair["ETH/USDT"] = decimal.Decimal("100") - exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ - portfolio_current_value = decimal.Decimal("11") - exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio = {} - exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio["BTC"] = \ - portfolio_assets.SpotAsset(name="BTC", available=decimal.Decimal("10"), total=decimal.Decimal("10")) - exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio["USDT"] = \ - portfolio_assets.SpotAsset(name="USDT", available=decimal.Decimal("1000"), total=decimal.Decimal("1000")) - - assert consumer.get_holdings_ratio("BTC", include_assets_in_open_orders=False) \ - == decimal.Decimal('0.9090909090909090909090909091') - assert consumer.get_holdings_ratio("USDT", include_assets_in_open_orders=False) \ - == decimal.Decimal('0.09090909090909090909090909091') - - # no open order - assert consumer.get_holdings_ratio("BTC", include_assets_in_open_orders=True) \ - == decimal.Decimal('0.9090909090909090909090909091') - assert consumer.get_holdings_ratio("USDT", include_assets_in_open_orders=True) \ - == decimal.Decimal('0.09090909090909090909090909091') - - order_1 = mock.Mock( - order_id="1", symbol="BTC/USDT", origin_quantity=decimal.Decimal("1"), total_cost=decimal.Decimal('1000'), - status=enums.OrderStatus.OPEN, side=enums.TradeOrderSide.BUY - ) - order_2 = mock.Mock( - order_id="2", symbol="ETH/USDT", origin_quantity=decimal.Decimal("2"), total_cost=decimal.Decimal('100'), - status=enums.OrderStatus.OPEN, side=enums.TradeOrderSide.BUY - ) - order_3 = mock.Mock( - order_id="3", symbol="XRP/ETH", origin_quantity=decimal.Decimal("100"), total_cost=decimal.Decimal('0.001'), - status=enums.OrderStatus.OPEN, side=enums.TradeOrderSide.SELL - ) - # open orders - await exchange_manager.exchange_personal_data.orders_manager.upsert_order_instance(order_1) - await exchange_manager.exchange_personal_data.orders_manager.upsert_order_instance(order_2) - assert consumer.get_holdings_ratio("BTC", include_assets_in_open_orders=False) \ - == decimal.Decimal('0.9090909090909090909090909091') - assert consumer.get_holdings_ratio("ETH", include_assets_in_open_orders=False) \ - == decimal.Decimal('0') - assert consumer.get_holdings_ratio("USDT", include_assets_in_open_orders=False) \ - == decimal.Decimal('0.09090909090909090909090909091') - - assert consumer.get_holdings_ratio("BTC", include_assets_in_open_orders=True) \ - == decimal.Decimal('1') - assert consumer.get_holdings_ratio("ETH", include_assets_in_open_orders=True) \ - == decimal.Decimal('0.01818181818181818181818181818') - assert consumer.get_holdings_ratio("USDT", include_assets_in_open_orders=True) \ - == decimal.Decimal('0.09090909090909090909090909091') - - await exchange_manager.exchange_personal_data.orders_manager.upsert_order_instance(order_3) - assert consumer.get_holdings_ratio("BTC", include_assets_in_open_orders=True) \ - == decimal.Decimal('1') - assert consumer.get_holdings_ratio("ETH", include_assets_in_open_orders=True) \ - == decimal.Decimal('0.01819090909090909090909090909') - assert consumer.get_holdings_ratio("USDT", include_assets_in_open_orders=True) \ - == decimal.Decimal('0.09090909090909090909090909091') - - async def test_get_number_of_traded_assets(): exchange_manager, symbol, consumer = await _get_tools() exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ diff --git a/tests/personal_data/portfolios/test_portfolio_value_holder.py b/tests/personal_data/portfolios/test_portfolio_value_holder.py index e97f048e1..82182d5ce 100644 --- a/tests/personal_data/portfolios/test_portfolio_value_holder.py +++ b/tests/personal_data/portfolios/test_portfolio_value_holder.py @@ -14,10 +14,17 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. import decimal +import mock import os import pytest +import octobot_commons.asyncio_tools as asyncio_tools +import octobot_commons.symbols as commons_symbols + import octobot_trading.constants as constants +import octobot_trading.errors as errors +import octobot_trading.enums as enums +import octobot_trading.personal_data as personal_data from tests.test_utils.random_numbers import decimal_random_quantity, decimal_random_price from tests.exchanges import backtesting_trader, backtesting_config, backtesting_exchange_manager, fake_backtesting @@ -213,3 +220,149 @@ async def test_update_origin_crypto_currencies_values(backtesting_trader): # USDT is now priced and BTC is the reference market assert portfolio_value_holder.update_origin_crypto_currencies_values("DOT/ETH", decimal.Decimal(str(0.015))) \ is False + + +async def test_get_holdings_ratio(backtesting_trader): + config, exchange_manager, trader = backtesting_trader + portfolio_manager = exchange_manager.exchange_personal_data.portfolio_manager + portfolio_value_holder = portfolio_manager.portfolio_value_holder + symbol = "BTC/USDT" + exchange_manager.client_symbols = [symbol] + exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ + value_converter.last_prices_by_trading_pair[symbol] = decimal.Decimal("1000") + exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ + portfolio_current_value = decimal.Decimal("11") + exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio = {} + exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio["BTC"] = \ + personal_data.SpotAsset(name="BTC", available=decimal.Decimal("10"), total=decimal.Decimal("10")) + exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio["USDT"] = \ + personal_data.SpotAsset(name="USDT", available=decimal.Decimal("1000"), total=decimal.Decimal("1000")) + + assert portfolio_value_holder.get_holdings_ratio("BTC") == decimal.Decimal('0.9090909090909090909090909091') + assert portfolio_value_holder.get_holdings_ratio("BTC", include_assets_in_open_orders=False) \ + == decimal.Decimal('0.9090909090909090909090909091') + assert portfolio_value_holder.get_holdings_ratio("USDT") == decimal.Decimal('0.09090909090909090909090909091') + + exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio.pop("USDT") + exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ + portfolio_current_value = decimal.Decimal("10") + assert portfolio_value_holder.get_holdings_ratio("BTC") == constants.ONE + # add ETH and try to get ratio without symbol price + exchange_manager.exchange_personal_data.portfolio_manager.portfolio.\ + get_currency_portfolio("ETH").total = decimal.Decimal(10) + # force not backtesting mode + exchange_manager.is_backtesting = False + # force add symbol in exchange symbols + exchange_manager.client_symbols.append("ETH/BTC") + with pytest.raises(errors.MissingPriceDataError): + ratio = portfolio_value_holder.get_holdings_ratio("ETH") + # let channel register proceed + await asyncio_tools.wait_asyncio_next_cycle() + assert portfolio_value_holder.get_holdings_ratio("BTC") == constants.ONE + assert portfolio_value_holder.get_holdings_ratio("USDT") == constants.ZERO + assert portfolio_value_holder.get_holdings_ratio("XYZ") == constants.ZERO + + # without traded_symbols + assert portfolio_value_holder.get_holdings_ratio("BTC", traded_symbols_only=True) == constants.ZERO + + # with traded_symbols + exchange_manager.exchange_config.traded_symbols.extend([ + commons_symbols.parse_symbol("BTC/USDT"), commons_symbols.parse_symbol("ETH/USDT") + ]) + assert portfolio_value_holder.get_holdings_ratio("BTC", traded_symbols_only=True) == constants.ONE + +async def test_get_holdings_ratio_with_include_assets_in_open_orders(backtesting_trader): + config, exchange_manager, trader = backtesting_trader + portfolio_manager = exchange_manager.exchange_personal_data.portfolio_manager + portfolio_value_holder = portfolio_manager.portfolio_value_holder + symbol = "BTC/USDT" + exchange_manager.client_symbols = [symbol] + exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ + value_converter.last_prices_by_trading_pair[symbol] = decimal.Decimal("1000") + exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ + value_converter.last_prices_by_trading_pair["ETH/USDT"] = decimal.Decimal("100") + exchange_manager.exchange_personal_data.portfolio_manager.portfolio_value_holder.\ + portfolio_current_value = decimal.Decimal("11") + exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio = {} + exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio["BTC"] = \ + personal_data.SpotAsset(name="BTC", available=decimal.Decimal("10"), total=decimal.Decimal("10")) + exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio["USDT"] = \ + personal_data.SpotAsset(name="USDT", available=decimal.Decimal("1000"), total=decimal.Decimal("1000")) + + assert portfolio_value_holder.get_holdings_ratio("BTC", include_assets_in_open_orders=False) \ + == decimal.Decimal('0.9090909090909090909090909091') + assert portfolio_value_holder.get_holdings_ratio("USDT", include_assets_in_open_orders=False) \ + == decimal.Decimal('0.09090909090909090909090909091') + + # no open order + assert portfolio_value_holder.get_holdings_ratio("BTC", include_assets_in_open_orders=True) \ + == decimal.Decimal('0.9090909090909090909090909091') + assert portfolio_value_holder.get_holdings_ratio("USDT", include_assets_in_open_orders=True) \ + == decimal.Decimal('0.09090909090909090909090909091') + + order_1 = mock.Mock( + order_id="1", symbol="BTC/USDT", origin_quantity=decimal.Decimal("1"), total_cost=decimal.Decimal('1000'), + status=enums.OrderStatus.OPEN, side=enums.TradeOrderSide.BUY + ) + order_2 = mock.Mock( + order_id="2", symbol="ETH/USDT", origin_quantity=decimal.Decimal("2"), total_cost=decimal.Decimal('100'), + status=enums.OrderStatus.OPEN, side=enums.TradeOrderSide.BUY + ) + order_3 = mock.Mock( + order_id="3", symbol="XRP/ETH", origin_quantity=decimal.Decimal("100"), total_cost=decimal.Decimal('0.001'), + status=enums.OrderStatus.OPEN, side=enums.TradeOrderSide.SELL + ) + # open orders + await exchange_manager.exchange_personal_data.orders_manager.upsert_order_instance(order_1) + await exchange_manager.exchange_personal_data.orders_manager.upsert_order_instance(order_2) + assert portfolio_value_holder.get_holdings_ratio("BTC", include_assets_in_open_orders=False) \ + == decimal.Decimal('0.9090909090909090909090909091') + assert portfolio_value_holder.get_holdings_ratio("ETH", include_assets_in_open_orders=False) \ + == decimal.Decimal('0') + assert portfolio_value_holder.get_holdings_ratio("USDT", include_assets_in_open_orders=False) \ + == decimal.Decimal('0.09090909090909090909090909091') + + assert portfolio_value_holder.get_holdings_ratio("BTC", include_assets_in_open_orders=True) \ + == decimal.Decimal('1') + assert portfolio_value_holder.get_holdings_ratio("ETH", include_assets_in_open_orders=True) \ + == decimal.Decimal('0.01818181818181818181818181818') + assert portfolio_value_holder.get_holdings_ratio("USDT", include_assets_in_open_orders=True) \ + == decimal.Decimal('0.09090909090909090909090909091') + + await exchange_manager.exchange_personal_data.orders_manager.upsert_order_instance(order_3) + assert portfolio_value_holder.get_holdings_ratio("BTC", include_assets_in_open_orders=True) \ + == decimal.Decimal('1') + assert portfolio_value_holder.get_holdings_ratio("ETH", include_assets_in_open_orders=True) \ + == decimal.Decimal('0.01819090909090909090909090909') + assert portfolio_value_holder.get_holdings_ratio("USDT", include_assets_in_open_orders=True) \ + == decimal.Decimal('0.09090909090909090909090909091') + + # without traded_symbols + assert portfolio_value_holder.get_holdings_ratio( + "BTC", traded_symbols_only=True, include_assets_in_open_orders=True + ) == constants.ZERO + + # with traded_symbols + exchange_manager.exchange_personal_data.portfolio_manager.portfolio.portfolio["ETH"] = \ + personal_data.SpotAsset(name="ETH", available=decimal.Decimal("10"), total=decimal.Decimal("10")) + exchange_manager.exchange_config.traded_symbols.extend([ + commons_symbols.parse_symbol("BTC/USDT") + ]) + assert portfolio_value_holder.get_holdings_ratio( + "BTC", traded_symbols_only=True, include_assets_in_open_orders=True + ) == constants.ONE + assert portfolio_value_holder.get_holdings_ratio( + "ETH", traded_symbols_only=True, include_assets_in_open_orders=True + ) == decimal.Decimal('0.1091') + + # ETH now in traded assets + exchange_manager.exchange_config.traded_symbols.extend([ + commons_symbols.parse_symbol("ETH/USDT") + ]) + assert portfolio_value_holder.get_holdings_ratio( + "ETH", traded_symbols_only=True, include_assets_in_open_orders=True + ) == decimal.Decimal('0.1000083333333333333333333333') # ETH now taken into account in total value + # ETH now taken into account in total value: BTC % of holdings is not 1 anymore (as ETH takes a part of this %) + assert portfolio_value_holder.get_holdings_ratio( + "BTC", traded_symbols_only=True, include_assets_in_open_orders=True + ) == decimal.Decimal('0.9166666666666666666666666667') From c30cb610bfedf3ffb16752850ca181f17df3bc87 Mon Sep 17 00:00:00 2001 From: Guillaume De Saint Martin Date: Sat, 23 Mar 2024 09:57:17 +0100 Subject: [PATCH 6/6] [Version] v2.4.67 --- CHANGELOG.md | 5 +++++ README.md | 2 +- octobot_trading/__init__.py | 2 +- requirements.txt | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7542a03cc..80b7c5ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.4.67] - 2024-03-23 +### Updated +- [TradingMode] Requirements for indexes +- [Orders] Use creation_time in to_dict + ## [2.4.66] - 2024-03-19 ### Updated - [CCXT] to 4.2.77 diff --git a/README.md b/README.md index fbce353ff..c073a2398 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# OctoBot-Trading [2.4.66](https://github.com/Drakkar-Software/OctoBot-Trading/blob/master/CHANGELOG.md) +# OctoBot-Trading [2.4.67](https://github.com/Drakkar-Software/OctoBot-Trading/blob/master/CHANGELOG.md) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/903b6b22bceb4661b608a86fea655f69)](https://app.codacy.com/gh/Drakkar-Software/OctoBot-Trading?utm_source=github.com&utm_medium=referral&utm_content=Drakkar-Software/OctoBot-Trading&utm_campaign=Badge_Grade_Dashboard) [![PyPI](https://img.shields.io/pypi/v/OctoBot-Trading.svg)](https://pypi.python.org/pypi/OctoBot-Trading/) [![Coverage Status](https://coveralls.io/repos/github/Drakkar-Software/OctoBot-Trading/badge.svg?branch=master)](https://coveralls.io/github/Drakkar-Software/OctoBot-Trading?branch=master) diff --git a/octobot_trading/__init__.py b/octobot_trading/__init__.py index d1e006a67..736779c96 100644 --- a/octobot_trading/__init__.py +++ b/octobot_trading/__init__.py @@ -15,4 +15,4 @@ # License along with this library. PROJECT_NAME = "OctoBot-Trading" -VERSION = "2.4.66" # major.minor.revision +VERSION = "2.4.67" # major.minor.revision diff --git a/requirements.txt b/requirements.txt index 1f52b287f..b9bc9c6e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ numpy==1.26.3 # Drakkar-Software requirements OctoBot-Backtesting>=1.9, <1.10 Async-Channel>=2.2, <2.3 -OctoBot-Commons>=1.9, <1.10 +OctoBot-Commons>=1.9.43, <1.10 OctoBot-Tentacles-Manager>=2.9, <2.10 trading-backend>=1.2.7