Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions Trading/Exchange/hollaex/hollaex_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -398,16 +409,29 @@ 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."
)

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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@


class HyperliquidCCXTWebsocketConnector(exchanges.CCXTWebsocketConnector):
USE_REST_CONNECTOR_ADDITIONAL_CONFIG = True
EXCHANGE_FEEDS = {
Feeds.TRADES: True,
Feeds.KLINE: True,
Expand Down
26 changes: 17 additions & 9 deletions Trading/Mode/market_making_trading_mode/market_making_trading.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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()}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down