diff --git a/Trading/Exchange/hollaex/hollaex_exchange.py b/Trading/Exchange/hollaex/hollaex_exchange.py index 65a243d76..84be004cd 100644 --- a/Trading/Exchange/hollaex/hollaex_exchange.py +++ b/Trading/Exchange/hollaex/hollaex_exchange.py @@ -148,7 +148,8 @@ async def _refresh_exchange_fee_tiers(self, all_markets: list[dict]): fee_pairs = list(fees_by_tier[next(iter(fees_by_tier))]) if fees_by_tier else [] self.logger.info( f"Refreshed {exchange_name} fee tiers. Sample: {sample}. {len(sample)} tiers: {list(sample)} " - f"over {len(fee_pairs)} pairs: {fee_pairs}." + f"over {len(fee_pairs)} pairs: {fee_pairs}. Using fee tiers " + f"{self._get_fee_tiers(self.exchange_manager.exchange, not self.exchange_manager.is_backtesting).value}." ) @classmethod @@ -177,7 +178,7 @@ def register_simulator_connector_fee_methods( ): # only called in backtesting # overrides exchange simulator connector calculate_fees and get_fees to use fetched fees instead - fee_tiers = cls._get_fee_tiers(False) + fee_tiers = cls._get_fee_tiers(None, False) simulator_connector.calculate_fees = cls.simulator_connector_calculate_fees_factory(exchange_name, fee_tiers) simulator_connector.get_fees = cls.simulator_connector_get_fees_factory(exchange_name, fee_tiers) @@ -188,8 +189,9 @@ def calculate_fees( # only called in live trading is_real_trading = not self.exchange_manager.is_backtesting # consider live trading as real to use basic tier try: + fee_tiers = self._get_fee_tiers(self.exchange_manager.exchange, is_real_trading) return self._calculate_fetched_fees( - self.exchange_manager.exchange_name, self._get_fee_tiers(is_real_trading), + self.exchange_manager.exchange_name, fee_tiers, symbol, order_type, quantity, price, taker_or_maker ) except errors.MissingFeeDetailsError as err: @@ -201,7 +203,8 @@ def get_fees(self, symbol): # only called in live trading try: is_real_trading = not self.exchange_manager.is_backtesting # consider live trading as real to use basic tier - return self._get_fees(self.exchange_manager.exchange_name, self._get_fee_tiers(is_real_trading), symbol) + fee_tiers = self._get_fee_tiers(self.exchange_manager.exchange, is_real_trading) + return self._get_fees(self.exchange_manager.exchange_name, fee_tiers, symbol) except errors.MissingFeeDetailsError: self.logger.error(f"Missing fee details, using default value") market = self.get_market_status(symbol, with_fixer=False) @@ -258,7 +261,14 @@ def _calculate_fetched_fees( } @classmethod - def _get_fee_tiers(cls, is_real_trading: bool): + def _get_fee_tiers(cls, rest_exchange: typing.Optional[exchanges.RestExchange], is_real_trading: bool): + if ( + rest_exchange + and isinstance(rest_exchange, hollaex) + and (fee_tiers := rest_exchange.get_configured_fee_tiers()) + ): + return fee_tiers + # default to basic tier return FeeTiers.BASIC if is_real_trading else FeeTiers.VIP @classmethod @@ -320,6 +330,7 @@ class hollaex(exchanges.RestExchange): BASE_REST_API = "api.hollaex.com" REST_KEY = "rest" + FEE_TIERS_KEY = "fee_tiers" HAS_WEBSOCKETS_KEY = "has_websockets" REQUIRE_ORDER_FEES_FROM_TRADES = True # set True when get_order is not giving fees on closed orders and fees SUPPORT_FETCHING_CANCELLED_ORDERS = False @@ -398,6 +409,11 @@ def init_user_inputs_from_class(cls, inputs: dict) -> None: cls.REST_KEY, commons_enums.UserInputTypes.TEXT, f"https://{cls.BASE_REST_API}", inputs, title=f"Address of the Hollaex based exchange API (similar to https://{cls.BASE_REST_API})" ) + cls.CLASS_UI.user_input( + cls.FEE_TIERS_KEY, commons_enums.UserInputTypes.OPTIONS, FeeTiers.BASIC.value, inputs, + title=f"Fee tiers to use for the exchange. Used to predict fees.", + options=[tier.value for tier in FeeTiers] + ) cls.CLASS_UI.user_input( cls.HAS_WEBSOCKETS_KEY, commons_enums.UserInputTypes.BOOLEAN, True, inputs, title=f"Use websockets feed. To enable only when websockets are supported by the exchange." @@ -405,9 +421,17 @@ def init_user_inputs_from_class(cls, inputs: dict) -> None: def get_additional_connector_config(self): return { - ccxt_enums.ExchangeColumns.URLS.value: self.get_patched_urls(self.tentacle_config[self.REST_KEY]) + ccxt_enums.ExchangeColumns.URLS.value: self.get_patched_urls(self.get_api_url()) } + def get_api_url(self): + return self.tentacle_config[self.REST_KEY] + + def get_configured_fee_tiers(self) -> typing.Optional[FeeTiers]: + if tiers := self.tentacle_config.get(self.FEE_TIERS_KEY): + return FeeTiers(tiers) + return None + @classmethod def get_custom_url_config(cls, tentacle_config: dict, exchange_name: str) -> dict: if details := cls.get_exchange_details(tentacle_config, exchange_name): diff --git a/Trading/Exchange/hyperliquid_websocket_feed/hyperliquid_websocket.py b/Trading/Exchange/hyperliquid_websocket_feed/hyperliquid_websocket.py index 0142a8fbd..bdf6fdef9 100644 --- a/Trading/Exchange/hyperliquid_websocket_feed/hyperliquid_websocket.py +++ b/Trading/Exchange/hyperliquid_websocket_feed/hyperliquid_websocket.py @@ -19,6 +19,7 @@ class HyperliquidCCXTWebsocketConnector(exchanges.CCXTWebsocketConnector): + USE_REST_CONNECTOR_ADDITIONAL_CONFIG = True EXCHANGE_FEEDS = { Feeds.TRADES: True, Feeds.KLINE: True, diff --git a/Trading/Mode/market_making_trading_mode/market_making_trading.py b/Trading/Mode/market_making_trading_mode/market_making_trading.py index cd6d43f2e..7bcd97623 100644 --- a/Trading/Mode/market_making_trading_mode/market_making_trading.py +++ b/Trading/Mode/market_making_trading_mode/market_making_trading.py @@ -191,7 +191,16 @@ async def get_forced_updater_channels( @classmethod def get_is_trading_on_exchange(cls, exchange_name, tentacles_setup_config) -> bool: """ - returns True if exchange_name is not in price sources + returns True if exchange_name is trading exchange or the hedging exchange + """ + return cls.has_trading_exchange_configuration( + exchange_name, octobot_tentacles_manager.api.get_tentacle_config(tentacles_setup_config, cls) + ) + + @classmethod + def get_is_using_trading_mode_on_exchange(cls, exchange_name, tentacles_setup_config) -> bool: + """ + returns True if exchange_name is a trading exchange that is not the hedging exchange """ return cls.has_trading_exchange_configuration( exchange_name, octobot_tentacles_manager.api.get_tentacle_config(tentacles_setup_config, cls) @@ -762,8 +771,8 @@ async def _handle_market_making_orders( outdated_orders = self._get_orders_to_cancel(sorted_orders, reference_price) if outdated_orders: self.logger.info( - f"{len(outdated_orders)} outdated orders for {self.symbol} {self.exchange_manager.exchange_name}: " - f"{[str(o) for o in outdated_orders]} [trigger source: {trigger_source}]" + f"{len(outdated_orders)} outdated orders for {self.symbol} on {self.exchange_manager.exchange_name} (trigger_source: {trigger_source}): " + f"{[str(o) for o in outdated_orders]}" ) # get ideal distribution @@ -1071,12 +1080,11 @@ def _get_orders_to_cancel( ] def _is_outdated( - self, price: decimal.Decimal, side: trading_enums.TradeOrderSide, reference_price: decimal.Decimal + self, order_price: decimal.Decimal, side: trading_enums.TradeOrderSide, reference_price: decimal.Decimal ) -> bool: - return ( - (side == trading_enums.TradeOrderSide.BUY and price > reference_price) - or (side == trading_enums.TradeOrderSide.SELL and price < reference_price) - ) + if side == trading_enums.TradeOrderSide.BUY: + return order_price > reference_price + return order_price < reference_price def _sort_orders(self, open_orders: list) -> list: """ @@ -1292,7 +1300,7 @@ async def _get_reference_price(self) -> decimal.Decimal: local_exchange_name, self.exchange_manager.id ): exchange_manager = trading_api.get_exchange_manager_from_exchange_id(exchange_id) - if exchange_manager.is_trading and exchange_manager is not self.exchange_manager: + if exchange_manager.trading_modes and exchange_manager is not self.exchange_manager: await self.sent_once_critical_notification( "Configuration issue", f"Multiple simultaneous trading exchanges is not supported on {self.trading_mode.get_name()}" diff --git a/Trading/Mode/trading_view_signals_trading_mode/trading_view_signals_trading.py b/Trading/Mode/trading_view_signals_trading_mode/trading_view_signals_trading.py index f551eecff..66563e817 100644 --- a/Trading/Mode/trading_view_signals_trading_mode/trading_view_signals_trading.py +++ b/Trading/Mode/trading_view_signals_trading_mode/trading_view_signals_trading.py @@ -209,8 +209,8 @@ def is_compatible_trading_type(cls, parsed_signal: dict, trading_type: trading_e def _log_error_message_if_relevant(self, parsed_data: dict, signal_data: str): # only log error messages on one TradingViewSignalsTradingMode instance to avoid logging errors multiple times - all_trading_modes = trading_modes.get_trading_modes_of_this_type_on_this_matrix(self) - if all_trading_modes and all_trading_modes[0] is self: + if self.is_first_trading_mode_on_this_matrix(): + all_trading_modes = trading_modes.get_trading_modes_of_this_type_on_this_matrix(self) # Can log error message: this is the first trading mode on this matrix. # Each is notified by signals and only this one will log errors to avoid duplicating logs if not any(