From cac777cb214499b70f0cc187bc9d4888897fe3cc Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Wed, 4 Oct 2023 13:09:44 -0400 Subject: [PATCH 001/247] add property has_open_sl_orders to trade model --- freqtrade/persistence/trade_model.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 631585127..48fed1782 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -523,6 +523,17 @@ class LocalTrade: ] return len(open_orders_wo_sl) > 0 + @property + def has_open_sl_orders(self) -> int: + """ + True if there are open stoploss orders for this trade + """ + open_sl_orders = [ + o for o in self.orders + if o.ft_order_side in ['stoploss'] and o.ft_is_open + ] + return len(open_sl_orders) > 0 + @property def open_orders_ids(self) -> List[str]: open_orders_ids_wo_sl = [ From 9214af69012a6e73503c174f54ff9534e053447d Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Thu, 5 Oct 2023 22:24:17 -0400 Subject: [PATCH 002/247] update cancel_stoploss_on_exchange to cancel all sl orders of trade --- freqtrade/freqtradebot.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 02d43432d..ebc146ede 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -904,18 +904,18 @@ class FreqtradeBot(LoggingMixin): def cancel_stoploss_on_exchange(self, trade: Trade) -> Trade: # First cancelling stoploss on exchange ... - if trade.stoploss_order_id: - try: - logger.info(f"Canceling stoploss on exchange for {trade}") - co = self.exchange.cancel_stoploss_order_with_result( - trade.stoploss_order_id, trade.pair, trade.amount) - self.update_trade_state(trade, trade.stoploss_order_id, co, stoploss_order=True) - - # Reset stoploss order id. - trade.stoploss_order_id = None - except InvalidOrderException: - logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id} " - f"for pair {trade.pair}") + if trade.has_open_sl_orders: + for o in trade.orders: + if o.ft_order_side == 'stoploss' and o.ft_is_open: + try: + logger.info(f"Canceling stoploss on exchange for {trade} " + f"order: {o.order_id}") + co = self.exchange.cancel_stoploss_order_with_result( + o.order_id, trade.pair, trade.amount) + self.update_trade_state(trade, o.order_id, co, stoploss_order=True) + except InvalidOrderException: + logger.exception(f"Could not cancel stoploss order {o.order_id} " + f"for pair {trade.pair}") return trade def get_valid_enter_price_and_stake( From d5a0759051497c977499c38d747ca953aac1c99f Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Fri, 6 Oct 2023 10:29:57 -0400 Subject: [PATCH 003/247] add open_sl_orders helper, use it in cancel_stoploss_on_exchange --- freqtrade/freqtradebot.py | 22 ++++++++++------------ freqtrade/persistence/trade_model.py | 10 ++++++++++ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ebc146ede..ff3c36bf8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -904,18 +904,16 @@ class FreqtradeBot(LoggingMixin): def cancel_stoploss_on_exchange(self, trade: Trade) -> Trade: # First cancelling stoploss on exchange ... - if trade.has_open_sl_orders: - for o in trade.orders: - if o.ft_order_side == 'stoploss' and o.ft_is_open: - try: - logger.info(f"Canceling stoploss on exchange for {trade} " - f"order: {o.order_id}") - co = self.exchange.cancel_stoploss_order_with_result( - o.order_id, trade.pair, trade.amount) - self.update_trade_state(trade, o.order_id, co, stoploss_order=True) - except InvalidOrderException: - logger.exception(f"Could not cancel stoploss order {o.order_id} " - f"for pair {trade.pair}") + for oslo in trade.open_sl_orders: + try: + logger.info(f"Canceling stoploss on exchange for {trade} " + f"order: {oslo.order_id}") + co = self.exchange.cancel_stoploss_order_with_result( + oslo.order_id, trade.pair, trade.amount) + self.update_trade_state(trade, oslo.order_id, co, stoploss_order=True) + except InvalidOrderException: + logger.exception(f"Could not cancel stoploss order {oslo.order_id} " + f"for pair {trade.pair}") return trade def get_valid_enter_price_and_stake( diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 48fed1782..e483dcc24 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -523,6 +523,16 @@ class LocalTrade: ] return len(open_orders_wo_sl) > 0 + @property + def open_sl_orders(self) -> List[Order]: + """ + All open stoploss orders for this trade + """ + return [ + o for o in self.orders + if o.ft_order_side in ['stoploss'] and o.ft_is_open + ] + @property def has_open_sl_orders(self) -> int: """ From 2565d509a614b0d0f6730b5d1f819d912c3818f5 Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Fri, 6 Oct 2023 10:38:14 -0400 Subject: [PATCH 004/247] remove legacy sl management code from handle_insufficient_funds --- freqtrade/freqtradebot.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ff3c36bf8..7d78779ff 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -439,10 +439,6 @@ class FreqtradeBot(LoggingMixin): try: fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair, order.ft_order_side == 'stoploss') - if order.ft_order_side == 'stoploss': - if fo and fo['status'] == 'open': - # Assume this as the open stoploss order - trade.stoploss_order_id = order.order_id if fo: logger.info(f"Found {order} for trade {trade}.") self.update_trade_state(trade, order.order_id, fo, From ea828ccb4a8164f96a0853ae7e9e0b5a06e52adb Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Fri, 6 Oct 2023 10:40:23 -0400 Subject: [PATCH 005/247] remove legacy sl management code from create_stoploss_order --- freqtrade/freqtradebot.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 7d78779ff..a8f5665c8 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1189,7 +1189,6 @@ class FreqtradeBot(LoggingMixin): order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss', trade.amount, stop_price) trade.orders.append(order_obj) - trade.stoploss_order_id = str(stoploss_order['id']) trade.stoploss_last_update = datetime.now(timezone.utc) return True except InsufficientFundsError as e: @@ -1198,13 +1197,11 @@ class FreqtradeBot(LoggingMixin): self.handle_insufficient_funds(trade) except InvalidOrderException as e: - trade.stoploss_order_id = None logger.error(f'Unable to place a stoploss order on exchange. {e}') logger.warning('Exiting the trade forcefully') self.emergency_exit(trade, stop_price) except ExchangeError: - trade.stoploss_order_id = None logger.exception('Unable to place a stoploss order on exchange.') return False From 9d82de15d43dcb98e5c6c61912f0709315c874b2 Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Fri, 6 Oct 2023 15:56:08 -0400 Subject: [PATCH 006/247] first updated proposition of handle_stoploss_on_exchange, add sl_orders helper --- freqtrade/freqtradebot.py | 100 ++++++++++++++++----------- freqtrade/persistence/trade_model.py | 10 +++ 2 files changed, 70 insertions(+), 40 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a8f5665c8..3338805f7 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1215,27 +1215,30 @@ class FreqtradeBot(LoggingMixin): """ logger.debug('Handling stoploss on exchange %s ...', trade) - stoploss_order = None - try: - # First we check if there is already a stoploss on exchange - stoploss_order = self.exchange.fetch_stoploss_order( - trade.stoploss_order_id, trade.pair) if trade.stoploss_order_id else None - except InvalidOrderException as exception: - logger.warning('Unable to fetch stoploss order: %s', exception) + stoploss_orders = [] + for slo in trade.sl_orders: + stoploss_order = None + try: + # First we check if there is already a stoploss on exchange + stoploss_order = self.exchange.fetch_stoploss_order( + slo.order_id, trade.pair) if slo.order_id else None + except InvalidOrderException as exception: + logger.warning('Unable to fetch stoploss order: %s', exception) - if stoploss_order: - self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order, - stoploss_order=True) + if stoploss_order: + stoploss_orders.append(stoploss_order) + self.update_trade_state(trade, slo.order_id, stoploss_order, + stoploss_order=True) - # We check if stoploss order is fulfilled - if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'): - trade.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value - self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order, - stoploss_order=True) - self._notify_exit(trade, "stoploss", True) - self.handle_protections(trade.pair, trade.trade_direction) - return True + # We check if stoploss order is fulfilled + if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'): + trade.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value + self.update_trade_state(trade, slo.order_id, stoploss_order, + stoploss_order=True) + self._notify_exit(trade, "stoploss", True) + self.handle_protections(trade.pair, trade.trade_direction) + return True if trade.has_open_orders or not trade.is_open: # Trade has an open Buy or Sell order, Stoploss-handling can't happen in this case @@ -1244,7 +1247,7 @@ class FreqtradeBot(LoggingMixin): return False # If enter order is fulfilled but there is no stoploss, we add a stoploss on exchange - if not stoploss_order: + if len(stoploss_orders) == 0: stop_price = trade.stoploss_or_liquidation if self.edge: stoploss = self.edge.get_stoploss(pair=trade.pair) @@ -1258,27 +1261,7 @@ class FreqtradeBot(LoggingMixin): # in which case the trade will be closed - which we must check below. return False - # If stoploss order is canceled for some reason we add it again - if (trade.is_open - and stoploss_order - and stoploss_order['status'] in ('canceled', 'cancelled')): - if self.create_stoploss_order(trade=trade, stop_price=trade.stoploss_or_liquidation): - return False - else: - logger.warning('Stoploss order was cancelled, but unable to recreate one.') - - # Finally we check if stoploss on exchange should be moved up because of trailing. - # Triggered Orders are now real orders - so don't replace stoploss anymore - if ( - trade.is_open and stoploss_order - and stoploss_order.get('status_stop') != 'triggered' - and (self.config.get('trailing_stop', False) - or self.config.get('use_custom_stoploss', False)) - ): - # if trailing stoploss is enabled we check if stoploss value has changed - # in which case we cancel stoploss order and put another one with new - # value immediately - self.handle_trailing_stoploss_on_exchange(trade, stoploss_order) + self.manage_trade_stoploss_orders(trade, stoploss_orders) return False @@ -1314,6 +1297,43 @@ class FreqtradeBot(LoggingMixin): logger.warning(f"Could not create trailing stoploss order " f"for pair {trade.pair}.") + def manage_trade_stoploss_orders(self, trade, stoploss_orders): + """ + Check to see if stoploss on exchange should be updated + in case of trailing stoploss on exchange + :param trade: Corresponding Trade + :param stoploss_orders: Current on exchange stoploss orders + :return: None + """ + # If all stoploss orderd are canceled for some reason we add it again + canceled_sl_orders = [o for o in stoploss_orders if o.status in ['canceled', 'cancelled']] + if ( + trade.is_open and + len(stoploss_orders) > 0 and + len(stoploss_orders) == len(canceled_sl_orders) + ): + if self.create_stoploss_order(trade=trade, stop_price=trade.stoploss_or_liquidation): + return False + else: + logger.warning('All Stoploss orders are cancelled, but unable to recreate one.') + + active_sl_orders = [o for o in stoploss_orders if o not in canceled_sl_orders] + if len(active_sl_orders) > 0: + last_active_sl_order = active_sl_orders[-1] + # Finally we check if stoploss on exchange should be moved up because of trailing. + # Triggered Orders are now real orders - so don't replace stoploss anymore + if (trade.is_open and + last_active_sl_order.get('status_stop') != 'triggered' and + (self.config.get('trailing_stop', False) or + self.config.get('use_custom_stoploss', False))): + # if trailing stoploss is enabled we check if stoploss value has changed + # in which case we cancel stoploss order and put another one with new + # value immediately + self.handle_trailing_stoploss_on_exchange(trade, last_active_sl_order) + + # TODO cancel remaining_active_sl_orders active_sl_orders[:-1] + return + def manage_open_orders(self) -> None: """ Management of open orders on exchange. Unfilled orders might be cancelled if timeout diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index e483dcc24..ee6531030 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -544,6 +544,16 @@ class LocalTrade: ] return len(open_sl_orders) > 0 + @property + def sl_orders(self) -> List[Order]: + """ + All stoploss orders for this trade + """ + return [ + o for o in self.orders + if o.ft_order_side in ['stoploss'] + ] + @property def open_orders_ids(self) -> List[str]: open_orders_ids_wo_sl = [ From df8f1b93285b33599d1b4c26aeeeb575a2221afd Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Fri, 6 Oct 2023 15:58:51 -0400 Subject: [PATCH 007/247] update manage_trade_stoploss_orders description --- freqtrade/freqtradebot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 3338805f7..6f5ba41ae 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1299,8 +1299,7 @@ class FreqtradeBot(LoggingMixin): def manage_trade_stoploss_orders(self, trade, stoploss_orders): """ - Check to see if stoploss on exchange should be updated - in case of trailing stoploss on exchange + Perform required actions acording to existing stoploss orders of trade :param trade: Corresponding Trade :param stoploss_orders: Current on exchange stoploss orders :return: None From 2bb68ca53d23634ba6ff5a8a7005bff6866d85c6 Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Fri, 6 Oct 2023 16:08:40 -0400 Subject: [PATCH 008/247] remove stoploss_order_id from LocalTrade class --- freqtrade/persistence/trade_model.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index ee6531030..9c671d4b3 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -366,8 +366,6 @@ class LocalTrade: # percentage value of the initial stop loss initial_stop_loss_pct: Optional[float] = None is_stop_loss_trailing: bool = False - # stoploss order id which is on exchange - stoploss_order_id: Optional[str] = None # last update time of the stoploss order on exchange stoploss_last_update: Optional[datetime] = None # absolute value of the highest reached price From aaa82e1fa9c1f12db70526c463c55b1ac1c2221f Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Fri, 6 Oct 2023 16:34:03 -0400 Subject: [PATCH 009/247] remove all occurence of stoploss_order_id in trade_model, update api schemas, update rpc_delete --- freqtrade/persistence/trade_model.py | 6 ------ freqtrade/rpc/api_server/api_schemas.py | 1 - freqtrade/rpc/rpc.py | 17 +++++++++-------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 9c671d4b3..26834ae48 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -636,7 +636,6 @@ class LocalTrade: 'stop_loss_abs': self.stop_loss, 'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None, 'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None, - 'stoploss_order_id': self.stoploss_order_id, 'stoploss_last_update': (self.stoploss_last_update.strftime(DATETIME_PRINT_FORMAT) if self.stoploss_last_update else None), 'stoploss_last_update_timestamp': int(self.stoploss_last_update.replace( @@ -787,7 +786,6 @@ class LocalTrade: logger.info(f'{order.order_type.upper()}_{payment} has been fulfilled for {self}.') elif order.ft_order_side == 'stoploss' and order.status not in ('open', ): - self.stoploss_order_id = None self.close_rate_requested = self.stop_loss self.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value if self.is_open and order.safe_filled > 0: @@ -1378,9 +1376,6 @@ class Trade(ModelBase, LocalTrade): Float(), nullable=True) # type: ignore is_stop_loss_trailing: Mapped[bool] = mapped_column( nullable=False, default=False) # type: ignore - # stoploss order id which is on exchange - stoploss_order_id: Mapped[Optional[str]] = mapped_column( - String(255), nullable=True, index=True) # type: ignore # last update time of the stoploss order on exchange stoploss_last_update: Mapped[Optional[datetime]] = mapped_column(nullable=True) # type: ignore # absolute value of the highest reached price @@ -1805,7 +1800,6 @@ class Trade(ModelBase, LocalTrade): exit_order_status=data["exit_order_status"], stop_loss=data["stop_loss_abs"], stop_loss_pct=data["stop_loss_ratio"], - stoploss_order_id=data["stoploss_order_id"], stoploss_last_update=( datetime.fromtimestamp(data["stoploss_last_update_timestamp"] // 1000, tz=timezone.utc) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 97f6251bc..4f154f3a3 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -299,7 +299,6 @@ class TradeSchema(BaseModel): stop_loss_abs: Optional[float] = None stop_loss_ratio: Optional[float] = None stop_loss_pct: Optional[float] = None - stoploss_order_id: Optional[str] = None stoploss_last_update: Optional[str] = None stoploss_last_update_timestamp: Optional[int] = None initial_stop_loss_abs: Optional[float] = None diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 0abac3975..3ee4bbc91 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -978,15 +978,16 @@ class RPC: except (ExchangeError): pass - # cancel stoploss on exchange ... + # cancel stoploss on exchange orders ... if (self._freqtrade.strategy.order_types.get('stoploss_on_exchange') - and trade.stoploss_order_id): - try: - self._freqtrade.exchange.cancel_stoploss_order(trade.stoploss_order_id, - trade.pair) - c_count += 1 - except (ExchangeError): - pass + and trade.has_open_sl_orders): + + for oslo in trade.open_sl_orders: + try: + self._freqtrade.exchange.cancel_stoploss_order(oslo.order_id, trade.pair) + c_count += 1 + except (ExchangeError): + pass trade.delete() self._freqtrade.wallets.update() From e8be011e2bdd6c2960fa223520b31d6d45cbbff8 Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Fri, 6 Oct 2023 17:01:12 -0400 Subject: [PATCH 010/247] update manage_trade_stoploss_orders: remove unrelevant TODO --- freqtrade/freqtradebot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6f5ba41ae..834002495 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1330,7 +1330,6 @@ class FreqtradeBot(LoggingMixin): # value immediately self.handle_trailing_stoploss_on_exchange(trade, last_active_sl_order) - # TODO cancel remaining_active_sl_orders active_sl_orders[:-1] return def manage_open_orders(self) -> None: From c2b32769a19fb9dd04af654570b9ca6e74d4e496 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Nov 2023 06:48:20 +0100 Subject: [PATCH 011/247] Remove further occurance in bot file --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 852e71a6b..69b81a67c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1080,7 +1080,7 @@ class FreqtradeBot(LoggingMixin): if ( not trade.has_open_orders - and not trade.stoploss_order_id + and not trade.has_open_sl_orders and not self.wallets.check_exit_amount(trade) ): logger.warning( From 41e40e6214093ab89d2cebcbd27a0f5e2fd5eea3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Nov 2023 06:48:27 +0100 Subject: [PATCH 012/247] Update some initial tests --- tests/conftest_trades.py | 1 - tests/conftest_trades_usdt.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index a2276ae16..9ac43d73d 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -266,7 +266,6 @@ def mock_trade_5(fee, is_short: bool): exchange='binance', strategy='SampleStrategy', enter_tag='TEST1', - stoploss_order_id=f'prod_stoploss_{direc(is_short)}_3455', timeframe=5, is_short=is_short, stop_loss_pct=0.10, diff --git a/tests/conftest_trades_usdt.py b/tests/conftest_trades_usdt.py index d73a53605..cf3109090 100644 --- a/tests/conftest_trades_usdt.py +++ b/tests/conftest_trades_usdt.py @@ -282,7 +282,6 @@ def mock_trade_usdt_5(fee, is_short: bool): open_rate=2.0, exchange='binance', strategy='SampleStrategy', - stoploss_order_id=f'prod_stoploss_3455_{direc(is_short)}', timeframe=5, is_short=is_short, ) From 13780d5963ce6ee74268da2076bb78a2788ca678 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 22 Dec 2023 17:22:33 +0100 Subject: [PATCH 013/247] Remove further usage --- freqtrade/persistence/migrations.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index bb6c04922..a5e3c4640 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -91,7 +91,6 @@ def migrate_trades_and_orders_table( is_stop_loss_trailing = get_column_def( cols, 'is_stop_loss_trailing', f'coalesce({stop_loss_pct}, 0.0) <> coalesce({initial_stop_loss_pct}, 0.0)') - stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null') stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') max_rate = get_column_def(cols, 'max_rate', '0.0') min_rate = get_column_def(cols, 'min_rate', 'null') @@ -160,7 +159,7 @@ def migrate_trades_and_orders_table( open_rate_requested, close_rate, close_rate_requested, close_profit, stake_amount, amount, amount_requested, open_date, close_date, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, - is_stop_loss_trailing, stoploss_order_id, stoploss_last_update, + is_stop_loss_trailing, stoploss_last_update, max_rate, min_rate, exit_reason, exit_order_status, strategy, enter_tag, timeframe, open_trade_value, close_profit_abs, trading_mode, leverage, liquidation_price, is_short, @@ -180,7 +179,7 @@ def migrate_trades_and_orders_table( {initial_stop_loss} initial_stop_loss, {initial_stop_loss_pct} initial_stop_loss_pct, {is_stop_loss_trailing} is_stop_loss_trailing, - {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, + {stoploss_last_update} stoploss_last_update, {max_rate} max_rate, {min_rate} min_rate, case when {exit_reason} = 'sell_signal' then 'exit_signal' when {exit_reason} = 'custom_sell' then 'custom_exit' From b33a9059abfa3d5e50957096613ae60c16f5bc12 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 22 Dec 2023 17:35:02 +0100 Subject: [PATCH 014/247] Fix some more tests --- freqtrade/persistence/migrations.py | 24 ++++++++++++------------ tests/persistence/test_persistence.py | 2 -- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index a5e3c4640..fc67448eb 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -280,19 +280,19 @@ def fix_old_dry_orders(engine): # - current Trade is closed # - current Order trade_id not equal to current Trade.id # - current Order not stoploss + # TODO: is this still necessary ? how can this be done now ? + # stmt = update(Order).where( + # Order.ft_is_open.is_(True), + # tuple_(Order.ft_trade_id, Order.order_id).not_in( + # select( + # Trade.id, Trade.stoploss_order_id + # ).where(Trade.stoploss_order_id.is_not(None)) + # ), + # Order.ft_order_side == 'stoploss', + # Order.order_id.like('dry%'), - stmt = update(Order).where( - Order.ft_is_open.is_(True), - tuple_(Order.ft_trade_id, Order.order_id).not_in( - select( - Trade.id, Trade.stoploss_order_id - ).where(Trade.stoploss_order_id.is_not(None)) - ), - Order.ft_order_side == 'stoploss', - Order.order_id.like('dry%'), - - ).values(ft_is_open=False) - connection.execute(stmt) + # ).values(ft_is_open=False) + # connection.execute(stmt) # Close dry-run orders for closed trades. stmt = update(Order).where( diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index 5829f8b71..95db7bc0f 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -1432,7 +1432,6 @@ def test_to_json(fee): 'stop_loss_abs': None, 'stop_loss_ratio': None, 'stop_loss_pct': None, - 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss_abs': None, @@ -1500,7 +1499,6 @@ def test_to_json(fee): 'stop_loss_abs': None, 'stop_loss_pct': None, 'stop_loss_ratio': None, - 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss_abs': None, From 8234879b583f41a151ba214df12a85d6740253e7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 22 Dec 2023 17:38:14 +0100 Subject: [PATCH 015/247] stoploss_order_id removal tests --- tests/rpc/test_rpc.py | 1 - tests/rpc/test_rpc_apiserver.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 7ea9dae89..ebbc62af6 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -63,7 +63,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stop_loss_abs': 9.89e-06, 'stop_loss_pct': -10.0, 'stop_loss_ratio': -0.1, - 'stoploss_order_id': None, 'stoploss_last_update': ANY, 'stoploss_last_update_timestamp': ANY, 'initial_stop_loss_abs': 9.89e-06, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 17b0399d9..f89b06d39 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1175,7 +1175,6 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, 'stop_loss_abs': ANY, 'stop_loss_pct': ANY, 'stop_loss_ratio': ANY, - 'stoploss_order_id': None, 'stoploss_last_update': ANY, 'stoploss_last_update_timestamp': ANY, 'initial_stop_loss_abs': 0.0, @@ -1379,7 +1378,6 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): 'stop_loss_abs': None, 'stop_loss_pct': None, 'stop_loss_ratio': None, - 'stoploss_order_id': None, 'stoploss_last_update': None, 'stoploss_last_update_timestamp': None, 'initial_stop_loss_abs': None, From 15058d3ce675deac6dd3e6ac7e24bed89643c984 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Dec 2023 09:16:13 +0100 Subject: [PATCH 016/247] Add type hints to manage_trade_orders, fix content ... --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 816d35cdc..40cd6cecd 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1299,7 +1299,7 @@ class FreqtradeBot(LoggingMixin): logger.warning(f"Could not create trailing stoploss order " f"for pair {trade.pair}.") - def manage_trade_stoploss_orders(self, trade, stoploss_orders): + def manage_trade_stoploss_orders(self, trade: Trade, stoploss_orders: Dict): """ Perform required actions acording to existing stoploss orders of trade :param trade: Corresponding Trade @@ -1307,7 +1307,7 @@ class FreqtradeBot(LoggingMixin): :return: None """ # If all stoploss orderd are canceled for some reason we add it again - canceled_sl_orders = [o for o in stoploss_orders if o.status in ['canceled', 'cancelled']] + canceled_sl_orders = [o for o in stoploss_orders if o['status'] in ['canceled', 'cancelled']] if ( trade.is_open and len(stoploss_orders) > 0 and From 28e2bfaf1cbe374b04a9088d07ce715801429b06 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Dec 2023 09:25:35 +0100 Subject: [PATCH 017/247] Fix types of "has" calls --- freqtrade/persistence/trade_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 13578ff6a..a4a785c55 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -512,7 +512,7 @@ class LocalTrade: return [o for o in self.orders if o.ft_is_open and o.ft_order_side != 'stoploss'] @property - def has_open_orders(self) -> int: + def has_open_orders(self) -> bool: """ True if there are open orders for this trade excluding stoploss orders """ @@ -533,7 +533,7 @@ class LocalTrade: ] @property - def has_open_sl_orders(self) -> int: + def has_open_sl_orders(self) -> bool: """ True if there are open stoploss orders for this trade """ From c35b308adabcf7c73a77d23d95df77cdbc79f1a2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Dec 2023 09:25:39 +0100 Subject: [PATCH 018/247] Fix some tests --- tests/test_freqtradebot.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 06d40dfb0..61c63d064 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1118,12 +1118,11 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho freqtrade.enter_positions() trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short - trade.stoploss_order_id = None trade.is_open = True trades = [trade] freqtrade.exit_positions(trades) - assert trade.stoploss_order_id == '13434334' + assert trade.has_open_sl_orders is True assert stoploss.call_count == 1 assert trade.is_open is True @@ -1535,7 +1534,7 @@ def test_create_stoploss_order_invalid_order( caplog.clear() rpc_mock.reset_mock() freqtrade.create_stoploss_order(trade, 200) - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders is False assert trade.exit_reason == ExitType.EMERGENCY_EXIT.value assert log_has("Unable to place a stoploss order on exchange. ", caplog) assert log_has("Exiting the trade forcefully", caplog) @@ -1589,14 +1588,13 @@ def test_create_stoploss_order_insufficient_funds( caplog.clear() freqtrade.create_stoploss_order(trade, 200) # stoploss_orderid was empty before - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders is False assert mock_insuf.call_count == 1 mock_insuf.reset_mock() - trade.stoploss_order_id = 'stoploss_orderid' freqtrade.create_stoploss_order(trade, 200) # No change to stoploss-orderid - assert trade.stoploss_order_id == 'stoploss_orderid' + assert trade.has_open_sl_orders is False assert mock_insuf.call_count == 1 @@ -5679,7 +5677,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap trade = trades[1] reset_open_orders(trade) assert not trade.has_open_orders - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders is False freqtrade.handle_insufficient_funds(trade) order = trade.orders[0] @@ -5689,7 +5687,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap assert mock_uts.call_count == 0 # No change to orderid - as update_trade_state is mocked assert not trade.has_open_orders - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders is False caplog.clear() mock_fo.reset_mock() @@ -5700,7 +5698,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap # This part in not relevant anymore # assert not trade.has_open_orders - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders is False freqtrade.handle_insufficient_funds(trade) order = mock_order_4(is_short=is_short) @@ -5708,8 +5706,8 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap assert mock_fo.call_count == 1 assert mock_uts.call_count == 1 # Found open buy order - assert trade.has_open_orders - assert trade.stoploss_order_id is None + assert trade.has_open_orders is True + assert trade.has_open_sl_orders is False caplog.clear() mock_fo.reset_mock() @@ -5718,7 +5716,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap trade = trades[4] reset_open_orders(trade) assert not trade.has_open_orders - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders freqtrade.handle_insufficient_funds(trade) order = mock_order_5_stoploss(is_short=is_short) @@ -5727,7 +5725,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap assert mock_uts.call_count == 2 # stoploss_order_id is "refound" and added to the trade assert not trade.has_open_orders - assert trade.stoploss_order_id is not None + assert trade.has_open_sl_orders is True caplog.clear() mock_fo.reset_mock() @@ -5738,7 +5736,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap reset_open_orders(trade) # This part in not relevant anymore # assert not trade.has_open_orders - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders is False freqtrade.handle_insufficient_funds(trade) order = mock_order_6_sell(is_short=is_short) @@ -5747,7 +5745,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap assert mock_uts.call_count == 1 # sell-orderid is "refound" and added to the trade assert trade.open_orders_ids[0] == order['id'] - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders is False caplog.clear() From 6427144934983ee5c1ba25ac7277c0d6440e236e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Dec 2023 09:27:32 +0100 Subject: [PATCH 019/247] Fix stoploss test --- tests/test_freqtradebot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 61c63d064..a2337fa2a 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4130,11 +4130,11 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( freqtrade.manage_open_orders() trade = Trade.session.scalars(select(Trade)).first() trades = [trade] - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders is False freqtrade.exit_positions(trades) assert trade - assert trade.stoploss_order_id == '123' + assert trade.has_open_sl_orders is True assert not trade.has_open_orders # Assuming stoploss on exchange is hit @@ -4161,7 +4161,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_executed) freqtrade.exit_positions(trades) - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders is False assert trade.is_open is False assert trade.exit_reason == ExitType.STOPLOSS_ON_EXCHANGE.value assert rpc_mock.call_count == 4 From c6ffe82a7a72f1293b901d4685685dd0be429cdc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Dec 2023 09:29:45 +0100 Subject: [PATCH 020/247] Update more tests --- tests/test_freqtradebot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index a2337fa2a..ca8b6a4b9 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1345,11 +1345,11 @@ def test_handle_stoploss_on_exchange_partial( trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True - trade.stoploss_order_id = None assert freqtrade.handle_stoploss_on_exchange(trade) is False assert stoploss.call_count == 1 - assert trade.stoploss_order_id == "101" + assert trade.has_open_sl_orders is True + assert trade.open_sl_orders[-1].order_id == "101" assert trade.amount == 30 stop_order_dict.update({'id': "102"}) # Stoploss on exchange is cancelled on exchange, but filled partially. @@ -1369,7 +1369,7 @@ def test_handle_stoploss_on_exchange_partial( # Stoploss filled partially ... assert trade.amount == 15 - assert trade.stoploss_order_id == "102" + assert trade.open_sl_orders[-1].order_id == "102" @pytest.mark.parametrize("is_short", [False, True]) From cbfebd397c1149e9aa8543b9e7995788ba866de0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Dec 2023 09:47:33 +0100 Subject: [PATCH 021/247] Use a trade for test that actually has an open stop order --- tests/persistence/test_trade_fromjson.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/persistence/test_trade_fromjson.py b/tests/persistence/test_trade_fromjson.py index bb5e77f22..302a81c54 100644 --- a/tests/persistence/test_trade_fromjson.py +++ b/tests/persistence/test_trade_fromjson.py @@ -54,7 +54,6 @@ def test_trade_fromjson(): "stop_loss_abs": 0.1981, "stop_loss_ratio": -0.216, "stop_loss_pct": -21.6, - "stoploss_order_id": null, "stoploss_last_update": "2022-10-18 09:13:42", "stoploss_last_update_timestamp": 1666077222000, "initial_stop_loss_abs": 0.1981, From ae3f62cf9be4ae875de3ca48274c2146e60217e8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Dec 2023 09:50:42 +0100 Subject: [PATCH 022/247] Fix RPC tests --- tests/rpc/test_rpc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 869150e3d..4d43660e5 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -354,7 +354,6 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short): rpc._rpc_delete('200') trades = Trade.session.scalars(select(Trade)).all() - trades[2].stoploss_order_id = '102' trades[2].orders.append( Order( ft_order_side='stoploss', From 1db4732648de9fb269def2a88402f9da3af2fcab Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Dec 2023 09:57:05 +0100 Subject: [PATCH 023/247] Fix some more tests --- tests/test_freqtradebot.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index ca8b6a4b9..3467f037e 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1403,11 +1403,11 @@ def test_handle_stoploss_on_exchange_partial_cancel_here( trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True - trade.stoploss_order_id = None assert freqtrade.handle_stoploss_on_exchange(trade) is False assert stoploss.call_count == 1 - assert trade.stoploss_order_id == "101" + assert trade.has_open_sl_orders is True + assert trade.open_sl_orders[-1].order_id == "101" assert trade.amount == 30 stop_order_dict.update({'id': "102"}) # Stoploss on exchange is open. @@ -1440,7 +1440,8 @@ def test_handle_stoploss_on_exchange_partial_cancel_here( # Canceled Stoploss filled partially ... assert log_has_re('Cancelling current stoploss on exchange.*', caplog) - assert trade.stoploss_order_id == "102" + assert trade.has_open_sl_orders is True + assert trade.open_sl_orders[-1].order_id == "102" assert trade.amount == 15 @@ -4027,7 +4028,17 @@ def test_execute_trade_exit_sloe_cancel_exception( PairLock.session = MagicMock() freqtrade.config['dry_run'] = False - trade.stoploss_order_id = "abcd" + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_id='abcd', + status='open', + ) + ) freqtrade.execute_trade_exit(trade=trade, limit=1234, exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)) From 33bd433c2299cd98a9c7dead01a47c317e555d4b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Dec 2023 10:31:03 +0100 Subject: [PATCH 024/247] Don't run against all orders, only consider open sl orders. --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 40cd6cecd..fec2ed6a4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1219,7 +1219,7 @@ class FreqtradeBot(LoggingMixin): logger.debug('Handling stoploss on exchange %s ...', trade) stoploss_orders = [] - for slo in trade.sl_orders: + for slo in trade.open_sl_orders: stoploss_order = None try: # First we check if there is already a stoploss on exchange From 600e311b3ee116555d62fef0d6ab1dd4cfe58e0b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Dec 2023 10:35:30 +0100 Subject: [PATCH 025/247] Fix test test_handle_stoploss_on_exchange_custom_stop --- tests/test_freqtradebot.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 3467f037e..95635edff 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1866,6 +1866,7 @@ def test_handle_stoploss_on_exchange_custom_stop( exit_order, ]), get_fee=fee, + is_cancel_order_result_suitable=MagicMock(return_value=True), ) mocker.patch.multiple( EXMS, @@ -1896,7 +1897,6 @@ def test_handle_stoploss_on_exchange_custom_stop( trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True - trade.stoploss_order_id = '100' trade.stoploss_last_update = dt_now() - timedelta(minutes=601) trade.orders.append( Order( @@ -1908,8 +1908,8 @@ def test_handle_stoploss_on_exchange_custom_stop( order_id='100', ) ) - - stoploss_order_hanging = MagicMock(return_value={ + Trade.commit() + slo = { 'id': '100', 'status': 'open', 'type': 'stop_loss_limit', @@ -1918,9 +1918,17 @@ def test_handle_stoploss_on_exchange_custom_stop( 'info': { 'stopPrice': '2.0805' } - }) + } + slo_canceled = deepcopy(slo) + slo_canceled.update({'status': 'canceled'}) - mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hanging) + def fetch_stoploss_order_mock(order_id, *args, **kwargs): + x = deepcopy(slo) + x['id'] = order_id + return x + + mocker.patch(f'{EXMS}.fetch_stoploss_order', MagicMock(fetch_stoploss_order_mock)) + mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value=slo_canceled) assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False @@ -1939,7 +1947,6 @@ def test_handle_stoploss_on_exchange_custom_stop( stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'}) mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock) - trade.stoploss_order_id = '100' # stoploss should not be updated as the interval is 60 seconds assert freqtrade.handle_trade(trade) is False @@ -1953,10 +1960,12 @@ def test_handle_stoploss_on_exchange_custom_stop( # setting stoploss_on_exchange_interval to 0 seconds freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 + cancel_order_mock.assert_not_called() + stoploss_order_mock.assert_not_called() assert freqtrade.handle_stoploss_on_exchange(trade) is False - cancel_order_mock.assert_called_once_with('100', 'ETH/USDT') + cancel_order_mock.assert_called_once_with('13434334', 'ETH/USDT') # Long uses modified ask - offset, short modified bid + offset stoploss_order_mock.assert_called_once_with( amount=pytest.approx(trade.amount), From 68f9402384aad8db16418abd5c32c0e364f5eb26 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Dec 2023 10:44:21 +0100 Subject: [PATCH 026/247] Fix further test --- tests/test_freqtradebot.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 95635edff..a37d88fd6 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1759,9 +1759,6 @@ def test_handle_stoploss_on_exchange_trailing_error( {'id': exit_order['id']}, ]), get_fee=fee, - ) - mocker.patch.multiple( - EXMS, create_stoploss=stoploss, stoploss_adjust=MagicMock(return_value=True), ) @@ -1783,10 +1780,8 @@ def test_handle_stoploss_on_exchange_trailing_error( trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True - trade.stoploss_order_id = "abcd" trade.stop_loss = 0.2 trade.stoploss_last_update = (dt_now() - timedelta(minutes=601)).replace(tzinfo=None) - trade.is_short = is_short stoploss_order_hanging = { 'id': "abcd", @@ -1798,6 +1793,16 @@ def test_handle_stoploss_on_exchange_trailing_error( 'stopPrice': '0.1' } } + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=3, + order_id='abcd', + ) + ) mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=InvalidOrderException()) mocker.patch(f'{EXMS}.fetch_stoploss_order', @@ -1807,6 +1812,8 @@ def test_handle_stoploss_on_exchange_trailing_error( # Still try to create order assert stoploss.call_count == 1 + # TODO: Is this actually correct ? This will create a new order every time, + assert len(trade.open_sl_orders) == 2 # Fail creating stoploss order trade.stoploss_last_update = dt_now() - timedelta(minutes=601) @@ -1814,7 +1821,7 @@ def test_handle_stoploss_on_exchange_trailing_error( cancel_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order') mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError()) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) - assert cancel_mock.call_count == 1 + assert cancel_mock.call_count == 2 assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog) From a39b329e3baa84dbfe27658d087ccbefb7d2500e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Dec 2023 15:30:24 +0100 Subject: [PATCH 027/247] Fix line-length --- freqtrade/freqtradebot.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index fec2ed6a4..e414f9e82 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1307,12 +1307,13 @@ class FreqtradeBot(LoggingMixin): :return: None """ # If all stoploss orderd are canceled for some reason we add it again - canceled_sl_orders = [o for o in stoploss_orders if o['status'] in ['canceled', 'cancelled']] + canceled_sl_orders = [o for o in stoploss_orders + if o['status'] in ('canceled', 'cancelled')] if ( - trade.is_open and - len(stoploss_orders) > 0 and - len(stoploss_orders) == len(canceled_sl_orders) - ): + trade.is_open and + len(stoploss_orders) > 0 and + len(stoploss_orders) == len(canceled_sl_orders) + ): if self.create_stoploss_order(trade=trade, stop_price=trade.stoploss_or_liquidation): return False else: From f0073078e9a4950f84e78cc02d03b6b124055d79 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jan 2024 11:27:42 +0100 Subject: [PATCH 028/247] Fix stop order test --- tests/test_freqtradebot.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index a37d88fd6..bafb13a9f 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1468,7 +1468,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, ) mocker.patch.multiple( EXMS, - fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}), + fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': '100'}), create_stoploss=MagicMock(side_effect=ExchangeError()), ) freqtrade = FreqtradeBot(default_conf_usdt) @@ -1478,7 +1478,6 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, trade = Trade.session.scalars(select(Trade)).first() assert trade.is_short == is_short trade.is_open = True - trade.stoploss_order_id = "100" trade.orders.append( Order( ft_order_side='stoploss', @@ -1493,8 +1492,8 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, assert trade assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert log_has_re(r'Stoploss order was cancelled, but unable to recreate one.*', caplog) - assert trade.stoploss_order_id is None + assert log_has_re(r'All Stoploss orders are cancelled, but unable to recreate one\.', caplog) + assert trade.has_open_sl_orders is False assert trade.is_open is True From 501e256c587456d7c19eb822288a08a1969617d7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jan 2024 16:25:42 +0100 Subject: [PATCH 029/247] Fix further stoploss test --- tests/test_freqtradebot.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 703c06118..763ca8b48 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1656,7 +1656,7 @@ def test_handle_stoploss_on_exchange_trailing( trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True - trade.stoploss_order_id = '100' + assert trade.has_open_sl_orders is False trade.stoploss_last_update = dt_now() - timedelta(minutes=20) trade.orders.append( Order( @@ -1669,24 +1669,31 @@ def test_handle_stoploss_on_exchange_trailing( ) ) - stoploss_order_hanging = MagicMock(return_value={ + stoploss_order_hanging = { 'id': '100', 'status': 'open', 'type': 'stop_loss_limit', 'price': hang_price, 'average': 2, + 'fee': {}, + 'amount': 0, 'info': { 'stopPrice': stop_price[0] } - }) + } + stoploss_order_cancel = deepcopy(stoploss_order_hanging) + stoploss_order_cancel['status'] = 'canceled' - mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hanging) + mocker.patch(f'{EXMS}.fetch_stoploss_order', return_value=stoploss_order_hanging) + mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value=stoploss_order_cancel) # stoploss initially at 5% assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert trade.stoploss_order_id == '13434334' + assert len(trade.open_sl_orders) == 1 + + assert trade.open_sl_orders[-1].order_id == '13434334' # price jumped 2x mocker.patch( @@ -1698,14 +1705,17 @@ def test_handle_stoploss_on_exchange_trailing( }) ) - cancel_order_mock = MagicMock() + cancel_order_mock = MagicMock(return_value={ + 'id': '13434334', 'status': 'canceled', 'fee': {}, 'amount': trade.amount}) stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'}) + mocker.patch(f'{EXMS}.fetch_stoploss_order') mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock) # stoploss should not be updated as the interval is 60 seconds assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert len(trade.open_sl_orders) == 1 cancel_order_mock.assert_not_called() stoploss_order_mock.assert_not_called() @@ -1736,8 +1746,14 @@ def test_handle_stoploss_on_exchange_trailing( 'last': bid[1], }) ) + mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', + return_value={'id': 'so1', 'status': 'canceled'}) + assert len(trade.open_sl_orders) == 1 + assert trade.open_sl_orders[-1].order_id == 'so1' + assert freqtrade.handle_trade(trade) is True - assert trade.stoploss_order_id is None + assert trade.is_open is False + assert trade.has_open_sl_orders is False @pytest.mark.parametrize("is_short", [False, True]) From 206809d2e7a8e9876bec38c6aeefd9bf02d3d3c8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jan 2024 17:05:21 +0100 Subject: [PATCH 030/247] Update emergency sell test --- tests/test_freqtradebot.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index d07c1db23..33b0828a9 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1279,7 +1279,7 @@ def test_handle_stoploss_on_exchange_emergency(mocker, default_conf_usdt, fee, i trade = Trade.session.scalars(select(Trade)).first() assert trade.is_short == is_short assert trade.is_open - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders is False # emergency exit triggered # Trailing stop should not act anymore @@ -1294,7 +1294,6 @@ def test_handle_stoploss_on_exchange_emergency(mocker, default_conf_usdt, fee, i 'remaining': enter_order['amount'], 'info': {'stopPrice': 22}, }]) - trade.stoploss_order_id = "107" trade.stoploss_last_update = dt_now() - timedelta(hours=1) trade.stop_loss = 24 trade.exit_reason = None @@ -1311,14 +1310,14 @@ def test_handle_stoploss_on_exchange_emergency(mocker, default_conf_usdt, fee, i ) freqtrade.config['trailing_stop'] = True stoploss = MagicMock(side_effect=InvalidOrderException()) - + assert trade.has_open_sl_orders is True Trade.commit() mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', side_effect=InvalidOrderException()) mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_cancelled) mocker.patch(f'{EXMS}.create_stoploss', stoploss) assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders is False assert trade.is_open is False assert trade.exit_reason == str(ExitType.EMERGENCY_EXIT) From e199083287f9311d11400699b549620e613af6f2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jan 2024 17:10:33 +0100 Subject: [PATCH 031/247] Fix test ... --- tests/test_freqtradebot.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 33b0828a9..6738fc5ae 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1161,11 +1161,11 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ trade = Trade.session.scalars(select(Trade)).first() assert trade.is_short == is_short assert trade.is_open - assert trade.stoploss_order_id is None + assert trade.has_open_sl_orders is False assert freqtrade.handle_stoploss_on_exchange(trade) is False assert stoploss.call_count == 1 - assert trade.stoploss_order_id == "13434334" + assert trade.open_sl_orders[-1].order_id == "13434334" # Second case: when stoploss is set but it is not yet hit # should do nothing and return false @@ -1176,7 +1176,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ assert freqtrade.handle_stoploss_on_exchange(trade) is False hanging_stoploss_order.assert_called_once_with('13434334', trade.pair) - assert trade.stoploss_order_id == "13434334" + assert len(trade.open_sl_orders) == 1 + assert trade.open_sl_orders[-1].order_id == "13434334" # Third case: when stoploss was set but it was canceled for some reason # should set a stoploss immediately and return False @@ -1192,7 +1193,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ assert freqtrade.handle_stoploss_on_exchange(trade) is False assert stoploss.call_count == 1 - assert trade.stoploss_order_id == "103_1" + assert len(trade.open_sl_orders) == 1 + assert trade.open_sl_orders[-1].order_id == "103_1" assert trade.amount == amount_before # Fourth case: when stoploss is set and it is hit @@ -1218,7 +1220,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit) assert freqtrade.handle_stoploss_on_exchange(trade) is True assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog) - assert trade.stoploss_order_id is None + assert len(trade.open_sl_orders) == 0 assert trade.is_open is False caplog.clear() @@ -1226,26 +1228,27 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ trade.is_open = True freqtrade.handle_stoploss_on_exchange(trade) assert log_has('Unable to place a stoploss order on exchange.', caplog) - assert trade.stoploss_order_id is None + assert len(trade.open_sl_orders) == 0 # Fifth case: fetch_order returns InvalidOrder # It should try to add stoploss order stop_order_dict.update({'id': "105"}) - trade.stoploss_order_id = "105" stoploss.reset_mock() mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=InvalidOrderException()) mocker.patch(f'{EXMS}.create_stoploss', stoploss) freqtrade.handle_stoploss_on_exchange(trade) + assert len(trade.open_sl_orders) == 1 assert stoploss.call_count == 1 # Sixth case: Closed Trade # Should not create new order - trade.stoploss_order_id = None trade.is_open = False + trade.open_sl_orders[-1].ft_is_open = False stoploss.reset_mock() mocker.patch(f'{EXMS}.fetch_order') mocker.patch(f'{EXMS}.create_stoploss', stoploss) assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert trade.has_open_sl_orders is False assert stoploss.call_count == 0 From 14660f54f8fca7767155746ab3de05bca99ca9fd Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jan 2024 19:22:16 +0100 Subject: [PATCH 032/247] Remove duplicate call to update_trade_state --- freqtrade/freqtradebot.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0b30d89a4..3b01f7756 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1229,8 +1229,6 @@ class FreqtradeBot(LoggingMixin): # We check if stoploss order is fulfilled if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'): trade.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value - self.update_trade_state(trade, slo.order_id, stoploss_order, - stoploss_order=True) self._notify_exit(trade, "stoploss", True) self.handle_protections(trade.pair, trade.trade_direction) return True From dc9c4da95e8f38fb8a249cea6be068b6bf68eb2c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Jan 2024 19:22:46 +0100 Subject: [PATCH 033/247] Improve integration test stability --- tests/test_integration.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 2e7f38fc8..ffb955f11 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -49,7 +49,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, stoploss_order_closed['filled'] = stoploss_order_closed['amount'] # Sell first trade based on stoploss, keep 2nd and 3rd trade open - stop_orders = [stoploss_order_closed, stoploss_order_open, stoploss_order_open] + stop_orders = [stoploss_order_closed, stoploss_order_open.copy(), stoploss_order_open.copy()] stoploss_order_mock = MagicMock( side_effect=stop_orders) # Sell 3rd trade (not called for the first trade) @@ -100,9 +100,10 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, stop_order = stop_orders[idx] stop_order['id'] = f"stop{idx}" oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, 'stoploss') + oobj.ft_is_open = True trade.orders.append(oobj) - trade.stoploss_order_id = f"stop{idx}" + assert len(trade.open_sl_orders) == 1 n = freqtrade.exit_positions(trades) assert n == 2 @@ -113,6 +114,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, # Only order for 3rd trade needs to be cancelled assert cancel_order_mock.call_count == 1 + assert stoploss_order_mock.call_count == 3 # Wallets must be updated between stoploss cancellation and selling, and will be updated again # during update_trade_state assert wallets_mock.call_count == 4 From 1e4920059650cba27d91999dfc311de0a21af07b Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sun, 7 Jan 2024 20:16:44 +0900 Subject: [PATCH 034/247] early stage of marketcapfilter --- freqtrade/constants.py | 7 +- freqtrade/plugins/pairlist/MarketCapFilter.py | 119 ++++++++++++++++++ 2 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 freqtrade/plugins/pairlist/MarketCapFilter.py diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 71081433e..d7767dc2e 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -33,9 +33,10 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'MaxDrawDownHyperOptLoss', 'MaxDrawDownRelativeHyperOptLoss', 'ProfitDrawDownHyperOptLoss'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'ProducerPairList', 'RemotePairList', - 'AgeFilter', "FullTradesFilter", 'OffsetFilter', 'PerformanceFilter', - 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', - 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] + 'AgeFilter', "FullTradesFilter", 'MarketCapFilter', 'OffsetFilter', + 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', + 'RangeStabilityFilter', 'ShuffleFilter', 'SpreadFilter', + 'VolatilityFilter'] AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5', 'feather', 'parquet'] diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py new file mode 100644 index 000000000..6238b6211 --- /dev/null +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -0,0 +1,119 @@ +""" +Market Cap PairList provider + +Provides dynamic pair list based on Market Cap +""" +import logging +from datetime import timedelta +from typing import Any, Dict, List, Literal + +from cachetools import TTLCache + +from freqtrade.constants import Config, ListPairsWithTimeframes +from freqtrade.exceptions import OperationalException +from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date +from freqtrade.exchange.types import Tickers +from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter +from freqtrade.util import dt_now, format_ms_time + +from pycoingecko import CoinGeckoAPI + +logger = logging.getLogger(__name__) + + +SORT_VALUES = ['quoteVolume'] + + +class MarketCapFilter(IPairList): + + is_pairlist_generator = True + + def __init__(self, exchange, pairlistmanager, + config: Config, pairlistconfig: Dict[str, Any], + pairlist_pos: int) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) + + if 'max_rank' not in self._pairlistconfig: + raise OperationalException( + '`max_rank` not specified. Please check your configuration ' + 'for "pairlist.config.max_rank"') + + self._stake_currency = config['stake_currency'] + self._max_rank = self._pairlistconfig['max_rank'] + self._refresh_period = self._pairlistconfig.get('refresh_period', 86400) + self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) + self._def_candletype = self._config['candle_type_def'] + self._coingekko: CoinGeckoAPI = CoinGeckoAPI() + + if self._max_rank > 250: + raise OperationalException( + "This filter only support up to rank 250." + ) + + + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requires tickers, an empty Dict is passed + as tickers argument to filter_pairlist + """ + return False + + def _validate_keys(self, key): + return key in SORT_VALUES + + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + """ + return f"{self.name} - Only use top {self._pairlistconfig['max_rank']} market cap pairs." + + @staticmethod + def description() -> str: + return "Filter pair list based on market cap." + + @staticmethod + def available_parameters() -> Dict[str, PairlistParameter]: + return { + "max_rank": { + "type": "number", + "default": 30, + "description": "Max market cap rank", + "help": "Only use assets that ranked within top max_rank market cap", + }, + "refresh_period": { + "type": "number", + "default": 86400, + "description": "Refresh period", + "help": "Refresh period in seconds", + } + } + + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: + """ + Filters and sorts pairlist and returns the whitelist again. + Called on each bot iteration - please use internal caching if necessary + :param pairlist: pairlist to filter or sort + :param tickers: Tickers (from exchange.get_tickers). May be cached. + :return: new whitelist + """ + marketcap_list = self._marketcap_cache.get('marketcap') + can_filter = False + + if marketcap_list: + can_filter = True + else: + data = self._coingekko.get_coins_markets(vs_currencies='usd', order='market_cap_desc', + per_page='250', page='1', sparkline='false', + locale='en') + if data: + pairs_data = [] + for row in data: + pairs_data.append(row['symbol']) + + if len(pairs_data) > 0: + logger.info(pairs_data) + self._marketcap_cache['marketcap'] = pairs_data + + return pairlist From 70cc2942d7632d1ee1af02affba44b862850a8c4 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sun, 7 Jan 2024 23:10:18 +0900 Subject: [PATCH 035/247] wrong arg name --- freqtrade/plugins/pairlist/MarketCapFilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index 6238b6211..a98f435bb 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -104,7 +104,7 @@ class MarketCapFilter(IPairList): if marketcap_list: can_filter = True else: - data = self._coingekko.get_coins_markets(vs_currencies='usd', order='market_cap_desc', + data = self._coingekko.get_coins_markets(vs_currency='usd', order='market_cap_desc', per_page='250', page='1', sparkline='false', locale='en') if data: From 2eb3b73a934527c04f7ce68ff2f604cced24d594 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sun, 7 Jan 2024 23:22:58 +0900 Subject: [PATCH 036/247] cut the list to top x --- freqtrade/plugins/pairlist/MarketCapFilter.py | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index a98f435bb..4650a8c35 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -108,12 +108,22 @@ class MarketCapFilter(IPairList): per_page='250', page='1', sparkline='false', locale='en') if data: - pairs_data = [] + marketcap_list = [] for row in data: - pairs_data.append(row['symbol']) + marketcap_list.append(row['symbol']) + + if len(marketcap_list) > 0: + self._marketcap_cache['marketcap'] = marketcap_list + can_filter = True + + + if can_filter: + filtered_pairlist = [] + top_marketcap = marketcap_list[:self._max_rank:] + logger.info(top_marketcap) + logger.info(len(top_marketcap)) + + # for pair in pairlist: - if len(pairs_data) > 0: - logger.info(pairs_data) - self._marketcap_cache['marketcap'] = pairs_data return pairlist From 1cd08c96a1030bf870e875d80695acca405c1245 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sun, 7 Jan 2024 23:30:11 +0900 Subject: [PATCH 037/247] filter the pairlist --- freqtrade/plugins/pairlist/MarketCapFilter.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index 4650a8c35..c922402cd 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -120,10 +120,14 @@ class MarketCapFilter(IPairList): if can_filter: filtered_pairlist = [] top_marketcap = marketcap_list[:self._max_rank:] - logger.info(top_marketcap) - logger.info(len(top_marketcap)) - # for pair in pairlist: + for pair in pairlist: + base = pair.split('/')[0] + if base.lower() in top_marketcap: + filtered_pairlist.append(pair) + + if len(filtered_pairlist) > 0: + return filtered_pairlist return pairlist From a5862dcc4a47d38ad0222374895c8507fd6598d8 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sun, 7 Jan 2024 23:33:34 +0900 Subject: [PATCH 038/247] add log message --- freqtrade/plugins/pairlist/MarketCapFilter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index c922402cd..0d28671ef 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -125,6 +125,9 @@ class MarketCapFilter(IPairList): base = pair.split('/')[0] if base.lower() in top_marketcap: filtered_pairlist.append(pair) + else: + logger.info(f"Remove {pair} from whitelist because it's not in " + f"top {self._max_rank} market cap") if len(filtered_pairlist) > 0: return filtered_pairlist From e10ab8c543afa905879a74356a0e993d39eda325 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 00:04:58 +0900 Subject: [PATCH 039/247] test gen_pairlist --- freqtrade/plugins/pairlist/MarketCapFilter.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index 0d28671ef..5b48ed2f5 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -90,6 +90,41 @@ class MarketCapFilter(IPairList): } } + def gen_pairlist(self, tickers: Tickers) -> List[str]: + """ + Generate the pairlist + :param tickers: Tickers (from exchange.get_tickers). May be cached. + :return: List of pairs + """ + # Generate dynamic whitelist + # Must always run if this pairlist is not the first in the list. + pairlist = self._marketcap_cache.get('pairlist_mc') + if pairlist: + # Item found - no refresh necessary + return pairlist.copy() + else: + # Use fresh pairlist + # Check if pair quote currency equals to the stake currency. + _pairlist = [k for k in self._exchange.get_markets( + quote_currencies=[self._stake_currency], + tradable_only=True, active_only=True).keys()] + # No point in testing for blacklisted pairs... + _pairlist = self.verify_blacklist(_pairlist, logger.info) + # if not self._use_range: + # filtered_tickers = [ + # v for k, v in tickers.items() + # if (self._exchange.get_pair_quote_currency(k) == self._stake_currency + # and (self._use_range or v.get(self._sort_key) is not None) + # and v['symbol'] in _pairlist)] + # pairlist = [s['symbol'] for s in filtered_tickers] + # else: + # pairlist = _pairlist + + pairlist = self.filter_pairlist(_pairlist, tickers) + self._marketcap_cache['pairlist_mc'] = pairlist.copy() + + return pairlist + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ Filters and sorts pairlist and returns the whitelist again. From ef528fa69c3e9eb4387dac55eea3c620ae243efa Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 00:14:55 +0900 Subject: [PATCH 040/247] option B --- freqtrade/plugins/pairlist/MarketCapFilter.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index 5b48ed2f5..bd58a7a11 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -154,15 +154,25 @@ class MarketCapFilter(IPairList): if can_filter: filtered_pairlist = [] - top_marketcap = marketcap_list[:self._max_rank:] - for pair in pairlist: - base = pair.split('/')[0] - if base.lower() in top_marketcap: - filtered_pairlist.append(pair) - else: - logger.info(f"Remove {pair} from whitelist because it's not in " - f"top {self._max_rank} market cap") + # option A + # top_marketcap = marketcap_list[:self._max_rank:] + + # for pair in pairlist: + # base = pair.split('/')[0] + # if base.lower() in top_marketcap: + # filtered_pairlist.append(pair) + # else: + # logger.info(f"Remove {pair} from whitelist because it's not in " + # f"top {self._max_rank} market cap") + + # option B + for mc_pair in marketcap_list: + test_pair = f"{mc_pair.upper()}/{self._stake_currency.upper}" + if test_pair in pairlist: + filtered_pairlist.append(test_pair) + if len(filtered_pairlist) == self._max_rank: + break if len(filtered_pairlist) > 0: return filtered_pairlist From adf07dd3e133476aa255403ab96e66cb29bf3429 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 00:16:16 +0900 Subject: [PATCH 041/247] remove cache for noe --- freqtrade/plugins/pairlist/MarketCapFilter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index bd58a7a11..a3b794c6f 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -98,7 +98,7 @@ class MarketCapFilter(IPairList): """ # Generate dynamic whitelist # Must always run if this pairlist is not the first in the list. - pairlist = self._marketcap_cache.get('pairlist_mc') + # pairlist = self._marketcap_cache.get('pairlist_mc') if pairlist: # Item found - no refresh necessary return pairlist.copy() @@ -121,7 +121,7 @@ class MarketCapFilter(IPairList): # pairlist = _pairlist pairlist = self.filter_pairlist(_pairlist, tickers) - self._marketcap_cache['pairlist_mc'] = pairlist.copy() + # self._marketcap_cache['pairlist_mc'] = pairlist.copy() return pairlist From bb684dd6137ed9001b0cc3e5774c6fed9c33fb9e Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 00:16:49 +0900 Subject: [PATCH 042/247] empty pairlist --- freqtrade/plugins/pairlist/MarketCapFilter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index a3b794c6f..9775d50c9 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -99,6 +99,7 @@ class MarketCapFilter(IPairList): # Generate dynamic whitelist # Must always run if this pairlist is not the first in the list. # pairlist = self._marketcap_cache.get('pairlist_mc') + pairlist=[] if pairlist: # Item found - no refresh necessary return pairlist.copy() From 35467619c45d953fbb4fc5c9004d130ca17de26b Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 00:20:09 +0900 Subject: [PATCH 043/247] add logger --- freqtrade/plugins/pairlist/MarketCapFilter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index 9775d50c9..c2df8921c 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -170,8 +170,11 @@ class MarketCapFilter(IPairList): # option B for mc_pair in marketcap_list: test_pair = f"{mc_pair.upper()}/{self._stake_currency.upper}" + logger.info(f"Check test pair name {test_pair}") if test_pair in pairlist: + logger.info(f"{test_pair} is in pairlist") filtered_pairlist.append(test_pair) + logger.info(f"{len(filtered_pairlist)} pairs in filtered pairlist") if len(filtered_pairlist) == self._max_rank: break From 1f0a2ab242fab892159e3577b06440171103cae5 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 00:21:07 +0900 Subject: [PATCH 044/247] forgot () --- freqtrade/plugins/pairlist/MarketCapFilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index c2df8921c..dca8c911e 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -169,7 +169,7 @@ class MarketCapFilter(IPairList): # option B for mc_pair in marketcap_list: - test_pair = f"{mc_pair.upper()}/{self._stake_currency.upper}" + test_pair = f"{mc_pair.upper()}/{self._stake_currency.upper()}" logger.info(f"Check test pair name {test_pair}") if test_pair in pairlist: logger.info(f"{test_pair} is in pairlist") From 09cb6cb02d632d731ffa98bf82acb1bbffde5699 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 00:21:58 +0900 Subject: [PATCH 045/247] remove debug --- freqtrade/plugins/pairlist/MarketCapFilter.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index dca8c911e..c9dfa7769 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -170,11 +170,8 @@ class MarketCapFilter(IPairList): # option B for mc_pair in marketcap_list: test_pair = f"{mc_pair.upper()}/{self._stake_currency.upper()}" - logger.info(f"Check test pair name {test_pair}") if test_pair in pairlist: - logger.info(f"{test_pair} is in pairlist") filtered_pairlist.append(test_pair) - logger.info(f"{len(filtered_pairlist)} pairs in filtered pairlist") if len(filtered_pairlist) == self._max_rank: break From 96a6c00acc80adf3a9aa01b5c8acbef87d2c325c Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 00:42:33 +0900 Subject: [PATCH 046/247] add mode --- freqtrade/plugins/pairlist/MarketCapFilter.py | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index c9dfa7769..1231d7752 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -21,7 +21,7 @@ from pycoingecko import CoinGeckoAPI logger = logging.getLogger(__name__) -SORT_VALUES = ['quoteVolume'] +MODE_VALUES = ['top_rank', 'total_assets'] class MarketCapFilter(IPairList): @@ -33,23 +33,28 @@ class MarketCapFilter(IPairList): pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) - if 'max_rank' not in self._pairlistconfig: + if 'limit' not in self._pairlistconfig: raise OperationalException( - '`max_rank` not specified. Please check your configuration ' - 'for "pairlist.config.max_rank"') + '`limit` not specified. Please check your configuration ' + 'for "pairlist.config.limit"') self._stake_currency = config['stake_currency'] - self._max_rank = self._pairlistconfig['max_rank'] + self._mode = self._pairlistconfig.get('mode', 'top_rank') + self._limit = self._pairlistconfig['limit'] self._refresh_period = self._pairlistconfig.get('refresh_period', 86400) self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) self._def_candletype = self._config['candle_type_def'] self._coingekko: CoinGeckoAPI = CoinGeckoAPI() - if self._max_rank > 250: + if self._limit > 250: raise OperationalException( "This filter only support up to rank 250." ) + if not self._validate_keys(self._mode): + raise OperationalException( + f'key {self._mode} not in {MODE_VALUES}') + @property def needstickers(self) -> bool: @@ -61,13 +66,13 @@ class MarketCapFilter(IPairList): return False def _validate_keys(self, key): - return key in SORT_VALUES + return key in MODE_VALUES def short_desc(self) -> str: """ Short whitelist method description - used for startup-messages """ - return f"{self.name} - Only use top {self._pairlistconfig['max_rank']} market cap pairs." + return f"{self.name} - Only use top {self._pairlistconfig['limit']} market cap pairs." @staticmethod def description() -> str: @@ -76,11 +81,18 @@ class MarketCapFilter(IPairList): @staticmethod def available_parameters() -> Dict[str, PairlistParameter]: return { - "max_rank": { + "limit": { "type": "number", "default": 30, "description": "Max market cap rank", - "help": "Only use assets that ranked within top max_rank market cap", + "help": "Only use assets with high market cap rank", + }, + "mode": { + "type": "option", + "default": "top_rank", + "options": MODE_VALUES, + "description": "Mode of number", + "help": "How to interpret the number", }, "refresh_period": { "type": "number", @@ -156,24 +168,24 @@ class MarketCapFilter(IPairList): if can_filter: filtered_pairlist = [] - # option A - # top_marketcap = marketcap_list[:self._max_rank:] + if self._mode == 'top_rank': + top_marketcap = marketcap_list[:self._limit:] - # for pair in pairlist: - # base = pair.split('/')[0] - # if base.lower() in top_marketcap: - # filtered_pairlist.append(pair) - # else: - # logger.info(f"Remove {pair} from whitelist because it's not in " - # f"top {self._max_rank} market cap") + for pair in pairlist: + base = pair.split('/')[0] + if base.lower() in top_marketcap: + filtered_pairlist.append(pair) + else: + logger.info(f"Remove {pair} from whitelist because it's not ranked " + f"within top {self._limit} market cap") - # option B - for mc_pair in marketcap_list: - test_pair = f"{mc_pair.upper()}/{self._stake_currency.upper()}" - if test_pair in pairlist: - filtered_pairlist.append(test_pair) - if len(filtered_pairlist) == self._max_rank: - break + else: + for mc_pair in marketcap_list: + test_pair = f"{mc_pair.upper()}/{self._stake_currency.upper()}" + if test_pair in pairlist: + filtered_pairlist.append(test_pair) + if len(filtered_pairlist) == self._limit: + break if len(filtered_pairlist) > 0: return filtered_pairlist From 8055140e665313478c7a760a5662971df8b9df14 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 00:53:37 +0900 Subject: [PATCH 047/247] simplify code --- freqtrade/plugins/pairlist/MarketCapFilter.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index 1231d7752..0553dcc68 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -156,9 +156,7 @@ class MarketCapFilter(IPairList): per_page='250', page='1', sparkline='false', locale='en') if data: - marketcap_list = [] - for row in data: - marketcap_list.append(row['symbol']) + marketcap_list = [row['symbol'] for row in data] if len(marketcap_list) > 0: self._marketcap_cache['marketcap'] = marketcap_list From f29fcb31e03b74720a8f8d273e62d520c26cbb58 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 00:54:21 +0900 Subject: [PATCH 048/247] debug --- freqtrade/plugins/pairlist/MarketCapFilter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index 0553dcc68..7632117ab 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -146,7 +146,8 @@ class MarketCapFilter(IPairList): :param tickers: Tickers (from exchange.get_tickers). May be cached. :return: new whitelist """ - marketcap_list = self._marketcap_cache.get('marketcap') + # marketcap_list = self._marketcap_cache.get('marketcap') + marketcap_list = [] can_filter = False if marketcap_list: From 5bb4824aaefff30cd48d1532e9d5973eed6cc864 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 00:58:39 +0900 Subject: [PATCH 049/247] add log print to see the time --- freqtrade/plugins/pairlist/MarketCapFilter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index 7632117ab..cae971762 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -118,6 +118,7 @@ class MarketCapFilter(IPairList): else: # Use fresh pairlist # Check if pair quote currency equals to the stake currency. + logger.info("Get active pairs for the market") _pairlist = [k for k in self._exchange.get_markets( quote_currencies=[self._stake_currency], tradable_only=True, active_only=True).keys()] @@ -153,6 +154,7 @@ class MarketCapFilter(IPairList): if marketcap_list: can_filter = True else: + logger.info("Get top 250 marketcap coins from coingecko") data = self._coingekko.get_coins_markets(vs_currency='usd', order='market_cap_desc', per_page='250', page='1', sparkline='false', locale='en') @@ -167,6 +169,8 @@ class MarketCapFilter(IPairList): if can_filter: filtered_pairlist = [] + logger.info("Create filtered pairlist") + if self._mode == 'top_rank': top_marketcap = marketcap_list[:self._limit:] From d578c910bccb769bb3fd654ef1a2a6cfe13db59c Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 15:09:42 +0900 Subject: [PATCH 050/247] remove unused lines --- freqtrade/plugins/pairlist/MarketCapFilter.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index cae971762..b9b9eb067 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -48,7 +48,7 @@ class MarketCapFilter(IPairList): if self._limit > 250: raise OperationalException( - "This filter only support up to rank 250." + "This filter only support limit value up to 250." ) if not self._validate_keys(self._mode): @@ -124,15 +124,6 @@ class MarketCapFilter(IPairList): tradable_only=True, active_only=True).keys()] # No point in testing for blacklisted pairs... _pairlist = self.verify_blacklist(_pairlist, logger.info) - # if not self._use_range: - # filtered_tickers = [ - # v for k, v in tickers.items() - # if (self._exchange.get_pair_quote_currency(k) == self._stake_currency - # and (self._use_range or v.get(self._sort_key) is not None) - # and v['symbol'] in _pairlist)] - # pairlist = [s['symbol'] for s in filtered_tickers] - # else: - # pairlist = _pairlist pairlist = self.filter_pairlist(_pairlist, tickers) # self._marketcap_cache['pairlist_mc'] = pairlist.copy() From 442ca86d1508b65552d0b18de34a3445b459d357 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 15:33:43 +0900 Subject: [PATCH 051/247] add market check --- freqtrade/plugins/pairlist/MarketCapFilter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index b9b9eb067..663eac0e3 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -174,8 +174,10 @@ class MarketCapFilter(IPairList): f"within top {self._limit} market cap") else: + market = self._config['trading_mode'] + pair_format = f"{self._stake_currency.upper()}" if (market == 'spot') else f"{self._stake_currency.upper()}:{self._stake_currency.upper()}" for mc_pair in marketcap_list: - test_pair = f"{mc_pair.upper()}/{self._stake_currency.upper()}" + test_pair = f"{mc_pair.upper()}/{pair_format}" if test_pair in pairlist: filtered_pairlist.append(test_pair) if len(filtered_pairlist) == self._limit: From fdf9cc1b76bcee8f3f184f70942fc2019be3392d Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 16:02:31 +0900 Subject: [PATCH 052/247] tidy up codes --- freqtrade/plugins/pairlist/MarketCapFilter.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index 663eac0e3..1cf8b7b7c 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -11,7 +11,6 @@ from cachetools import TTLCache from freqtrade.constants import Config, ListPairsWithTimeframes from freqtrade.exceptions import OperationalException -from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date from freqtrade.exchange.types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter from freqtrade.util import dt_now, format_ms_time @@ -110,8 +109,7 @@ class MarketCapFilter(IPairList): """ # Generate dynamic whitelist # Must always run if this pairlist is not the first in the list. - # pairlist = self._marketcap_cache.get('pairlist_mc') - pairlist=[] + pairlist = self._marketcap_cache.get('pairlist_mc') if pairlist: # Item found - no refresh necessary return pairlist.copy() @@ -126,7 +124,7 @@ class MarketCapFilter(IPairList): _pairlist = self.verify_blacklist(_pairlist, logger.info) pairlist = self.filter_pairlist(_pairlist, tickers) - # self._marketcap_cache['pairlist_mc'] = pairlist.copy() + self._marketcap_cache['pairlist_mc'] = pairlist.copy() return pairlist @@ -138,8 +136,7 @@ class MarketCapFilter(IPairList): :param tickers: Tickers (from exchange.get_tickers). May be cached. :return: new whitelist """ - # marketcap_list = self._marketcap_cache.get('marketcap') - marketcap_list = [] + marketcap_list = self._marketcap_cache.get('marketcap') can_filter = False if marketcap_list: @@ -175,7 +172,8 @@ class MarketCapFilter(IPairList): else: market = self._config['trading_mode'] - pair_format = f"{self._stake_currency.upper()}" if (market == 'spot') else f"{self._stake_currency.upper()}:{self._stake_currency.upper()}" + pair_format = f"{self._stake_currency.upper()}" if (market == 'spot') + else f"{self._stake_currency.upper()}:{self._stake_currency.upper()}" for mc_pair in marketcap_list: test_pair = f"{mc_pair.upper()}/{pair_format}" if test_pair in pairlist: @@ -186,5 +184,4 @@ class MarketCapFilter(IPairList): if len(filtered_pairlist) > 0: return filtered_pairlist - return pairlist From ef8712f4d967a5d3f71dafc432a34b4569dd694c Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 16:04:30 +0900 Subject: [PATCH 053/247] tidy up --- freqtrade/plugins/pairlist/MarketCapFilter.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapFilter.py index 1cf8b7b7c..a8c1c2080 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapFilter.py @@ -172,8 +172,9 @@ class MarketCapFilter(IPairList): else: market = self._config['trading_mode'] - pair_format = f"{self._stake_currency.upper()}" if (market == 'spot') - else f"{self._stake_currency.upper()}:{self._stake_currency.upper()}" + pair_format = f"{self._stake_currency.upper()}" + if (market == 'futures'): + pair_format += f":{self._stake_currency.upper()}" for mc_pair in marketcap_list: test_pair = f"{mc_pair.upper()}/{pair_format}" if test_pair in pairlist: From bc8c5e743fb1295a4a59e5bb0512f161bfdece49 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 8 Jan 2024 16:38:30 +0900 Subject: [PATCH 054/247] rename to PairList --- freqtrade/constants.py | 2 +- .../pairlist/{MarketCapFilter.py => MarketCapPairList.py} | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) rename freqtrade/plugins/pairlist/{MarketCapFilter.py => MarketCapPairList.py} (96%) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index d7767dc2e..37e2d849c 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -33,7 +33,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'MaxDrawDownHyperOptLoss', 'MaxDrawDownRelativeHyperOptLoss', 'ProfitDrawDownHyperOptLoss'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'ProducerPairList', 'RemotePairList', - 'AgeFilter', "FullTradesFilter", 'MarketCapFilter', 'OffsetFilter', + 'MarketCapPairList', 'AgeFilter', "FullTradesFilter", 'OffsetFilter', 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] diff --git a/freqtrade/plugins/pairlist/MarketCapFilter.py b/freqtrade/plugins/pairlist/MarketCapPairList.py similarity index 96% rename from freqtrade/plugins/pairlist/MarketCapFilter.py rename to freqtrade/plugins/pairlist/MarketCapPairList.py index a8c1c2080..1f5474303 100644 --- a/freqtrade/plugins/pairlist/MarketCapFilter.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -23,7 +23,7 @@ logger = logging.getLogger(__name__) MODE_VALUES = ['top_rank', 'total_assets'] -class MarketCapFilter(IPairList): +class MarketCapPairList(IPairList): is_pairlist_generator = True @@ -116,7 +116,6 @@ class MarketCapFilter(IPairList): else: # Use fresh pairlist # Check if pair quote currency equals to the stake currency. - logger.info("Get active pairs for the market") _pairlist = [k for k in self._exchange.get_markets( quote_currencies=[self._stake_currency], tradable_only=True, active_only=True).keys()] @@ -142,7 +141,6 @@ class MarketCapFilter(IPairList): if marketcap_list: can_filter = True else: - logger.info("Get top 250 marketcap coins from coingecko") data = self._coingekko.get_coins_markets(vs_currency='usd', order='market_cap_desc', per_page='250', page='1', sparkline='false', locale='en') @@ -157,7 +155,6 @@ class MarketCapFilter(IPairList): if can_filter: filtered_pairlist = [] - logger.info("Create filtered pairlist") if self._mode == 'top_rank': top_marketcap = marketcap_list[:self._limit:] From d3506c249d2fd5d087fee62d2a9a4b0f0e36c904 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Tue, 9 Jan 2024 14:08:15 +0900 Subject: [PATCH 055/247] update docs --- docs/includes/pairlists.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 8e4b43178..c5ea43382 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -6,7 +6,7 @@ In your configuration, you can use Static Pairlist (defined by the [`StaticPairL Additionally, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter), [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist. -If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You should always configure either `StaticPairList` or `VolumePairList` as the starting Pairlist Handler. +If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList` or `MarketCapPairList` as the starting Pairlist Handler. Inactive markets are always removed from the resulting pairlist. Explicitly blacklisted pairs (those in the `pair_blacklist` configuration setting) are also always removed from the resulting pairlist. @@ -24,6 +24,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged * [`VolumePairList`](#volume-pair-list) * [`ProducerPairList`](#producerpairlist) * [`RemotePairList`](#remotepairlist) +* [`MarketCapPairList`](#marketcappairlist) * [`AgeFilter`](#agefilter) * [`FullTradesFilter`](#fulltradesfilter) * [`OffsetFilter`](#offsetfilter) @@ -227,6 +228,29 @@ The optional `bearer_token` will be included in the requests Authorization Heade !!! Note In case of a server error the last received pairlist will be kept if `keep_pairlist_on_failure` is set to true, when set to false a empty pairlist is returned. +#### MarketCapPairList + +`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. It will only recognize coins up to the coin placed at rank 250. The number of pairs in the resulted pairlist will be slightly different depends on the `mode` defined in the config (available mode are `top_rank` and `total_assets`). The marketcap data from + +```json +"pairlists": [ + { + "method": "MarketCapPairList", + "mode": "top_rank", + "limit": 20, + "refresh_period": 86400 + } +] +``` + +##### `top_rank` mode +In this mode, it will return pairlist consist of active and not-blaclisted pairs that are placed at the top `limit` rank of the marketcap. + +##### `total_assets` mode +In this mode, it will return pairlist consist of `limit` number of active and not-blaclisted pairs sorted by their marketcap rank. + +The refresh_period setting allows to define the period (in seconds), at which the marketcap rank data will be refreshed. Defaults to 86,400s (1 day). The pairlist cache (refresh_period) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list). + #### AgeFilter Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`) or more than `max_days_listed` days (defaults `None` mean infinity). From 95af462e80f333eb221b99345dc0437146743a5f Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Tue, 9 Jan 2024 14:21:07 +0900 Subject: [PATCH 056/247] fix pre-commit --- freqtrade/plugins/pairlist/MarketCapPairList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 1f5474303..465374edb 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -8,6 +8,7 @@ from datetime import timedelta from typing import Any, Dict, List, Literal from cachetools import TTLCache +from pycoingecko import CoinGeckoAPI from freqtrade.constants import Config, ListPairsWithTimeframes from freqtrade.exceptions import OperationalException @@ -15,7 +16,6 @@ from freqtrade.exchange.types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter from freqtrade.util import dt_now, format_ms_time -from pycoingecko import CoinGeckoAPI logger = logging.getLogger(__name__) From e8fcac491a0c5b70f47a53e509e44a78f3cd55f5 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Tue, 9 Jan 2024 14:32:27 +0900 Subject: [PATCH 057/247] use number_assets instead of limit --- docs/includes/pairlists.md | 6 ++-- .../plugins/pairlist/MarketCapPairList.py | 28 +++++++++++-------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index c5ea43382..07d672039 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -237,17 +237,17 @@ The optional `bearer_token` will be included in the requests Authorization Heade { "method": "MarketCapPairList", "mode": "top_rank", - "limit": 20, + "number_assets": 20, "refresh_period": 86400 } ] ``` ##### `top_rank` mode -In this mode, it will return pairlist consist of active and not-blaclisted pairs that are placed at the top `limit` rank of the marketcap. +In this mode, it will return pairlist consist of active and not-blaclisted pairs that are placed at the top `number_assets` rank of the marketcap. ##### `total_assets` mode -In this mode, it will return pairlist consist of `limit` number of active and not-blaclisted pairs sorted by their marketcap rank. +In this mode, it will return pairlist consist of `number_assets` number of active and not-blaclisted pairs sorted by their marketcap rank. The refresh_period setting allows to define the period (in seconds), at which the marketcap rank data will be refreshed. Defaults to 86,400s (1 day). The pairlist cache (refresh_period) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list). diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 465374edb..fb026759a 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -32,22 +32,22 @@ class MarketCapPairList(IPairList): pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) - if 'limit' not in self._pairlistconfig: + if 'number_assets' not in self._pairlistconfig: raise OperationalException( - '`limit` not specified. Please check your configuration ' - 'for "pairlist.config.limit"') + '`number_assets` not specified. Please check your configuration ' + 'for "pairlist.config.number_assets"') self._stake_currency = config['stake_currency'] self._mode = self._pairlistconfig.get('mode', 'top_rank') - self._limit = self._pairlistconfig['limit'] + self._number_assets = self._pairlistconfig['number_assets'] self._refresh_period = self._pairlistconfig.get('refresh_period', 86400) self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) self._def_candletype = self._config['candle_type_def'] self._coingekko: CoinGeckoAPI = CoinGeckoAPI() - if self._limit > 250: + if self._number_assets > 250: raise OperationalException( - "This filter only support limit value up to 250." + "This filter only support number_assets value up to 250." ) if not self._validate_keys(self._mode): @@ -71,16 +71,20 @@ class MarketCapPairList(IPairList): """ Short whitelist method description - used for startup-messages """ - return f"{self.name} - Only use top {self._pairlistconfig['limit']} market cap pairs." + num = self._pairlistconfig['number_assets'] + msg = f"{self.name} - Only include pairs ranked within top {num} market cap." + if self._mode == "total_assets": + msg = f"{self.name} - top {num} pairs sorted by market cap." + return msg @staticmethod def description() -> str: - return "Filter pair list based on market cap." + return "Provides pair list based on CoinGecko's market cap rank." @staticmethod def available_parameters() -> Dict[str, PairlistParameter]: return { - "limit": { + "number_assets": { "type": "number", "default": 30, "description": "Max market cap rank", @@ -157,7 +161,7 @@ class MarketCapPairList(IPairList): if self._mode == 'top_rank': - top_marketcap = marketcap_list[:self._limit:] + top_marketcap = marketcap_list[:self._number_assets:] for pair in pairlist: base = pair.split('/')[0] @@ -165,7 +169,7 @@ class MarketCapPairList(IPairList): filtered_pairlist.append(pair) else: logger.info(f"Remove {pair} from whitelist because it's not ranked " - f"within top {self._limit} market cap") + f"within top {self._number_assets} market cap") else: market = self._config['trading_mode'] @@ -176,7 +180,7 @@ class MarketCapPairList(IPairList): test_pair = f"{mc_pair.upper()}/{pair_format}" if test_pair in pairlist: filtered_pairlist.append(test_pair) - if len(filtered_pairlist) == self._limit: + if len(filtered_pairlist) == self._number_assets: break if len(filtered_pairlist) > 0: From de91261f732357f59bfd867791a039345aa22e33 Mon Sep 17 00:00:00 2001 From: Stefano Date: Tue, 9 Jan 2024 15:03:10 +0900 Subject: [PATCH 058/247] fix pre-commit --- freqtrade/plugins/pairlist/MarketCapPairList.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index fb026759a..7faf971f3 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -4,17 +4,15 @@ Market Cap PairList provider Provides dynamic pair list based on Market Cap """ import logging -from datetime import timedelta -from typing import Any, Dict, List, Literal +from typing import Any, Dict, List from cachetools import TTLCache from pycoingecko import CoinGeckoAPI -from freqtrade.constants import Config, ListPairsWithTimeframes +from freqtrade.constants import Config from freqtrade.exceptions import OperationalException from freqtrade.exchange.types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter -from freqtrade.util import dt_now, format_ms_time logger = logging.getLogger(__name__) @@ -54,7 +52,6 @@ class MarketCapPairList(IPairList): raise OperationalException( f'key {self._mode} not in {MODE_VALUES}') - @property def needstickers(self) -> bool: """ @@ -150,16 +147,12 @@ class MarketCapPairList(IPairList): locale='en') if data: marketcap_list = [row['symbol'] for row in data] - - if len(marketcap_list) > 0: - self._marketcap_cache['marketcap'] = marketcap_list - can_filter = True - + self._marketcap_cache['marketcap'] = marketcap_list + can_filter = True if can_filter: filtered_pairlist = [] - if self._mode == 'top_rank': top_marketcap = marketcap_list[:self._number_assets:] From 9f3c6f2dcc2fc4982823a6b581b52c3bc1ae9c38 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 19 Jan 2024 06:48:32 +0100 Subject: [PATCH 059/247] Fix some tests and comments --- tests/test_freqtradebot.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 6738fc5ae..57cd1a2f5 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1198,8 +1198,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ assert trade.amount == amount_before # Fourth case: when stoploss is set and it is hit - # should unset stoploss_order_id and return true - # as a trade actually happened + # should return true as a trade actually happened caplog.clear() stop_order_dict.update({'id': "103_1"}) @@ -1871,7 +1870,6 @@ def test_stoploss_on_exchange_price_rounding( price_to_precision=price_mock, ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - open_trade_usdt.stoploss_order_id = '13434334' open_trade_usdt.stop_loss = 222.55 freqtrade.handle_trailing_stoploss_on_exchange(open_trade_usdt, {}) @@ -2078,7 +2076,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde freqtrade.enter_positions() trade = Trade.session.scalars(select(Trade)).first() trade.is_open = True - trade.stoploss_order_id = '100' + trade.stoploss_last_update = dt_now() trade.orders.append( Order( @@ -4194,8 +4192,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( assert not trade.has_open_orders # Assuming stoploss on exchange is hit - # stoploss_order_id should become None - # and trade should be sold at the price of stoploss + # trade should be sold at the price of stoploss, with exit_reaeon STOPLOSS_ON_EXCHANGE stoploss_executed = MagicMock(return_value={ "id": "123", "timestamp": 1542707426845, @@ -5721,7 +5718,6 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap def reset_open_orders(trade): - trade.stoploss_order_id = None trade.is_short = is_short create_mock_trades(fee, is_short=is_short) @@ -5779,7 +5775,7 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap assert log_has_re(r"Trying to refind Order\(.*", caplog) assert mock_fo.call_count == 1 assert mock_uts.call_count == 2 - # stoploss_order_id is "refound" and added to the trade + # stoploss order is "refound" and added to the trade assert not trade.has_open_orders assert trade.has_open_sl_orders is True From 59b34865740cdeb170971034c58cf1f8c7914022 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 19 Jan 2024 06:49:49 +0100 Subject: [PATCH 060/247] Update migrations --- freqtrade/persistence/migrations.py | 21 +++++++++------------ tests/persistence/test_migrations.py | 6 +++--- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index fc67448eb..2970da918 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -276,23 +276,20 @@ def fix_old_dry_orders(engine): with engine.begin() as connection: # Update current dry-run Orders where + # - stoploss order is Open (will be replaced eventually) + # 2nd query: # - current Order is open # - current Trade is closed # - current Order trade_id not equal to current Trade.id # - current Order not stoploss - # TODO: is this still necessary ? how can this be done now ? - # stmt = update(Order).where( - # Order.ft_is_open.is_(True), - # tuple_(Order.ft_trade_id, Order.order_id).not_in( - # select( - # Trade.id, Trade.stoploss_order_id - # ).where(Trade.stoploss_order_id.is_not(None)) - # ), - # Order.ft_order_side == 'stoploss', - # Order.order_id.like('dry%'), - # ).values(ft_is_open=False) - # connection.execute(stmt) + stmt = update(Order).where( + Order.ft_is_open.is_(True), + Order.ft_order_side == 'stoploss', + Order.order_id.like('dry%'), + + ).values(ft_is_open=False) + connection.execute(stmt) # Close dry-run orders for closed trades. stmt = update(Order).where( diff --git a/tests/persistence/test_migrations.py b/tests/persistence/test_migrations.py index f2bb0b2f1..6ef098cb3 100644 --- a/tests/persistence/test_migrations.py +++ b/tests/persistence/test_migrations.py @@ -74,7 +74,7 @@ def test_init_dryrun_db(default_conf, tmpdir): assert Path(filename).is_file() -def test_migrate_new(mocker, default_conf, fee, caplog): +def test_migrate(mocker, default_conf, fee, caplog): """ Test Database migration (starting with new pairformat) """ @@ -277,7 +277,6 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.exit_reason is None assert trade.strategy is None assert trade.timeframe == '5m' - assert trade.stoploss_order_id == 'dry_stop_order_id222' assert trade.stoploss_last_update is None assert log_has("trying trades_bak1", caplog) assert log_has("trying trades_bak2", caplog) @@ -294,9 +293,10 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert orders[0].order_id == 'dry_buy_order' assert orders[0].ft_order_side == 'buy' + # All dry-run stoploss orders will be closed assert orders[-1].order_id == 'dry_stop_order_id222' assert orders[-1].ft_order_side == 'stoploss' - assert orders[-1].ft_is_open is True + assert orders[-1].ft_is_open is False assert orders[1].order_id == 'dry_buy_order22' assert orders[1].ft_order_side == 'buy' From b9a43b8e248a53dd28a26b9eeedcfced61f8bf92 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 19 Jan 2024 07:12:49 +0100 Subject: [PATCH 061/247] Don't store 'stoploss_last_updated' explicitly it can easily be derived from the very last stoploss order. --- freqtrade/freqtradebot.py | 1 - freqtrade/persistence/migrations.py | 4 +--- freqtrade/persistence/trade_model.py | 20 ++++++-------------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 3b01f7756..26631eb30 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1184,7 +1184,6 @@ class FreqtradeBot(LoggingMixin): order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss', trade.amount, stop_price) trade.orders.append(order_obj) - trade.stoploss_last_update = datetime.now(timezone.utc) return True except InsufficientFundsError as e: logger.warning(f"Unable to place stoploss order {e}.") diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 2970da918..eb55cf455 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -91,7 +91,6 @@ def migrate_trades_and_orders_table( is_stop_loss_trailing = get_column_def( cols, 'is_stop_loss_trailing', f'coalesce({stop_loss_pct}, 0.0) <> coalesce({initial_stop_loss_pct}, 0.0)') - stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') max_rate = get_column_def(cols, 'max_rate', '0.0') min_rate = get_column_def(cols, 'min_rate', 'null') exit_reason = get_column_def(cols, 'sell_reason', get_column_def(cols, 'exit_reason', 'null')) @@ -159,7 +158,7 @@ def migrate_trades_and_orders_table( open_rate_requested, close_rate, close_rate_requested, close_profit, stake_amount, amount, amount_requested, open_date, close_date, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, - is_stop_loss_trailing, stoploss_last_update, + is_stop_loss_trailing, max_rate, min_rate, exit_reason, exit_order_status, strategy, enter_tag, timeframe, open_trade_value, close_profit_abs, trading_mode, leverage, liquidation_price, is_short, @@ -179,7 +178,6 @@ def migrate_trades_and_orders_table( {initial_stop_loss} initial_stop_loss, {initial_stop_loss_pct} initial_stop_loss_pct, {is_stop_loss_trailing} is_stop_loss_trailing, - {stoploss_last_update} stoploss_last_update, {max_rate} max_rate, {min_rate} min_rate, case when {exit_reason} = 'sell_signal' then 'exit_signal' when {exit_reason} = 'custom_sell' then 'custom_exit' diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 7d88294b0..9db13dabc 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -364,8 +364,6 @@ class LocalTrade: # percentage value of the initial stop loss initial_stop_loss_pct: Optional[float] = None is_stop_loss_trailing: bool = False - # last update time of the stoploss order on exchange - stoploss_last_update: Optional[datetime] = None # absolute value of the highest reached price max_rate: Optional[float] = None # Lowest price reached @@ -455,8 +453,8 @@ class LocalTrade: @property def stoploss_last_update_utc(self): - if self.stoploss_last_update: - return self.stoploss_last_update.replace(tzinfo=timezone.utc) + if self.has_open_sl_orders: + return max(o.order_date_utc for o in self.open_sl_orders) return None @property @@ -638,10 +636,10 @@ class LocalTrade: 'stop_loss_abs': self.stop_loss, 'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None, 'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None, - 'stoploss_last_update': (self.stoploss_last_update.strftime(DATETIME_PRINT_FORMAT) - if self.stoploss_last_update else None), - 'stoploss_last_update_timestamp': int(self.stoploss_last_update.replace( - tzinfo=timezone.utc).timestamp() * 1000) if self.stoploss_last_update else None, + 'stoploss_last_update': (self.stoploss_last_update_utc.strftime(DATETIME_PRINT_FORMAT) + if self.stoploss_last_update_utc else None), + 'stoploss_last_update_timestamp': int(self.stoploss_last_update_utc.timestamp() * 1000 + ) if self.stoploss_last_update_utc else None, 'initial_stop_loss_abs': self.initial_stop_loss, 'initial_stop_loss_ratio': (self.initial_stop_loss_pct if self.initial_stop_loss_pct else None), @@ -1378,10 +1376,6 @@ class LocalTrade: exit_order_status=data["exit_order_status"], stop_loss=data["stop_loss_abs"], stop_loss_pct=data["stop_loss_ratio"], - stoploss_last_update=( - datetime.fromtimestamp(data["stoploss_last_update_timestamp"] // 1000, - tz=timezone.utc) - if data["stoploss_last_update_timestamp"] else None), initial_stop_loss=data["initial_stop_loss_abs"], initial_stop_loss_pct=data["initial_stop_loss_ratio"], min_rate=data["min_rate"], @@ -1487,8 +1481,6 @@ class Trade(ModelBase, LocalTrade): Float(), nullable=True) # type: ignore is_stop_loss_trailing: Mapped[bool] = mapped_column( nullable=False, default=False) # type: ignore - # last update time of the stoploss order on exchange - stoploss_last_update: Mapped[Optional[datetime]] = mapped_column(nullable=True) # type: ignore # absolute value of the highest reached price max_rate: Mapped[Optional[float]] = mapped_column( Float(), nullable=True, default=0.0) # type: ignore From acbea4e26ffe9e6e7ed927ca243a9537a65b8dfe Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 19 Jan 2024 18:15:21 +0100 Subject: [PATCH 062/247] Fix some tests after update_stoploss_date removal --- tests/test_freqtradebot.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 57cd1a2f5..2c14fdca1 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1675,6 +1675,7 @@ def test_handle_stoploss_on_exchange_trailing( ft_amount=trade.amount, ft_price=trade.stop_loss, order_id='100', + order_date=dt_now() - timedelta(minutes=20), ) ) @@ -1767,8 +1768,9 @@ def test_handle_stoploss_on_exchange_trailing( @pytest.mark.parametrize("is_short", [False, True]) def test_handle_stoploss_on_exchange_trailing_error( - mocker, default_conf_usdt, fee, caplog, limit_order, is_short + mocker, default_conf_usdt, fee, caplog, limit_order, is_short, time_machine ) -> None: + time_machine.move_to(dt_now() - timedelta(minutes=601)) enter_order = limit_order[entry_side(is_short)] exit_order = limit_order[exit_side(is_short)] # When trailing stoploss is set @@ -1809,7 +1811,6 @@ def test_handle_stoploss_on_exchange_trailing_error( trade.is_short = is_short trade.is_open = True trade.stop_loss = 0.2 - trade.stoploss_last_update = (dt_now() - timedelta(minutes=601)).replace(tzinfo=None) stoploss_order_hanging = { 'id': "abcd", @@ -1829,12 +1830,14 @@ def test_handle_stoploss_on_exchange_trailing_error( ft_amount=trade.amount, ft_price=3, order_id='abcd', + order_date=dt_now(), ) ) mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=InvalidOrderException()) mocker.patch(f'{EXMS}.fetch_stoploss_order', return_value=stoploss_order_hanging) + time_machine.shift(timedelta(minutes=50)) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog) @@ -1844,10 +1847,10 @@ def test_handle_stoploss_on_exchange_trailing_error( assert len(trade.open_sl_orders) == 2 # Fail creating stoploss order - trade.stoploss_last_update = dt_now() - timedelta(minutes=601) caplog.clear() cancel_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order') mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError()) + time_machine.shift(timedelta(minutes=50)) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) assert cancel_mock.call_count == 2 assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog) From 88ba82d4fd4ee74bf86bdc599d7dceb5b1c58b02 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 19 Jan 2024 18:17:31 +0100 Subject: [PATCH 063/247] Fix more tests --- tests/test_freqtradebot.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 2c14fdca1..2d962ae1b 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1383,8 +1383,9 @@ def test_handle_stoploss_on_exchange_partial( @pytest.mark.parametrize("is_short", [False, True]) def test_handle_stoploss_on_exchange_partial_cancel_here( - mocker, default_conf_usdt, fee, is_short, limit_order, caplog) -> None: + mocker, default_conf_usdt, fee, is_short, limit_order, caplog, time_machine) -> None: stop_order_dict = {'id': "101", "status": "open"} + time_machine.move_to(dt_now()) default_conf_usdt['trailing_stop'] = True stoploss = MagicMock(return_value=stop_order_dict) enter_order = limit_order[entry_side(is_short)] @@ -1443,7 +1444,7 @@ def test_handle_stoploss_on_exchange_partial_cancel_here( }) mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit) mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', stoploss_order_cancel) - trade.stoploss_last_update = dt_now() - timedelta(minutes=10) + time_machine.shift(timedelta(minutes=15)) assert freqtrade.handle_stoploss_on_exchange(trade) is False # Canceled Stoploss filled partially ... @@ -1934,7 +1935,6 @@ def test_handle_stoploss_on_exchange_custom_stop( trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short trade.is_open = True - trade.stoploss_last_update = dt_now() - timedelta(minutes=601) trade.orders.append( Order( ft_order_side='stoploss', @@ -1942,6 +1942,7 @@ def test_handle_stoploss_on_exchange_custom_stop( ft_is_open=True, ft_amount=trade.amount, ft_price=trade.stop_loss, + order_date=dt_now() - timedelta(minutes=601), order_id='100', ) ) From 6eaf42fe33fb1106c0241ca86cd6a1d3092cda4f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 19 Jan 2024 18:17:53 +0100 Subject: [PATCH 064/247] Default order_date to dt_now if it's not set via ccxt and wasn't previously set. --- freqtrade/persistence/trade_model.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 9db13dabc..1484c006f 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -170,6 +170,8 @@ class Order(ModelBase): order_date = safe_value_fallback(order, 'timestamp') if order_date: self.order_date = datetime.fromtimestamp(order_date / 1000, tz=timezone.utc) + elif not self.order_date: + self.order_date = dt_now() self.ft_is_open = True if self.status in NON_OPEN_EXCHANGE_STATES: From 58058f0197ee331832ca1c1889eae2aba718b2ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 19 Jan 2024 18:20:23 +0100 Subject: [PATCH 065/247] Fix migration test --- tests/persistence/test_migrations.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/persistence/test_migrations.py b/tests/persistence/test_migrations.py index 6ef098cb3..a6a107a5e 100644 --- a/tests/persistence/test_migrations.py +++ b/tests/persistence/test_migrations.py @@ -277,7 +277,6 @@ def test_migrate(mocker, default_conf, fee, caplog): assert trade.exit_reason is None assert trade.strategy is None assert trade.timeframe == '5m' - assert trade.stoploss_last_update is None assert log_has("trying trades_bak1", caplog) assert log_has("trying trades_bak2", caplog) assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0", From 3ab226a0965fdaf7428a75e5d6b5245bc812272c Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 19 Jan 2024 18:24:03 +0100 Subject: [PATCH 066/247] Remove unused import --- freqtrade/persistence/migrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index eb55cf455..cf2e06f71 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -1,7 +1,7 @@ import logging from typing import List, Optional -from sqlalchemy import inspect, select, text, tuple_, update +from sqlalchemy import inspect, select, text, update from freqtrade.exceptions import OperationalException from freqtrade.persistence.trade_model import Order, Trade From e76888882dadae9286ed1dcc64830b487c9fd5ca Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 19 Jan 2024 18:59:52 +0100 Subject: [PATCH 067/247] Fix typehint --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 26631eb30..73fa9fa68 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1289,7 +1289,7 @@ class FreqtradeBot(LoggingMixin): logger.warning(f"Could not create trailing stoploss order " f"for pair {trade.pair}.") - def manage_trade_stoploss_orders(self, trade: Trade, stoploss_orders: Dict): + def manage_trade_stoploss_orders(self, trade: Trade, stoploss_orders: List[Dict]): """ Perform required actions acording to existing stoploss orders of trade :param trade: Corresponding Trade From ad0f88796bd6854eb0840e258ea5b29a926d9c60 Mon Sep 17 00:00:00 2001 From: Shane Date: Sat, 20 Jan 2024 10:42:37 +1100 Subject: [PATCH 068/247] fix: logical error Fix logical error in the conditional checks for model classes. The `elif` statement that looks for "lightgbm.sklearn" or "xgb" in the model class string is now broken into two separate conditions because the old condition would always evaluate to `True` due to the non-empty string "xgb". --- freqtrade/freqai/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqai/utils.py b/freqtrade/freqai/utils.py index 4428d9578..bc335bf20 100644 --- a/freqtrade/freqai/utils.py +++ b/freqtrade/freqai/utils.py @@ -118,10 +118,12 @@ def plot_feature_importance(model: Any, pair: str, dk: FreqaiDataKitchen, mdl = models[label] if "catboost.core" in str(mdl.__class__): feature_importance = mdl.get_feature_importance() - elif "lightgbm.sklearn" or "xgb" in str(mdl.__class__): + elif "lightgbm.sklearn" in str(mdl.__class__): + feature_importance = mdl.feature_importances_ + elif "xgb" in str(mdl.__class__): feature_importance = mdl.feature_importances_ else: - logger.info('Model type not support for generating feature importances.') + logger.info('Model type does not support for generating feature importances.') return # Data preparation From bbec51685d8217d52c80eec52e792e1a1dd4dbf5 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sun, 21 Jan 2024 13:28:50 +0900 Subject: [PATCH 069/247] remove can_filter, and use log_once --- freqtrade/plugins/pairlist/MarketCapPairList.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 7faf971f3..7e75d2223 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -137,20 +137,16 @@ class MarketCapPairList(IPairList): :return: new whitelist """ marketcap_list = self._marketcap_cache.get('marketcap') - can_filter = False - if marketcap_list: - can_filter = True - else: + if marketcap_list is None: data = self._coingekko.get_coins_markets(vs_currency='usd', order='market_cap_desc', per_page='250', page='1', sparkline='false', locale='en') if data: marketcap_list = [row['symbol'] for row in data] self._marketcap_cache['marketcap'] = marketcap_list - can_filter = True - if can_filter: + if marketcap_list: filtered_pairlist = [] if self._mode == 'top_rank': @@ -161,8 +157,8 @@ class MarketCapPairList(IPairList): if base.lower() in top_marketcap: filtered_pairlist.append(pair) else: - logger.info(f"Remove {pair} from whitelist because it's not ranked " - f"within top {self._number_assets} market cap") + self.log_once(f"Remove {pair} from whitelist because it's not ranked " + f"within top {self._number_assets} market cap") else: market = self._config['trading_mode'] From 60812983e3d46f487cb5e3b6fd249ed1104ba346 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sun, 21 Jan 2024 13:33:38 +0900 Subject: [PATCH 070/247] fix error --- freqtrade/plugins/pairlist/MarketCapPairList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 7e75d2223..0cddcd05a 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -158,7 +158,7 @@ class MarketCapPairList(IPairList): filtered_pairlist.append(pair) else: self.log_once(f"Remove {pair} from whitelist because it's not ranked " - f"within top {self._number_assets} market cap") + f"within top {self._number_assets} market cap", logger.info) else: market = self._config['trading_mode'] From bf17236e9baea090465065e17cd5a1430b9a8e5d Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sat, 27 Jan 2024 16:25:54 +0900 Subject: [PATCH 071/247] fix typo in the docs --- docs/includes/pairlists.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 07d672039..85ce6e143 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -230,7 +230,7 @@ The optional `bearer_token` will be included in the requests Authorization Heade #### MarketCapPairList -`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. It will only recognize coins up to the coin placed at rank 250. The number of pairs in the resulted pairlist will be slightly different depends on the `mode` defined in the config (available mode are `top_rank` and `total_assets`). The marketcap data from +`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. It will only recognize coins up to the coin placed at rank 250. The number of pairs in the resulted pairlist will be slightly different depends on the `mode` defined in the config (available mode are `top_rank` and `total_assets`). ```json "pairlists": [ @@ -244,12 +244,12 @@ The optional `bearer_token` will be included in the requests Authorization Heade ``` ##### `top_rank` mode -In this mode, it will return pairlist consist of active and not-blaclisted pairs that are placed at the top `number_assets` rank of the marketcap. +In this mode, it will return pairlist consist of active and not-blacklisted pairs that are placed at the top `number_assets` rank of the marketcap. ##### `total_assets` mode -In this mode, it will return pairlist consist of `number_assets` number of active and not-blaclisted pairs sorted by their marketcap rank. +In this mode, it will return pairlist consist of `number_assets` number of active and not-blacklisted pairs sorted by their marketcap rank. -The refresh_period setting allows to define the period (in seconds), at which the marketcap rank data will be refreshed. Defaults to 86,400s (1 day). The pairlist cache (refresh_period) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list). +The `refresh_period` setting define the period (in seconds) at which the marketcap rank data will be cached. Defaults to 86,400s (1 day). The pairlist cache (refresh_period) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list). #### AgeFilter From 006639820bbddb68c02a02b8a1d379baf6f9650a Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sat, 27 Jan 2024 18:10:10 +0900 Subject: [PATCH 072/247] add simple test --- tests/plugins/test_pairlist.py | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index d66a47aa6..b2aa2b8f0 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -1513,3 +1513,80 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: pm.refresh_pairlist() assert pm.whitelist == [] assert log_has_re(r'Whitelist with 0 pairs: \[]', caplog) + + + +def test_MarketCapPairList_filter(mocker, default_conf_usdt): + mock_response = MagicMock() + + mock_response.json.return_value = [ + { + "symbol": "btc", + }, + { + "symbol": "eth", + }, + { + "symbol": "usdt", + }, + { + "symbol": "bnb", + }, + { + "symbol": "sol", + }, + { + "symbol": "xrp", + }, + { + "symbol": "usdc", + }, + { + "symbol": "steth", + }, + { + "symbol": "ada", + }, + { + "symbol": "avax", + } + ] + + mock_response.headers = { + "content-type": "application/json" + } + + # Test top 2 mc + default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT']) + default_conf_usdt['pairlists'] = [ + {"method": "StaticPairList"}, + {"method": "MarketCapPairList", "mode": "top_rank", "number_assets": 2} + ] + mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) + + mocker.patch("freqtrade.plugins.pairlist.MarketCapPairList._coingekko.get_coins_markets", + return_value=mock_response) + + exchange = get_patched_exchange(mocker, default_conf_usdt) + + pm = PairListManager(exchange, default_conf_usdt) + + pm.refresh_pairlist() + + whitelist = ['ETH/USDT', 'BTC/USDT'] + + assert set(whitelist) == set(pm.whitelist) + + # Test top 6 mc + default_conf_usdt['pairlists'] = [ + {"method": "StaticPairList"}, + {"method": "MarketCapPairList", "mode": "top_rank", "number_assets": 6} + ] + + pm = PairListManager(exchange, default_conf_usdt) + + pm.refresh_pairlist() + + whitelist = ['ETH/USDT', 'XRP/USDT', 'BTC/USDT'] + + assert set(whitelist) == set(pm.whitelist) From c854cef3138acfe79dc838a4a274a5e31f6a1239 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sat, 27 Jan 2024 18:12:19 +0900 Subject: [PATCH 073/247] fix precommit --- tests/plugins/test_pairlist.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index b2aa2b8f0..0f50ec165 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -1515,40 +1515,39 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: assert log_has_re(r'Whitelist with 0 pairs: \[]', caplog) - def test_MarketCapPairList_filter(mocker, default_conf_usdt): mock_response = MagicMock() mock_response.json.return_value = [ { - "symbol": "btc", + "symbol": "btc", }, { - "symbol": "eth", + "symbol": "eth", }, { - "symbol": "usdt", + "symbol": "usdt", }, { - "symbol": "bnb", + "symbol": "bnb", }, { - "symbol": "sol", + "symbol": "sol", }, { - "symbol": "xrp", + "symbol": "xrp", }, { - "symbol": "usdc", + "symbol": "usdc", }, { - "symbol": "steth", + "symbol": "steth", }, { - "symbol": "ada", + "symbol": "ada", }, { - "symbol": "avax", + "symbol": "avax", } ] From e0f7b62b542bfb740ecf606a64d860528b872513 Mon Sep 17 00:00:00 2001 From: Stefano Date: Sat, 27 Jan 2024 19:12:50 +0900 Subject: [PATCH 074/247] fix initial tests --- tests/plugins/test_pairlist.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 0f50ec165..1582cfcfb 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -1516,9 +1516,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: def test_MarketCapPairList_filter(mocker, default_conf_usdt): - mock_response = MagicMock() - - mock_response.json.return_value = [ + test_value = [ { "symbol": "btc", }, @@ -1551,20 +1549,16 @@ def test_MarketCapPairList_filter(mocker, default_conf_usdt): } ] - mock_response.headers = { - "content-type": "application/json" - } - # Test top 2 mc default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT']) default_conf_usdt['pairlists'] = [ - {"method": "StaticPairList"}, + {"method": "StaticPairList", "allow_inactive": True}, {"method": "MarketCapPairList", "mode": "top_rank", "number_assets": 2} ] mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) - mocker.patch("freqtrade.plugins.pairlist.MarketCapPairList._coingekko.get_coins_markets", - return_value=mock_response) + mocker.patch("freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets", + return_value=test_value) exchange = get_patched_exchange(mocker, default_conf_usdt) @@ -1578,7 +1572,7 @@ def test_MarketCapPairList_filter(mocker, default_conf_usdt): # Test top 6 mc default_conf_usdt['pairlists'] = [ - {"method": "StaticPairList"}, + {"method": "StaticPairList", "allow_inactive": True}, {"method": "MarketCapPairList", "mode": "top_rank", "number_assets": 6} ] From f8dc161ab45174ccad85e88ef113930a82659c6a Mon Sep 17 00:00:00 2001 From: Stefano Date: Sat, 27 Jan 2024 19:33:12 +0900 Subject: [PATCH 075/247] add 2 more final tests --- tests/plugins/test_pairlist.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 1582cfcfb..6bef0456f 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -1551,6 +1551,7 @@ def test_MarketCapPairList_filter(mocker, default_conf_usdt): # Test top 2 mc default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT']) + default_conf_usdt['trading_mode'] = 'spot' default_conf_usdt['pairlists'] = [ {"method": "StaticPairList", "allow_inactive": True}, {"method": "MarketCapPairList", "mode": "top_rank", "number_assets": 2} @@ -1568,7 +1569,7 @@ def test_MarketCapPairList_filter(mocker, default_conf_usdt): whitelist = ['ETH/USDT', 'BTC/USDT'] - assert set(whitelist) == set(pm.whitelist) + assert whitelist == pm.whitelist # Test top 6 mc default_conf_usdt['pairlists'] = [ @@ -1582,4 +1583,32 @@ def test_MarketCapPairList_filter(mocker, default_conf_usdt): whitelist = ['ETH/USDT', 'XRP/USDT', 'BTC/USDT'] - assert set(whitelist) == set(pm.whitelist) + assert whitelist == pm.whitelist + + # Test total assets mode, 2 assets + default_conf_usdt['pairlists'] = [ + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "MarketCapPairList", "mode": "total_assets", "number_assets": 2} + ] + + pm = PairListManager(exchange, default_conf_usdt) + + pm.refresh_pairlist() + + whitelist = ['BTC/USDT', 'ETH/USDT'] + + assert whitelist == pm.whitelist + + # Test total assets mode, 5 assets + default_conf_usdt['pairlists'] = [ + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "MarketCapPairList", "mode": "total_assets", "number_assets": 5} + ] + + pm = PairListManager(exchange, default_conf_usdt) + + pm.refresh_pairlist() + + whitelist = ['BTC/USDT', 'ETH/USDT', 'XRP/USDT'] + + assert whitelist == pm.whitelist From 132e143b9ad25c342769919e73c660e59bcafd44 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Jan 2024 14:49:50 +0100 Subject: [PATCH 076/247] Minor comment fix --- docs/includes/pairlists.md | 4 +++- freqtrade/plugins/pairlist/MarketCapPairList.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 85ce6e143..482de3782 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -244,12 +244,14 @@ The optional `bearer_token` will be included in the requests Authorization Heade ``` ##### `top_rank` mode + In this mode, it will return pairlist consist of active and not-blacklisted pairs that are placed at the top `number_assets` rank of the marketcap. ##### `total_assets` mode + In this mode, it will return pairlist consist of `number_assets` number of active and not-blacklisted pairs sorted by their marketcap rank. -The `refresh_period` setting define the period (in seconds) at which the marketcap rank data will be cached. Defaults to 86,400s (1 day). The pairlist cache (refresh_period) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list). +The `refresh_period` setting define the period (in seconds) at which the marketcap rank data will be cached. Defaults to 86,400s (1 day). The pairlist cache (`refresh_period`) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list). #### AgeFilter diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index 0cddcd05a..c0232e0d0 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -109,7 +109,7 @@ class MarketCapPairList(IPairList): :return: List of pairs """ # Generate dynamic whitelist - # Must always run if this pairlist is not the first in the list. + # Must always run if this pairlist is the first in the list. pairlist = self._marketcap_cache.get('pairlist_mc') if pairlist: # Item found - no refresh necessary From 076ca7520096c7533408a83151bbd5eef5f7b6a5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Jan 2024 14:52:05 +0100 Subject: [PATCH 077/247] Slightly refactor pairlist test --- tests/plugins/test_pairlist.py | 82 +++++++++++++--------------------- 1 file changed, 30 insertions(+), 52 deletions(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 6bef0456f..e19968482 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -1515,7 +1515,34 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: assert log_has_re(r'Whitelist with 0 pairs: \[]', caplog) -def test_MarketCapPairList_filter(mocker, default_conf_usdt): +@pytest.mark.parametrize('pairlists,result', [ + ([ + # Test top 2 mc + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "MarketCapPairList", "mode": "top_rank", "number_assets": 2} + ], ['ETH/USDT', 'BTC/USDT']), + ([ + # Test top 6 mc + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "MarketCapPairList", "mode": "top_rank", "number_assets": 6} + ], ['ETH/USDT', 'XRP/USDT', 'BTC/USDT']), + ([ + # Test total assets mode, 2 assets + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "MarketCapPairList", "mode": "total_assets", "number_assets": 2} + ], ['BTC/USDT', 'ETH/USDT']), + + ([ + # Test total assets mode, 5 assets + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "MarketCapPairList", "mode": "total_assets", "number_assets": 5} + ], ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']), + ([ + # MarketCapPairList as generator + {"method": "MarketCapPairList", "mode": "total_assets", "number_assets": 5} + ], ['ETH/USDT', 'XRP/USDT']) +]) +def test_MarketCapPairList_filter(mocker, default_conf_usdt, pairlists, result): test_value = [ { "symbol": "btc", @@ -1549,13 +1576,9 @@ def test_MarketCapPairList_filter(mocker, default_conf_usdt): } ] - # Test top 2 mc default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT']) default_conf_usdt['trading_mode'] = 'spot' - default_conf_usdt['pairlists'] = [ - {"method": "StaticPairList", "allow_inactive": True}, - {"method": "MarketCapPairList", "mode": "top_rank", "number_assets": 2} - ] + default_conf_usdt['pairlists'] = pairlists mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) mocker.patch("freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets", @@ -1564,51 +1587,6 @@ def test_MarketCapPairList_filter(mocker, default_conf_usdt): exchange = get_patched_exchange(mocker, default_conf_usdt) pm = PairListManager(exchange, default_conf_usdt) - pm.refresh_pairlist() - whitelist = ['ETH/USDT', 'BTC/USDT'] - - assert whitelist == pm.whitelist - - # Test top 6 mc - default_conf_usdt['pairlists'] = [ - {"method": "StaticPairList", "allow_inactive": True}, - {"method": "MarketCapPairList", "mode": "top_rank", "number_assets": 6} - ] - - pm = PairListManager(exchange, default_conf_usdt) - - pm.refresh_pairlist() - - whitelist = ['ETH/USDT', 'XRP/USDT', 'BTC/USDT'] - - assert whitelist == pm.whitelist - - # Test total assets mode, 2 assets - default_conf_usdt['pairlists'] = [ - {"method": "StaticPairList", "allow_inactive": True}, - {"method": "MarketCapPairList", "mode": "total_assets", "number_assets": 2} - ] - - pm = PairListManager(exchange, default_conf_usdt) - - pm.refresh_pairlist() - - whitelist = ['BTC/USDT', 'ETH/USDT'] - - assert whitelist == pm.whitelist - - # Test total assets mode, 5 assets - default_conf_usdt['pairlists'] = [ - {"method": "StaticPairList", "allow_inactive": True}, - {"method": "MarketCapPairList", "mode": "total_assets", "number_assets": 5} - ] - - pm = PairListManager(exchange, default_conf_usdt) - - pm.refresh_pairlist() - - whitelist = ['BTC/USDT', 'ETH/USDT', 'XRP/USDT'] - - assert whitelist == pm.whitelist + assert pm.whitelist == result From b72078e76eacb50733158ca78c891ae7b955bcaa Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Jan 2024 16:07:55 +0100 Subject: [PATCH 078/247] use get_pair_base_currency --- freqtrade/plugins/pairlist/MarketCapPairList.py | 2 +- tests/plugins/test_pairlist.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index c0232e0d0..afe47bdce 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -153,7 +153,7 @@ class MarketCapPairList(IPairList): top_marketcap = marketcap_list[:self._number_assets:] for pair in pairlist: - base = pair.split('/')[0] + base = self._exchange.get_pair_base_currency(pair) if base.lower() in top_marketcap: filtered_pairlist.append(pair) else: diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index e19968482..1f3f0d16a 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -1542,7 +1542,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: {"method": "MarketCapPairList", "mode": "total_assets", "number_assets": 5} ], ['ETH/USDT', 'XRP/USDT']) ]) -def test_MarketCapPairList_filter(mocker, default_conf_usdt, pairlists, result): +def test_MarketCapPairList_filter(mocker, default_conf_usdt, markets, pairlists, result): test_value = [ { "symbol": "btc", @@ -1579,7 +1579,10 @@ def test_MarketCapPairList_filter(mocker, default_conf_usdt, pairlists, result): default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT']) default_conf_usdt['trading_mode'] = 'spot' default_conf_usdt['pairlists'] = pairlists - mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) + mocker.patch.multiple(EXMS, + markets=PropertyMock(return_value=markets), + exchange_has=MagicMock(return_value=True), + ) mocker.patch("freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets", return_value=test_value) From 23ac9e145a91acd91000ec95a675c22b07d4334f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Jan 2024 16:25:00 +0100 Subject: [PATCH 079/247] Fix generator test --- tests/plugins/test_pairlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 1f3f0d16a..3d26979e1 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -1540,7 +1540,7 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: ([ # MarketCapPairList as generator {"method": "MarketCapPairList", "mode": "total_assets", "number_assets": 5} - ], ['ETH/USDT', 'XRP/USDT']) + ], ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']) ]) def test_MarketCapPairList_filter(mocker, default_conf_usdt, markets, pairlists, result): test_value = [ From b02e15b1624cf238f968f6b72e04d8f430dd457b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 03:27:55 +0000 Subject: [PATCH 080/247] Bump lightgbm from 4.2.0 to 4.3.0 Bumps [lightgbm](https://github.com/microsoft/LightGBM) from 4.2.0 to 4.3.0. - [Release notes](https://github.com/microsoft/LightGBM/releases) - [Commits](https://github.com/microsoft/LightGBM/compare/v4.2.0...v4.3.0) --- updated-dependencies: - dependency-name: lightgbm dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-freqai.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index 988ac2143..2d33efc3c 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -6,7 +6,7 @@ scikit-learn==1.4.0 joblib==1.3.2 catboost==1.2.2; 'arm' not in platform_machine and python_version < '3.12' -lightgbm==4.2.0 +lightgbm==4.3.0 xgboost==2.0.3 tensorboard==2.15.1 datasieve==0.1.7 From 17af69435f9a1fc06aa7bfad11d357f54a189bf0 Mon Sep 17 00:00:00 2001 From: Stefano Date: Mon, 29 Jan 2024 17:57:14 +0900 Subject: [PATCH 081/247] remove mode, add max_rank, modify test --- .../plugins/pairlist/MarketCapPairList.py | 69 +++++++------------ tests/plugins/test_pairlist.py | 32 ++++----- 2 files changed, 40 insertions(+), 61 deletions(-) diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py index afe47bdce..a618f72d2 100644 --- a/freqtrade/plugins/pairlist/MarketCapPairList.py +++ b/freqtrade/plugins/pairlist/MarketCapPairList.py @@ -18,9 +18,6 @@ from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter logger = logging.getLogger(__name__) -MODE_VALUES = ['top_rank', 'total_assets'] - - class MarketCapPairList(IPairList): is_pairlist_generator = True @@ -36,22 +33,18 @@ class MarketCapPairList(IPairList): 'for "pairlist.config.number_assets"') self._stake_currency = config['stake_currency'] - self._mode = self._pairlistconfig.get('mode', 'top_rank') self._number_assets = self._pairlistconfig['number_assets'] + self._max_rank = self._pairlistconfig.get('max_rank', 30) self._refresh_period = self._pairlistconfig.get('refresh_period', 86400) self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period) self._def_candletype = self._config['candle_type_def'] self._coingekko: CoinGeckoAPI = CoinGeckoAPI() - if self._number_assets > 250: + if self._max_rank > 250: raise OperationalException( - "This filter only support number_assets value up to 250." + "This filter only support marketcap rank up to 250." ) - if not self._validate_keys(self._mode): - raise OperationalException( - f'key {self._mode} not in {MODE_VALUES}') - @property def needstickers(self) -> bool: """ @@ -61,17 +54,13 @@ class MarketCapPairList(IPairList): """ return False - def _validate_keys(self, key): - return key in MODE_VALUES - def short_desc(self) -> str: """ Short whitelist method description - used for startup-messages """ - num = self._pairlistconfig['number_assets'] - msg = f"{self.name} - Only include pairs ranked within top {num} market cap." - if self._mode == "total_assets": - msg = f"{self.name} - top {num} pairs sorted by market cap." + num = self._number_assets + rank = self._max_rank + msg = f"{self.name} - {num} pairs placed within top {rank} market cap." return msg @staticmethod @@ -84,15 +73,14 @@ class MarketCapPairList(IPairList): "number_assets": { "type": "number", "default": 30, - "description": "Max market cap rank", - "help": "Only use assets with high market cap rank", + "description": "Number of assets", + "help": "Number of assets to use from the pairlist", }, - "mode": { - "type": "option", - "default": "top_rank", - "options": MODE_VALUES, - "description": "Mode of number", - "help": "How to interpret the number", + "max_rank": { + "type": "number", + "default": 30, + "description": "Max rank of assets", + "help": "Maximum rank of assets to use from the pairlist", }, "refresh_period": { "type": "number", @@ -149,28 +137,19 @@ class MarketCapPairList(IPairList): if marketcap_list: filtered_pairlist = [] - if self._mode == 'top_rank': - top_marketcap = marketcap_list[:self._number_assets:] + market = self._config['trading_mode'] + pair_format = f"{self._stake_currency.upper()}" + if (market == 'futures'): + pair_format += f":{self._stake_currency.upper()}" - for pair in pairlist: - base = self._exchange.get_pair_base_currency(pair) - if base.lower() in top_marketcap: - filtered_pairlist.append(pair) - else: - self.log_once(f"Remove {pair} from whitelist because it's not ranked " - f"within top {self._number_assets} market cap", logger.info) + top_marketcap = marketcap_list[:self._max_rank:] - else: - market = self._config['trading_mode'] - pair_format = f"{self._stake_currency.upper()}" - if (market == 'futures'): - pair_format += f":{self._stake_currency.upper()}" - for mc_pair in marketcap_list: - test_pair = f"{mc_pair.upper()}/{pair_format}" - if test_pair in pairlist: - filtered_pairlist.append(test_pair) - if len(filtered_pairlist) == self._number_assets: - break + for mc_pair in top_marketcap: + test_pair = f"{mc_pair.upper()}/{pair_format}" + if test_pair in pairlist: + filtered_pairlist.append(test_pair) + if len(filtered_pairlist) == self._number_assets: + break if len(filtered_pairlist) > 0: return filtered_pairlist diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 3d26979e1..72fce666d 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -1517,29 +1517,29 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: @pytest.mark.parametrize('pairlists,result', [ ([ - # Test top 2 mc + # Get 2 pairs {"method": "StaticPairList", "allow_inactive": True}, - {"method": "MarketCapPairList", "mode": "top_rank", "number_assets": 2} - ], ['ETH/USDT', 'BTC/USDT']), - ([ - # Test top 6 mc - {"method": "StaticPairList", "allow_inactive": True}, - {"method": "MarketCapPairList", "mode": "top_rank", "number_assets": 6} - ], ['ETH/USDT', 'XRP/USDT', 'BTC/USDT']), - ([ - # Test total assets mode, 2 assets - {"method": "StaticPairList", "allow_inactive": True}, - {"method": "MarketCapPairList", "mode": "total_assets", "number_assets": 2} + {"method": "MarketCapPairList", "number_assets": 2} ], ['BTC/USDT', 'ETH/USDT']), + ([ + # Get 6 pairs + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "MarketCapPairList", "number_assets": 6} + ], ['BTC/USDT', 'ETH/USDT', 'XRP/USDT', 'ADA/USDT']), + ([ + # Get 3 pairs within top 6 ranks + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "MarketCapPairList", "max_rank": 6, "number_assets": 3} + ], ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']), ([ - # Test total assets mode, 5 assets + # Get 4 pairs within top 8 ranks {"method": "StaticPairList", "allow_inactive": True}, - {"method": "MarketCapPairList", "mode": "total_assets", "number_assets": 5} + {"method": "MarketCapPairList", "max_rank": 8, "number_assets": 4} ], ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']), ([ # MarketCapPairList as generator - {"method": "MarketCapPairList", "mode": "total_assets", "number_assets": 5} + {"method": "MarketCapPairList", "number_assets": 5} ], ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']) ]) def test_MarketCapPairList_filter(mocker, default_conf_usdt, markets, pairlists, result): @@ -1576,7 +1576,7 @@ def test_MarketCapPairList_filter(mocker, default_conf_usdt, markets, pairlists, } ] - default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT']) + default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT', 'ADA/USDT']) default_conf_usdt['trading_mode'] = 'spot' default_conf_usdt['pairlists'] = pairlists mocker.patch.multiple(EXMS, From 63aac1a2c99ef7f8156f3181c7ef1190154f484e Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Mon, 29 Jan 2024 19:50:19 +0900 Subject: [PATCH 082/247] update docs --- docs/includes/pairlists.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 12613adaf..b9074ab6b 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -267,28 +267,22 @@ The optional `bearer_token` will be included in the requests Authorization Heade #### MarketCapPairList -`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. It will only recognize coins up to the coin placed at rank 250. The number of pairs in the resulted pairlist will be slightly different depends on the `mode` defined in the config (available mode are `top_rank` and `total_assets`). +`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. It will only recognize coins up to the coin placed at rank 250. The returned pairlist will be sorted based of their marketcap ranks. ```json "pairlists": [ { "method": "MarketCapPairList", - "mode": "top_rank", "number_assets": 20, + "max_rank": 50, "refresh_period": 86400 } ] ``` -##### `top_rank` mode +`number_assets` will defines the maximum number of pairs returned by the pairlist. `max_rank` will defines the maximum rank used in creating/filtering the pairlist. Please note that it's normal that some coins that placed within `max_rank` might not included in the resulted pairlist. It's because some coins might not have active trading pairs in your preferred market/stake/exchange. -In this mode, it will return pairlist consist of active and not-blacklisted pairs that are placed at the top `number_assets` rank of the marketcap. - -##### `total_assets` mode - -In this mode, it will return pairlist consist of `number_assets` number of active and not-blacklisted pairs sorted by their marketcap rank. - -The `refresh_period` setting define the period (in seconds) at which the marketcap rank data will be cached. Defaults to 86,400s (1 day). The pairlist cache (`refresh_period`) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list). +`refresh_period` setting define the period (in seconds) at which the marketcap rank data will be cached. Defaults to 86,400s (1 day). The pairlist cache (`refresh_period`) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list). #### AgeFilter From eb7fbb00965168c9bd86b975bdeca5b5f034a3c2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 Jan 2024 19:39:02 +0100 Subject: [PATCH 083/247] Improve doc wording --- docs/includes/pairlists.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index b9074ab6b..9781edf10 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -280,9 +280,9 @@ The optional `bearer_token` will be included in the requests Authorization Heade ] ``` -`number_assets` will defines the maximum number of pairs returned by the pairlist. `max_rank` will defines the maximum rank used in creating/filtering the pairlist. Please note that it's normal that some coins that placed within `max_rank` might not included in the resulted pairlist. It's because some coins might not have active trading pairs in your preferred market/stake/exchange. +`number_assets` defines the maximum number of pairs returned by the pairlist. `max_rank` will determine the maximum rank used in creating/filtering the pairlist. It's expected that some coins within the top `max_rank` marketcap will not be included in the resulting pairlist since not all pairs will have active trading pairs in your preferred market/stake/exchange combination. -`refresh_period` setting define the period (in seconds) at which the marketcap rank data will be cached. Defaults to 86,400s (1 day). The pairlist cache (`refresh_period`) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list). +`refresh_period` setting defines the period (in seconds) at which the marketcap rank data will be refreshed. Defaults to 86,400s (1 day). The pairlist cache (`refresh_period`) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list). #### AgeFilter From d691ffe9db80f36f67a5bff911914e6535652d25 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 Jan 2024 19:46:21 +0100 Subject: [PATCH 084/247] Test more diff. scenario --- tests/plugins/test_pairlist.py | 71 +++++++++++++++------------------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 72fce666d..26227a09f 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -1515,69 +1515,62 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: assert log_has_re(r'Whitelist with 0 pairs: \[]', caplog) -@pytest.mark.parametrize('pairlists,result', [ +@pytest.mark.parametrize('pairlists,trade_mode,result', [ ([ # Get 2 pairs {"method": "StaticPairList", "allow_inactive": True}, {"method": "MarketCapPairList", "number_assets": 2} - ], ['BTC/USDT', 'ETH/USDT']), + ], 'spot', ['BTC/USDT', 'ETH/USDT']), ([ # Get 6 pairs {"method": "StaticPairList", "allow_inactive": True}, {"method": "MarketCapPairList", "number_assets": 6} - ], ['BTC/USDT', 'ETH/USDT', 'XRP/USDT', 'ADA/USDT']), + ], 'spot', ['BTC/USDT', 'ETH/USDT', 'XRP/USDT', 'ADA/USDT']), ([ # Get 3 pairs within top 6 ranks {"method": "StaticPairList", "allow_inactive": True}, {"method": "MarketCapPairList", "max_rank": 6, "number_assets": 3} - ], ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']), + ], 'spot', ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']), ([ # Get 4 pairs within top 8 ranks {"method": "StaticPairList", "allow_inactive": True}, {"method": "MarketCapPairList", "max_rank": 8, "number_assets": 4} - ], ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']), + ], 'spot', ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']), ([ # MarketCapPairList as generator {"method": "MarketCapPairList", "number_assets": 5} - ], ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']) + ], 'spot', ['BTC/USDT', 'ETH/USDT', 'XRP/USDT']), + ([ + # MarketCapPairList as generator - low max_rank + {"method": "MarketCapPairList", "max_rank": 2, "number_assets": 5} + ], 'spot', ['BTC/USDT', 'ETH/USDT']), + ([ + # MarketCapPairList as generator - futures - low max_rank + {"method": "MarketCapPairList", "max_rank": 2, "number_assets": 5} + ], 'futures', ['ETH/USDT:USDT']), + ([ + # MarketCapPairList as generator - futures - low number_assets + {"method": "MarketCapPairList", "number_assets": 2} + ], 'futures', ['ETH/USDT:USDT', 'ADA/USDT:USDT']), ]) -def test_MarketCapPairList_filter(mocker, default_conf_usdt, markets, pairlists, result): +def test_MarketCapPairList_filter(mocker, default_conf_usdt, trade_mode, markets, pairlists, result): test_value = [ - { - "symbol": "btc", - }, - { - "symbol": "eth", - }, - { - "symbol": "usdt", - }, - { - "symbol": "bnb", - }, - { - "symbol": "sol", - }, - { - "symbol": "xrp", - }, - { - "symbol": "usdc", - }, - { - "symbol": "steth", - }, - { - "symbol": "ada", - }, - { - "symbol": "avax", - } + {"symbol": "btc"}, + {"symbol": "eth"}, + {"symbol": "usdt"}, + {"symbol": "bnb"}, + {"symbol": "sol"}, + {"symbol": "xrp"}, + {"symbol": "usdc"}, + {"symbol": "steth"}, + {"symbol": "ada"}, + {"symbol": "avax"}, ] - default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT', 'ADA/USDT']) - default_conf_usdt['trading_mode'] = 'spot' + default_conf_usdt['trading_mode'] = trade_mode + if trade_mode == 'spot': + default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT', 'ADA/USDT']) default_conf_usdt['pairlists'] = pairlists mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets), From 4a580fc72f9f6e28de0c37eade3b721ad9a45e4d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 Jan 2024 20:01:21 +0100 Subject: [PATCH 085/247] Add test validating cache --- tests/plugins/test_pairlist.py | 51 ++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 26227a09f..c48347f2d 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -18,6 +18,7 @@ from freqtrade.persistence import LocalTrade, Trade from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist, expand_pairlist from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.resolvers import PairListResolver +from freqtrade.util.datetime_helpers import dt_now from tests.conftest import (EXMS, create_mock_trades_usdt, get_patched_exchange, get_patched_freqtradebot, log_has, log_has_re, num_log_has) @@ -1586,3 +1587,53 @@ def test_MarketCapPairList_filter(mocker, default_conf_usdt, trade_mode, markets pm.refresh_pairlist() assert pm.whitelist == result + + +def test_MarketCapPairList_timing(mocker, default_conf_usdt, markets, time_machine): + test_value = [ + {"symbol": "btc"}, + {"symbol": "eth"}, + {"symbol": "usdt"}, + {"symbol": "bnb"}, + {"symbol": "sol"}, + {"symbol": "xrp"}, + {"symbol": "usdc"}, + {"symbol": "steth"}, + {"symbol": "ada"}, + {"symbol": "avax"}, + ] + + default_conf_usdt['trading_mode'] = 'spot' + default_conf_usdt['exchange']['pair_whitelist'].extend(['BTC/USDT', 'ETC/USDT', 'ADA/USDT']) + default_conf_usdt['pairlists'] = [{"method": "MarketCapPairList", "number_assets": 2}] + + markets_mock = MagicMock(return_value=markets) + mocker.patch.multiple(EXMS, + get_markets=markets_mock, + exchange_has=MagicMock(return_value=True), + ) + + mocker.patch("freqtrade.plugins.pairlist.MarketCapPairList.CoinGeckoAPI.get_coins_markets", + return_value=test_value) + + start_dt = dt_now() + + exchange = get_patched_exchange(mocker, default_conf_usdt) + time_machine.move_to(start_dt) + + pm = PairListManager(exchange, default_conf_usdt) + markets_mock.reset_mock() + pm.refresh_pairlist() + assert markets_mock.call_count == 3 + markets_mock.reset_mock() + + time_machine.move_to(start_dt + timedelta(hours=20)) + pm.refresh_pairlist() + # Cached pairlist ... + assert markets_mock.call_count == 1 + + markets_mock.reset_mock() + time_machine.move_to(start_dt + timedelta(days=2)) + pm.refresh_pairlist() + # No longer cached pairlist ... + assert markets_mock.call_count == 3 From 01baea8aab84abdfbe32d4677dd5b2e4fdedcb7d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 Jan 2024 20:08:25 +0100 Subject: [PATCH 086/247] Test exceptions / errors --- tests/plugins/test_pairlist.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index c48347f2d..c3a7e4c66 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -1637,3 +1637,19 @@ def test_MarketCapPairList_timing(mocker, default_conf_usdt, markets, time_machi pm.refresh_pairlist() # No longer cached pairlist ... assert markets_mock.call_count == 3 + + +def test_MarketCapPairList_exceptions(mocker, default_conf_usdt, markets, time_machine): + + exchange = get_patched_exchange(mocker, default_conf_usdt) + default_conf_usdt['pairlists'] = [{"method": "MarketCapPairList"}] + with pytest.raises(OperationalException, match=r"`number_assets` not specified.*"): + # No number_assets + PairListManager(exchange, default_conf_usdt) + + default_conf_usdt['pairlists'] = [{ + "method": "MarketCapPairList", 'number_assets': 20, 'max_rank': 260 + }] + with pytest.raises(OperationalException, + match="This filter only support marketcap rank up to 250."): + PairListManager(exchange, default_conf_usdt) From 470a239e8279be842f998062815183e52c982ac8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 Jan 2024 20:10:50 +0100 Subject: [PATCH 087/247] Formatting ... --- tests/plugins/test_pairlist.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index c3a7e4c66..09dcd0af3 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -1555,7 +1555,9 @@ def test_FullTradesFilter(mocker, default_conf_usdt, fee, caplog) -> None: {"method": "MarketCapPairList", "number_assets": 2} ], 'futures', ['ETH/USDT:USDT', 'ADA/USDT:USDT']), ]) -def test_MarketCapPairList_filter(mocker, default_conf_usdt, trade_mode, markets, pairlists, result): +def test_MarketCapPairList_filter( + mocker, default_conf_usdt, trade_mode, markets, pairlists, result +): test_value = [ {"symbol": "btc"}, {"symbol": "eth"}, From 36d4f10a60f48f2343f22b968e50a6da74f2ea71 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Tue, 30 Jan 2024 03:03:17 +0000 Subject: [PATCH 088/247] chore: update pre-commit hooks --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ce36191b7..979fbe083 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.1.14' + rev: 'v0.1.15' hooks: - id: ruff From 6c27b27d4f841b8bdbc28c37577acb1560ec710b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jan 2024 07:00:44 +0100 Subject: [PATCH 089/247] Bump version to 2024.2-dev --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 204b1a0ff..7c699d643 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2024.1-dev' +__version__ = '2024.2-dev' if 'dev' in __version__: from pathlib import Path From d219983f5bcbd25a48e5a91e51914d3f82995bfd Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jan 2024 07:02:57 +0100 Subject: [PATCH 090/247] Bump ruff version --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a2a3da2b8..04d4a8563 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==3.3.1 -ruff==0.1.14 +ruff==0.1.15 mypy==1.8.0 pre-commit==3.6.0 pytest==7.4.4 From cfc6d3b32fc844bdcb5b880b2bcb0d1edc59e67f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jan 2024 07:04:41 +0100 Subject: [PATCH 091/247] Apply "Dependencies" label to pull requests --- .github/workflows/pre-commit-update.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml index 9a6d5bfe2..bd50a9c3c 100644 --- a/.github/workflows/pre-commit-update.yml +++ b/.github/workflows/pre-commit-update.yml @@ -36,6 +36,7 @@ jobs: add-paths: .pre-commit-config.yaml labels: | Tech maintenance + Dependencies branch: update/pre-commit-hooks title: Update pre-commit hooks commit-message: "chore: update pre-commit hooks" From c59ae30e31ff3193e586e98d67c1574bbb81751b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jan 2024 07:24:15 +0100 Subject: [PATCH 092/247] chore: Add minified parameter docs to to_json method --- freqtrade/persistence/trade_model.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 51520fa32..856a33abf 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -558,6 +558,11 @@ class LocalTrade: ) def to_json(self, minified: bool = False) -> Dict[str, Any]: + """ + :param minified: If True, only return a subset of the data is returned. + Only used for backtesting. + :return: Dictionary with trade data + """ filled_or_open_orders = self.select_filled_or_open_orders() orders_json = [order.to_json(self.entry_side, minified) for order in filled_or_open_orders] From d82bfc9fad00abcd750bb5ec32740582b906ecf9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 28 Jan 2024 19:09:25 +0100 Subject: [PATCH 093/247] Add order_tag to orders model --- freqtrade/persistence/migrations.py | 6 ++++-- freqtrade/persistence/trade_model.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index bb6c04922..4c748b15d 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -223,6 +223,7 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List): ft_amount = get_column_def(cols_order, 'ft_amount', 'coalesce(amount, 0.0)') ft_price = get_column_def(cols_order, 'ft_price', 'coalesce(price, 0.0)') ft_cancel_reason = get_column_def(cols_order, 'ft_cancel_reason', 'null') + ft_order_tag = get_column_def(cols_order, 'ft_order_tag', 'null') # sqlite does not support literals for booleans with engine.begin() as connection: @@ -230,13 +231,14 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List): insert into orders (id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status, symbol, order_type, side, price, amount, filled, average, remaining, cost, stop_price, order_date, order_filled_date, order_update_date, ft_fee_base, funding_fee, - ft_amount, ft_price, ft_cancel_reason + ft_amount, ft_price, ft_cancel_reason, ft_order_tag ) select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status, symbol, order_type, side, price, amount, filled, {average} average, remaining, cost, {stop_price} stop_price, order_date, order_filled_date, order_update_date, {ft_fee_base} ft_fee_base, {funding_fee} funding_fee, - {ft_amount} ft_amount, {ft_price} ft_price, {ft_cancel_reason} ft_cancel_reason + {ft_amount} ft_amount, {ft_price} ft_price, {ft_cancel_reason} ft_cancel_reason, + {ft_order_tag} ft_order_tag from {table_back_name} """)) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 856a33abf..005017dbb 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -89,6 +89,8 @@ class Order(ModelBase): funding_fee: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) ft_fee_base: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) + ft_order_tag: Mapped[Optional[str]] = mapped_column(String(CUSTOM_TAG_MAX_LENGTH), + nullable=True) @property def order_date_utc(self) -> datetime: From ccd4c715ca14bb37f46ddd2c1dc56a78c4c6331e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 28 Jan 2024 19:12:53 +0100 Subject: [PATCH 094/247] add order-tag to serialize / unserialize methods --- freqtrade/persistence/trade_model.py | 2 ++ freqtrade/rpc/api_server/api_schemas.py | 1 + 2 files changed, 3 insertions(+) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 005017dbb..2202b6e95 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -242,6 +242,7 @@ class Order(ModelBase): 'remaining': self.remaining, 'ft_fee_base': self.ft_fee_base, 'funding_fee': self.funding_fee, + 'ft_order_tag': self.ft_order_tag, }) return resp @@ -1407,6 +1408,7 @@ class LocalTrade: ft_price=order["price"], remaining=order["remaining"], funding_fee=order.get("funding_fee", None), + ft_order_tag=order.get("ft_order_tag", None), ) trade.orders.append(order_obj) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 20a614798..791f70fa0 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -261,6 +261,7 @@ class OrderSchema(BaseModel): order_timestamp: Optional[int] = None order_filled_timestamp: Optional[int] = None ft_fee_base: Optional[float] = None + ft_order_tag: Optional[str] = None class TradeSchema(BaseModel): From 39ffee381b99eb3e37db6ab32f9b2fea96824333 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 28 Jan 2024 19:23:18 +0100 Subject: [PATCH 095/247] Improve type hint --- freqtrade/optimize/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 2aa8a23d6..e3384d3e2 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -685,7 +685,7 @@ class Backtesting: return None def _exit_trade(self, trade: LocalTrade, sell_row: Tuple, - close_rate: float, amount: Optional[float] = None) -> Optional[LocalTrade]: + close_rate: float, amount: float) -> Optional[LocalTrade]: self.order_id_counter += 1 exit_candle_time = sell_row[DATE_IDX].to_pydatetime() order_type = self.strategy.order_types['exit'] From e8288a34c94c09e914ebf45346f024c3b9a593c6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 28 Jan 2024 19:56:30 +0100 Subject: [PATCH 096/247] add ft_order_tag to backtesting --- freqtrade/optimize/backtesting.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index e3384d3e2..29fafdb97 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -681,11 +681,11 @@ class Backtesting: trade.exit_reason = exit_reason - return self._exit_trade(trade, row, close_rate, amount_) + return self._exit_trade(trade, row, close_rate, amount_, exit_reason) return None - def _exit_trade(self, trade: LocalTrade, sell_row: Tuple, - close_rate: float, amount: float) -> Optional[LocalTrade]: + def _exit_trade(self, trade: LocalTrade, sell_row: Tuple, close_rate: float, + amount: float, exit_reason: Optional[str]) -> Optional[LocalTrade]: self.order_id_counter += 1 exit_candle_time = sell_row[DATE_IDX].to_pydatetime() order_type = self.strategy.order_types['exit'] @@ -712,6 +712,7 @@ class Backtesting: filled=0, remaining=amount, cost=amount * close_rate, + ft_order_tag=exit_reason, ) order._trade_bt = trade trade.orders.append(order) @@ -944,6 +945,7 @@ class Backtesting: filled=0, remaining=amount, cost=amount * propose_rate + trade.fee_open, + ft_order_tag=entry_tag, ) order._trade_bt = trade trade.orders.append(order) @@ -963,7 +965,8 @@ class Backtesting: # Ignore trade if entry-order did not fill yet continue exit_row = data[pair][-1] - self._exit_trade(trade, exit_row, exit_row[OPEN_IDX], trade.amount) + self._exit_trade(trade, exit_row, exit_row[OPEN_IDX], trade.amount, + ExitType.FORCE_EXIT.value) trade.orders[-1].close_bt_order(exit_row[DATE_IDX].to_pydatetime(), trade) trade.close_date = exit_row[DATE_IDX].to_pydatetime() From 95e51bf816971e42f94d6e3c4b96ed50af9c34c7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 28 Jan 2024 20:05:40 +0100 Subject: [PATCH 097/247] allow adjust_trade_position to return tuples in backtesting --- freqtrade/optimize/backtesting.py | 14 +++++++++++--- freqtrade/strategy/interface.py | 4 +++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 29fafdb97..8386a04e3 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -537,14 +537,22 @@ class Backtesting: min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, current_rate, -0.1) max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_rate) stake_available = self.wallets.get_available_stake_amount() - stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, - default_retval=None, supress_error=True)( + resp = strategy_safe_wrapper(self.strategy.adjust_trade_position, + default_retval=None, supress_error=True)( trade=trade, # type: ignore[arg-type] current_time=current_time, current_rate=current_rate, current_profit=current_profit, min_stake=min_stake, max_stake=min(max_stake, stake_available), current_entry_rate=current_rate, current_exit_rate=current_rate, current_entry_profit=current_profit, current_exit_profit=current_profit) + order_tag = '' + if isinstance(resp, tuple): + if len(resp) >= 1: + stake_amount = resp[0] + if len(resp) > 1: + order_tag = resp[1] or '' + else: + stake_amount = resp # Check if we should increase our position if stake_amount is not None and stake_amount > 0.0: @@ -569,7 +577,7 @@ class Backtesting: if min_stake and remaining != 0 and remaining < min_stake: # Remaining stake is too low to be sold. return trade - exit_ = ExitCheckTuple(ExitType.PARTIAL_EXIT) + exit_ = ExitCheckTuple(ExitType.PARTIAL_EXIT, order_tag) pos_trade = self._get_exit_for_signal(trade, row, exit_, current_time, amount) if pos_trade is not None: order = pos_trade.orders[-1] diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 7f10c2ea2..341dd0687 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -511,7 +511,8 @@ class IStrategy(ABC, HyperStrategyMixin): min_stake: Optional[float], max_stake: float, current_entry_rate: float, current_exit_rate: float, current_entry_profit: float, current_exit_profit: float, - **kwargs) -> Optional[float]: + **kwargs + ) -> Union[Optional[float], Tuple[Optional[float], Optional[str]]]: """ Custom trade adjustment logic, returning the stake amount that a trade should be increased or decreased. @@ -537,6 +538,7 @@ class IStrategy(ABC, HyperStrategyMixin): :return float: Stake amount to adjust your trade, Positive values to increase position, Negative values to decrease position. Return None for no action. + Optionally, return a tuple with a 2nd element with an order reason """ return None From 535ff387ff6a870d386a4fd05cf3152c7f6bde0e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 28 Jan 2024 20:12:37 +0100 Subject: [PATCH 098/247] add order_tag handlig to running bot --- freqtrade/freqtradebot.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0d7cef827..3355ed8d4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -645,8 +645,8 @@ class FreqtradeBot(LoggingMixin): max_entry_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_entry_rate) stake_available = self.wallets.get_available_stake_amount() logger.debug(f"Calling adjust_trade_position for pair {trade.pair}") - stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, - default_retval=None, supress_error=True)( + resp = strategy_safe_wrapper(self.strategy.adjust_trade_position, + default_retval=None, supress_error=True)( trade=trade, current_time=datetime.now(timezone.utc), current_rate=current_entry_rate, current_profit=current_entry_profit, min_stake=min_entry_stake, @@ -654,6 +654,14 @@ class FreqtradeBot(LoggingMixin): current_entry_rate=current_entry_rate, current_exit_rate=current_exit_rate, current_entry_profit=current_entry_profit, current_exit_profit=current_exit_profit ) + order_tag = '' + if isinstance(resp, tuple): + if len(resp) >= 1: + stake_amount = resp[0] + if len(resp) > 1: + order_tag = resp[1] or '' + else: + stake_amount = resp if stake_amount is not None and stake_amount > 0.0: # We should increase our position @@ -665,7 +673,8 @@ class FreqtradeBot(LoggingMixin): else: logger.debug("Max adjustment entries is set to unlimited.") self.execute_entry(trade.pair, stake_amount, price=current_entry_rate, - trade=trade, is_short=trade.is_short, mode='pos_adjust') + trade=trade, is_short=trade.is_short, mode='pos_adjust', + enter_tag=order_tag) if stake_amount is not None and stake_amount < 0.0: # We should decrease our position @@ -684,7 +693,7 @@ class FreqtradeBot(LoggingMixin): return self.execute_trade_exit(trade, current_exit_rate, exit_check=ExitCheckTuple( - exit_type=ExitType.PARTIAL_EXIT), sub_trade_amt=amount) + exit_type=ExitType.PARTIAL_EXIT), sub_trade_amt=amount, exit_tag=order_tag) def _check_depth_of_market(self, pair: str, conf: Dict, side: SignalDirection) -> bool: """ @@ -782,6 +791,7 @@ class FreqtradeBot(LoggingMixin): leverage=leverage ) order_obj = Order.parse_from_ccxt_object(order, pair, side, amount, enter_limit_requested) + order_obj.ft_order_tag = enter_tag order_id = order['id'] order_status = order.get('status') logger.info(f"Order {order_id} was created for {pair} and status is {order_status}.") @@ -1753,6 +1763,7 @@ class FreqtradeBot(LoggingMixin): return False order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side, amount, limit) + order_obj.ft_order_tag = exit_reason trade.orders.append(order_obj) trade.exit_order_status = '' From 830a004dfda7d34dcf88c95ab9147b5e090271f4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 28 Jan 2024 20:21:47 +0100 Subject: [PATCH 099/247] Move response handling to interface wrappermethod --- freqtrade/freqtradebot.py | 11 +---------- freqtrade/optimize/backtesting.py | 14 +++----------- freqtrade/strategy/interface.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 3355ed8d4..0eb1c608a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -645,8 +645,7 @@ class FreqtradeBot(LoggingMixin): max_entry_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_entry_rate) stake_available = self.wallets.get_available_stake_amount() logger.debug(f"Calling adjust_trade_position for pair {trade.pair}") - resp = strategy_safe_wrapper(self.strategy.adjust_trade_position, - default_retval=None, supress_error=True)( + stake_amount, order_tag = self.strategy._adjust_trade_position_internal( trade=trade, current_time=datetime.now(timezone.utc), current_rate=current_entry_rate, current_profit=current_entry_profit, min_stake=min_entry_stake, @@ -654,14 +653,6 @@ class FreqtradeBot(LoggingMixin): current_entry_rate=current_entry_rate, current_exit_rate=current_exit_rate, current_entry_profit=current_entry_profit, current_exit_profit=current_exit_profit ) - order_tag = '' - if isinstance(resp, tuple): - if len(resp) >= 1: - stake_amount = resp[0] - if len(resp) > 1: - order_tag = resp[1] or '' - else: - stake_amount = resp if stake_amount is not None and stake_amount > 0.0: # We should increase our position diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 8386a04e3..7c7fa60ed 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -537,22 +537,14 @@ class Backtesting: min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, current_rate, -0.1) max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_rate) stake_available = self.wallets.get_available_stake_amount() - resp = strategy_safe_wrapper(self.strategy.adjust_trade_position, - default_retval=None, supress_error=True)( + stake_amount, order_tag = self.strategy._adjust_trade_position_internal( trade=trade, # type: ignore[arg-type] current_time=current_time, current_rate=current_rate, current_profit=current_profit, min_stake=min_stake, max_stake=min(max_stake, stake_available), current_entry_rate=current_rate, current_exit_rate=current_rate, - current_entry_profit=current_profit, current_exit_profit=current_profit) - order_tag = '' - if isinstance(resp, tuple): - if len(resp) >= 1: - stake_amount = resp[0] - if len(resp) > 1: - order_tag = resp[1] or '' - else: - stake_amount = resp + current_entry_profit=current_profit, current_exit_profit=current_profit + ) # Check if we should increase our position if stake_amount is not None and stake_amount > 0.0: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 341dd0687..564d306d7 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -727,6 +727,36 @@ class IStrategy(ABC, HyperStrategyMixin): _ft_stop_uses_after_fill = False + def _adjust_trade_position_internal( + self, trade: Trade, current_time: datetime, + current_rate: float, current_profit: float, + min_stake: Optional[float], max_stake: float, + current_entry_rate: float, current_exit_rate: float, + current_entry_profit: float, current_exit_profit: float, + **kwargs + ) -> Tuple[Optional[float], str]: + """ + wrapper around adjust_trade_position to handle the return value + """ + resp = strategy_safe_wrapper(self.adjust_trade_position, + default_retval=(None, ''), supress_error=True)( + trade=trade, current_time=current_time, + current_rate=current_rate, current_profit=current_profit, + min_stake=min_stake, max_stake=max_stake, + current_entry_rate=current_entry_rate, current_exit_rate=current_exit_rate, + current_entry_profit=current_entry_profit, current_exit_profit=current_exit_profit, + **kwargs + ) + order_tag = '' + if isinstance(resp, tuple): + if len(resp) >= 1: + stake_amount = resp[0] + if len(resp) > 1: + order_tag = resp[1] or '' + else: + stake_amount = resp + return stake_amount, order_tag + def __informative_pairs_freqai(self) -> ListPairsWithTimeframes: """ Create informative-pairs needed for FreqAI From 2d704a77b55749650920a198a093e7f23352eed0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 28 Jan 2024 22:42:03 +0100 Subject: [PATCH 100/247] Improve formatting --- freqtrade/strategy/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 564d306d7..2630c3547 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -734,7 +734,7 @@ class IStrategy(ABC, HyperStrategyMixin): current_entry_rate: float, current_exit_rate: float, current_entry_profit: float, current_exit_profit: float, **kwargs - ) -> Tuple[Optional[float], str]: + ) -> Tuple[Optional[float], str]: """ wrapper around adjust_trade_position to handle the return value """ From 398b93beefd73fbe0a2cf175b1fda4ced1a6ae88 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 28 Jan 2024 22:48:26 +0100 Subject: [PATCH 101/247] Fix rpc test --- tests/rpc/test_rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 785efc522..ca81ea0e6 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -99,7 +99,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, 'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY, 'remaining': ANY, 'status': ANY, 'ft_is_entry': True, 'ft_fee_base': None, - 'funding_fee': ANY, + 'funding_fee': ANY, 'ft_order_tag': None, }], } mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) From d664e76834566ad49f4c08d31b0044333a116c5c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 28 Jan 2024 23:05:01 +0100 Subject: [PATCH 102/247] Add some tests --- tests/optimize/test_backtesting_adjust_position.py | 6 ++++-- tests/test_freqtradebot.py | 8 ++++++-- tests/test_integration.py | 5 +++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/optimize/test_backtesting_adjust_position.py b/tests/optimize/test_backtesting_adjust_position.py index 56b04b3fd..7f7bbb29f 100644 --- a/tests/optimize/test_backtesting_adjust_position.py +++ b/tests/optimize/test_backtesting_adjust_position.py @@ -148,7 +148,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera assert pytest.approx(trade.amount) == 47.61904762 * leverage assert len(trade.orders) == 1 # Increase position by 100 - backtesting.strategy.adjust_trade_position = MagicMock(return_value=100) + backtesting.strategy.adjust_trade_position = MagicMock(return_value=(100, 'PartIncrease')) trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time) @@ -156,6 +156,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera assert pytest.approx(trade.stake_amount) == 200.0 assert pytest.approx(trade.amount) == 95.23809524 * leverage assert len(trade.orders) == 2 + assert trade.orders[-1].ft_order_tag == 'PartIncrease' assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791) # Reduce by more than amount - no change to trade. @@ -171,13 +172,14 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791) # Reduce position by 50 - backtesting.strategy.adjust_trade_position = MagicMock(return_value=-100) + backtesting.strategy.adjust_trade_position = MagicMock(return_value=(-100, 'partDecrease')) trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time) assert trade assert pytest.approx(trade.stake_amount) == 100.0 assert pytest.approx(trade.amount) == 47.61904762 * leverage assert len(trade.orders) == 3 + assert trade.orders[-1].ft_order_tag == 'partDecrease' assert trade.nr_of_successful_entries == 2 assert trade.nr_of_successful_exits == 1 assert pytest.approx(trade.liquidation_price) == (0.1038916 if leverage == 1 else 1.2127791) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e61d5804d..8e17604ab 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -6725,11 +6725,15 @@ def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, ca ) create_mock_trades(fee) caplog.set_level(logging.DEBUG) - freqtrade.strategy.adjust_trade_position = MagicMock(return_value=10) + freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(10, 'aaaa')) freqtrade.process_open_trade_positions() assert log_has_re(r"Max adjustment entries for .* has been reached\.", caplog) + assert freqtrade.strategy.adjust_trade_position.call_count == 1 caplog.clear() - freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-10) + freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-10, 'partial_exit_c')) freqtrade.process_open_trade_positions() assert log_has_re(r"LIMIT_SELL has been fulfilled.*", caplog) + assert freqtrade.strategy.adjust_trade_position.call_count == 1 + trade = Trade.get_trades(trade_filter=[Trade.id == 5]).first() + assert trade.orders[-1].ft_order_tag == 'partial_exit_c' diff --git a/tests/test_integration.py b/tests/test_integration.py index 2e7f38fc8..94253dffb 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -536,7 +536,7 @@ def test_dca_order_adjust_entry_replace_fails( # Create DCA order for 2nd trade (so we have 2 open orders on 2 trades) # this 2nd order won't fill. - freqtrade.strategy.adjust_trade_position = MagicMock(return_value=20) + freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(20, 'PeNF')) freqtrade.process() @@ -627,12 +627,13 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera assert log_has_re( r"Remaining amount of \d\.\d+.* would be smaller than the minimum of 10.", caplog) - freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-20) + freqtrade.strategy.adjust_trade_position = MagicMock(return_value=(-20, 'PES')) freqtrade.process() trade = Trade.get_trades().first() assert len(trade.orders) == 2 assert trade.orders[-1].ft_order_side == 'sell' + assert trade.orders[-1].ft_order_tag == 'PES' assert pytest.approx(trade.stake_amount) == 40.198 assert pytest.approx(trade.amount) == 20.099 * leverage assert trade.open_rate == 2.0 From 79b8496f38c0b8427f068a2618258921b1cfda1a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 28 Jan 2024 23:05:19 +0100 Subject: [PATCH 103/247] Fix backtesting not setting entry_tag out of position adjustments --- freqtrade/optimize/backtesting.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 7c7fa60ed..21e9c75cc 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -554,7 +554,8 @@ class Backtesting: check_adjust_entry = (entry_count <= self.strategy.max_entry_position_adjustment) if check_adjust_entry: pos_trade = self._enter_trade( - trade.pair, row, 'short' if trade.is_short else 'long', stake_amount, trade) + trade.pair, row, 'short' if trade.is_short else 'long', stake_amount, trade, + entry_tag1=order_tag) if pos_trade is not None: self.wallets.update() return pos_trade @@ -836,7 +837,9 @@ class Backtesting: stake_amount: Optional[float] = None, trade: Optional[LocalTrade] = None, requested_rate: Optional[float] = None, - requested_stake: Optional[float] = None) -> Optional[LocalTrade]: + requested_stake: Optional[float] = None, + entry_tag1: Optional[str] = None + ) -> Optional[LocalTrade]: """ :param trade: Trade to adjust - initial entry if None :param requested_rate: Adjusted entry rate @@ -844,7 +847,7 @@ class Backtesting: """ current_time = row[DATE_IDX].to_pydatetime() - entry_tag = row[ENTER_TAG_IDX] if len(row) >= ENTER_TAG_IDX + 1 else None + entry_tag = entry_tag1 or (row[ENTER_TAG_IDX] if len(row) >= ENTER_TAG_IDX + 1 else None) # let's call the custom entry price, using the open price as default price order_type = self.strategy.order_types['entry'] pos_adjust = trade is not None and requested_rate is None From 6806fab1b5339499ec825988a637541dbb2eddb4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 29 Jan 2024 06:32:29 +0100 Subject: [PATCH 104/247] Fix migration not triggering --- freqtrade/persistence/migrations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 4c748b15d..f4d5a7174 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -333,8 +333,8 @@ def check_migrate(engine, decl_base, previous_tables) -> None: # if ('orders' not in previous_tables # or not has_column(cols_orders, 'funding_fee')): migrating = False - # if not has_column(cols_orders, 'ft_cancel_reason'): - if not has_column(cols_trades, 'funding_fee_running'): + # if not has_column(cols_trades, 'funding_fee_running'): + if not has_column(cols_orders, 'ft_order_tag'): migrating = True logger.info(f"Running database migration for trades - " f"backup: {table_back_name}, {order_table_bak_name}") From 0fa0f49b75fd4ad2b6a88741a27d827b9588fa64 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jan 2024 07:20:39 +0100 Subject: [PATCH 105/247] Add adjustment order tagging in strategy callbacks docs --- docs/strategy-callbacks.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 7242e9c90..2292b7ed0 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -767,6 +767,7 @@ This callback is **not** called when there is an open order (either buy or sell) `adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible. Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position (negative values will decrease your position), no matter if it's a long or short trade. +Adjustment orders can be assigned with a tag by returning a 2 element Tuple, with the first element being the adjustment amount, and the 2nd element the tag (e.g. `return 250, 'increase_favorable_conditions'`). Modifications to leverage are not possible, and the stake-amount returned is assumed to be before applying leverage. @@ -833,7 +834,8 @@ class DigDeeperStrategy(IStrategy): min_stake: Optional[float], max_stake: float, current_entry_rate: float, current_exit_rate: float, current_entry_profit: float, current_exit_profit: float, - **kwargs) -> Optional[float]: + **kwargs + ) -> Union[Optional[float], Tuple[Optional[float], Optional[str]]]: """ Custom trade adjustment logic, returning the stake amount that a trade should be increased or decreased. @@ -859,11 +861,12 @@ class DigDeeperStrategy(IStrategy): :return float: Stake amount to adjust your trade, Positive values to increase position, Negative values to decrease position. Return None for no action. + Optionally, return a tuple with a 2nd element with an order reason """ if current_profit > 0.05 and trade.nr_of_successful_exits == 0: # Take half of the profit at +5% - return -(trade.stake_amount / 2) + return -(trade.stake_amount / 2), 'half_profit_5%' if current_profit > -0.05: return None @@ -891,7 +894,7 @@ class DigDeeperStrategy(IStrategy): stake_amount = filled_entries[0].stake_amount # This then calculates current safety order size stake_amount = stake_amount * (1 + (count_of_entries * 0.25)) - return stake_amount + return stake_amount, '1/3rd_increase' except Exception as exception: return None From 78a1c7247a35bc2f8af037f443dc6c5cda21e2b3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jan 2024 07:25:15 +0100 Subject: [PATCH 106/247] keep ft_order_tag for backtest responses --- freqtrade/persistence/trade_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 2202b6e95..0c59df33d 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -221,6 +221,7 @@ class Order(ModelBase): 'order_filled_timestamp': int(self.order_filled_date.replace( tzinfo=timezone.utc).timestamp() * 1000) if self.order_filled_date else None, 'ft_is_entry': self.ft_order_side == entry_side, + 'ft_order_tag': self.ft_order_tag, } if not minified: resp.update({ @@ -242,7 +243,6 @@ class Order(ModelBase): 'remaining': self.remaining, 'ft_fee_base': self.ft_fee_base, 'funding_fee': self.funding_fee, - 'ft_order_tag': self.ft_order_tag, }) return resp From 6ba896609004626492ae21b423fff0c113a37d01 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jan 2024 07:25:46 +0100 Subject: [PATCH 107/247] chore: Add minified parameter docstring to to_json method --- freqtrade/persistence/trade_model.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 0c59df33d..7e3cf970f 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -214,6 +214,10 @@ class Order(ModelBase): return order def to_json(self, entry_side: str, minified: bool = False) -> Dict[str, Any]: + """ + :param minified: If True, only return a subset of the data is returned. + Only used for backtesting. + """ resp = { 'amount': self.safe_amount, 'safe_price': self.safe_price, From d1a96af5e8b2543895797aa4a2ae345fdcad558d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jan 2024 07:28:36 +0100 Subject: [PATCH 108/247] Add ft_order_Tag to backtest test --- tests/optimize/test_backtesting.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 87e92071f..603fcc310 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -742,14 +742,18 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: 'orders': [ [ {'amount': 0.00957442, 'safe_price': 0.104445, 'ft_order_side': 'buy', - 'order_filled_timestamp': 1517251200000, 'ft_is_entry': True}, + 'order_filled_timestamp': 1517251200000, 'ft_is_entry': True, + 'ft_order_tag': ''}, {'amount': 0.00957442, 'safe_price': 0.10496853383458644, 'ft_order_side': 'sell', - 'order_filled_timestamp': 1517265300000, 'ft_is_entry': False} + 'order_filled_timestamp': 1517265300000, 'ft_is_entry': False, + 'ft_order_tag': 'roi'} ], [ {'amount': 0.0097064, 'safe_price': 0.10302485, 'ft_order_side': 'buy', - 'order_filled_timestamp': 1517283000000, 'ft_is_entry': True}, + 'order_filled_timestamp': 1517283000000, 'ft_is_entry': True, + 'ft_order_tag': ''}, {'amount': 0.0097064, 'safe_price': 0.10354126528822055, 'ft_order_side': 'sell', - 'order_filled_timestamp': 1517285400000, 'ft_is_entry': False} + 'order_filled_timestamp': 1517285400000, 'ft_is_entry': False, + 'ft_order_tag': 'roi'} ] ] }) From cd484bd4831d2149759ff9f4db15c70efddc2523 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jan 2024 19:40:16 +0100 Subject: [PATCH 109/247] Update extract-branch-name to new syntax using GITHUB_OUTPUT --- .github/workflows/ci.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55420b81a..13cb12c83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -505,9 +505,8 @@ jobs: python-version: "3.11" - name: Extract branch name - shell: bash - run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})" - id: extract_branch + run: echo "branch=${GITHUB_REF##*/})" >> "$GITHUB_OUTPUT" + id: extract-branch - name: Dockerhub login env: @@ -536,7 +535,7 @@ jobs: - name: Build and test and push docker images env: - BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }} + BRANCH_NAME: ${{ steps.extract-branch.outputs.branch }} run: | build_helpers/publish_docker_multi.sh @@ -554,8 +553,8 @@ jobs: - name: Extract branch name shell: bash - run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})" - id: extract_branch + run: echo "branch=${GITHUB_REF##*/})" >> "$GITHUB_OUTPUT" + id: extract-branch - name: Dockerhub login env: @@ -566,7 +565,7 @@ jobs: - name: Build and test and push docker images env: - BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }} + BRANCH_NAME: ${{ steps.extract-branch.outputs.branch }} GHCR_USERNAME: ${{ github.actor }} GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | From 7082a8073c7999a63e682c951bd3d86ccd746a31 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jan 2024 19:42:32 +0100 Subject: [PATCH 110/247] add debugging for GITHUB_REF --- .github/workflows/ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13cb12c83..0c39e1773 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -505,8 +505,10 @@ jobs: python-version: "3.11" - name: Extract branch name - run: echo "branch=${GITHUB_REF##*/})" >> "$GITHUB_OUTPUT" id: extract-branch + run: | + echo "GITHUB_REF=`${GITHUB_REF}`" + echo "branch=${GITHUB_REF##*/})" >> "$GITHUB_OUTPUT" - name: Dockerhub login env: @@ -552,9 +554,10 @@ jobs: - uses: actions/checkout@v4 - name: Extract branch name - shell: bash - run: echo "branch=${GITHUB_REF##*/})" >> "$GITHUB_OUTPUT" id: extract-branch + run: | + echo "GITHUB_REF=`${GITHUB_REF}`" + echo "branch=${GITHUB_REF##*/})" >> "$GITHUB_OUTPUT" - name: Dockerhub login env: From 13ee90725eb41b1314f6bc7b9a5f23c6c3156d77 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jan 2024 20:19:24 +0100 Subject: [PATCH 111/247] Fix syntax error --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c39e1773..a406a8c92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -508,7 +508,7 @@ jobs: id: extract-branch run: | echo "GITHUB_REF=`${GITHUB_REF}`" - echo "branch=${GITHUB_REF##*/})" >> "$GITHUB_OUTPUT" + echo "branch=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT" - name: Dockerhub login env: @@ -557,7 +557,7 @@ jobs: id: extract-branch run: | echo "GITHUB_REF=`${GITHUB_REF}`" - echo "branch=${GITHUB_REF##*/})" >> "$GITHUB_OUTPUT" + echo "branch=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT" - name: Dockerhub login env: From 0bc5c876c2046527190fa7cef269bf21a8a8a136 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Jan 2024 20:47:48 +0100 Subject: [PATCH 112/247] Fix GITHUB_REF variable interpolation in ci.yml --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a406a8c92..a8a261d0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -507,7 +507,7 @@ jobs: - name: Extract branch name id: extract-branch run: | - echo "GITHUB_REF=`${GITHUB_REF}`" + echo "GITHUB_REF='${GITHUB_REF}'" echo "branch=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT" - name: Dockerhub login @@ -556,7 +556,7 @@ jobs: - name: Extract branch name id: extract-branch run: | - echo "GITHUB_REF=`${GITHUB_REF}`" + echo "GITHUB_REF='${GITHUB_REF}'" echo "branch=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT" - name: Dockerhub login From 67c3bad97790de375e696254c842cfe6f4e4ceae Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Feb 2024 06:43:41 +0100 Subject: [PATCH 113/247] Fix misspelled comment --- tests/test_freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index b3df537f8..bd0b131af 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4209,7 +4209,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( assert not trade.has_open_orders # Assuming stoploss on exchange is hit - # trade should be sold at the price of stoploss, with exit_reaeon STOPLOSS_ON_EXCHANGE + # trade should be sold at the price of stoploss, with exit_reason STOPLOSS_ON_EXCHANGE stoploss_executed = MagicMock(return_value={ "id": "123", "timestamp": 1542707426845, From ad121c19b02d65134df952291ffe9da563d03143 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Feb 2024 06:57:12 +0100 Subject: [PATCH 114/247] Allow <1m timeframes for utility modes --- freqtrade/exchange/exchange.py | 8 ++++++-- tests/exchange/test_exchange.py | 10 ++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 4b2d52a68..85a77fe5e 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -23,7 +23,7 @@ from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHAN BuySell, Config, EntryExit, ExchangeConfig, ListPairsWithTimeframes, MakerTaker, OBLiteral, PairWithTimeframe) from freqtrade.data.converter import clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_dict_to_list -from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, PriceType, TradingMode +from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, PriceType, RunMode, TradingMode from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, InvalidOrderException, OperationalException, PricingError, RetryableOrderError, TemporaryError) @@ -595,7 +595,11 @@ class Exchange: raise OperationalException( f"Invalid timeframe '{timeframe}'. This exchange supports: {self.timeframes}") - if timeframe and timeframe_to_minutes(timeframe) < 1: + if ( + timeframe + and self._config['runmode'] != RunMode.UTIL_EXCHANGE + and timeframe_to_minutes(timeframe) < 1 + ): raise OperationalException("Timeframes < 1m are currently not supported by Freqtrade.") def validate_ordertypes(self, order_types: Dict) -> None: diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index f686959fc..29e458cdd 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -9,7 +9,7 @@ import ccxt import pytest from pandas import DataFrame -from freqtrade.enums import CandleType, MarginMode, TradingMode +from freqtrade.enums import CandleType, MarginMode, RunMode, TradingMode from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError, InsufficientFundsError, InvalidOrderException, OperationalException, PricingError, TemporaryError) @@ -796,7 +796,9 @@ def test_validate_timeframes_failed(default_conf, mocker): mocker.patch(f'{EXMS}._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch(f'{EXMS}._load_markets', MagicMock(return_value={})) - mocker.patch(f'{EXMS}.validate_pairs', MagicMock()) + mocker.patch(f'{EXMS}.validate_pairs') + mocker.patch(f'{EXMS}.validate_stakecurrency') + mocker.patch(f'{EXMS}.validate_pricing') with pytest.raises(OperationalException, match=r"Invalid timeframe '3m'. This exchange supports.*"): Exchange(default_conf) @@ -806,6 +808,10 @@ def test_validate_timeframes_failed(default_conf, mocker): match=r"Timeframes < 1m are currently not supported by Freqtrade."): Exchange(default_conf) + # Will not raise an exception in util mode. + default_conf['runmode'] = RunMode.UTIL_EXCHANGE + Exchange(default_conf) + def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker): default_conf["timeframe"] = "3m" From 0d14b7a800eaecc38335e27913359f630f3e3a77 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Feb 2024 07:03:44 +0100 Subject: [PATCH 115/247] perf: only create detail timedelta object once for detail backtests --- freqtrade/optimize/backtesting.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 21e9c75cc..43aa00a65 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -185,13 +185,14 @@ class Backtesting: # Load detail timeframe if specified self.timeframe_detail = str(self.config.get('timeframe_detail', '')) if self.timeframe_detail: - self.timeframe_detail_min = timeframe_to_minutes(self.timeframe_detail) - if self.timeframe_min <= self.timeframe_detail_min: + timeframe_detail_min = timeframe_to_minutes(self.timeframe_detail) + self.timeframe_detail_td = timedelta(minutes=timeframe_detail_min) + if self.timeframe_min <= timeframe_detail_min: raise OperationalException( "Detail timeframe must be smaller than strategy timeframe.") else: - self.timeframe_detail_min = 0 + self.timeframe_detail_td = timedelta(seconds=0) self.detail_data: Dict[str, DataFrame] = {} self.futures_data: Dict[str, DataFrame] = {} @@ -1268,7 +1269,7 @@ class Backtesting: open_trade_count_start = self.backtest_loop( det_row, pair, current_time_det, end_date, open_trade_count_start, trade_dir, is_first) - current_time_det += timedelta(minutes=self.timeframe_detail_min) + current_time_det += self.timeframe_detail_td is_first = False else: self.dataprovider._set_dataframe_max_date(current_time) From 80f6fbbae9970c05cbbb3f3ce76f3debe62bc9a0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Feb 2024 07:07:58 +0100 Subject: [PATCH 116/247] chore: Move bot-related tests to test subdir --- tests/{ => freqtradebot}/test_freqtradebot.py | 0 tests/{ => freqtradebot}/test_integration.py | 0 tests/{ => freqtradebot}/test_worker.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tests/{ => freqtradebot}/test_freqtradebot.py (100%) rename tests/{ => freqtradebot}/test_integration.py (100%) rename tests/{ => freqtradebot}/test_worker.py (100%) diff --git a/tests/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py similarity index 100% rename from tests/test_freqtradebot.py rename to tests/freqtradebot/test_freqtradebot.py diff --git a/tests/test_integration.py b/tests/freqtradebot/test_integration.py similarity index 100% rename from tests/test_integration.py rename to tests/freqtradebot/test_integration.py diff --git a/tests/test_worker.py b/tests/freqtradebot/test_worker.py similarity index 100% rename from tests/test_worker.py rename to tests/freqtradebot/test_worker.py From 57df7d9ecaabd4a724b37b7868308dcd6eb3abd3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Feb 2024 07:23:19 +0100 Subject: [PATCH 117/247] chore: convert test dir to package --- tests/freqtradebot/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/freqtradebot/__init__.py diff --git a/tests/freqtradebot/__init__.py b/tests/freqtradebot/__init__.py new file mode 100644 index 000000000..e69de29bb From 8469484998333f1edbf063563c5fddf4c113acf6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Feb 2024 07:25:53 +0100 Subject: [PATCH 118/247] chore: Split stoploss tests from freqtradebot stoploss on exchange tests are quiet extensive, and deserve their own test file. --- tests/freqtradebot/test_freqtradebot.py | 1315 ---------------- .../freqtradebot/test_stoploss_on_exchange.py | 1334 +++++++++++++++++ 2 files changed, 1334 insertions(+), 1315 deletions(-) create mode 100644 tests/freqtradebot/test_stoploss_on_exchange.py diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py index bd0b131af..ca6f29078 100644 --- a/tests/freqtradebot/test_freqtradebot.py +++ b/tests/freqtradebot/test_freqtradebot.py @@ -20,7 +20,6 @@ from freqtrade.exceptions import (DependencyException, ExchangeError, Insufficie TemporaryError) from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Order, PairLocks, Trade -from freqtrade.persistence.models import PairLock from freqtrade.plugins.protections.iprotection import ProtectionReturn from freqtrade.util.datetime_helpers import dt_now, dt_utc from freqtrade.worker import Worker @@ -1090,1070 +1089,6 @@ def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order, # assert trade.stake_amount == 2 -@pytest.mark.parametrize("is_short", [False, True]) -def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short, fee) -> None: - patch_RPCManager(mocker) - patch_exchange(mocker) - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock(return_value={ - 'bid': 1.9, - 'ask': 2.2, - 'last': 1.9 - }), - create_order=MagicMock(return_value=limit_order[entry_side(is_short)]), - get_fee=fee, - ) - order = limit_order[entry_side(is_short)] - mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) - mocker.patch(f'{EXMS}.fetch_order', return_value=order) - mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) - - stoploss = MagicMock(return_value={'id': 13434334}) - mocker.patch(f'{EXMS}.create_stoploss', stoploss) - - freqtrade = FreqtradeBot(default_conf_usdt) - freqtrade.strategy.order_types['stoploss_on_exchange'] = True - - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - - freqtrade.enter_positions() - trade = Trade.session.scalars(select(Trade)).first() - trade.is_short = is_short - trade.is_open = True - trades = [trade] - - freqtrade.exit_positions(trades) - assert trade.has_open_sl_orders is True - assert stoploss.call_count == 1 - assert trade.is_open is True - - -@pytest.mark.parametrize("is_short", [False, True]) -def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_short, - limit_order) -> None: - stop_order_dict = {'id': "13434334"} - stoploss = MagicMock(return_value=stop_order_dict) - enter_order = limit_order[entry_side(is_short)] - exit_order = limit_order[exit_side(is_short)] - patch_RPCManager(mocker) - patch_exchange(mocker) - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock(return_value={ - 'bid': 1.9, - 'ask': 2.2, - 'last': 1.9 - }), - create_order=MagicMock(side_effect=[ - enter_order, - exit_order, - ]), - get_fee=fee, - create_stoploss=stoploss - ) - freqtrade = FreqtradeBot(default_conf_usdt) - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - - # First case: when stoploss is not yet set but the order is open - # should get the stoploss order id immediately - # and should return false as no trade actually happened - - freqtrade.enter_positions() - trade = Trade.session.scalars(select(Trade)).first() - assert trade.is_short == is_short - assert trade.is_open - assert trade.has_open_sl_orders is False - - assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert stoploss.call_count == 1 - assert trade.open_sl_orders[-1].order_id == "13434334" - - # Second case: when stoploss is set but it is not yet hit - # should do nothing and return false - trade.is_open = True - - hanging_stoploss_order = MagicMock(return_value={'id': '13434334', 'status': 'open'}) - mocker.patch(f'{EXMS}.fetch_stoploss_order', hanging_stoploss_order) - - assert freqtrade.handle_stoploss_on_exchange(trade) is False - hanging_stoploss_order.assert_called_once_with('13434334', trade.pair) - assert len(trade.open_sl_orders) == 1 - assert trade.open_sl_orders[-1].order_id == "13434334" - - # Third case: when stoploss was set but it was canceled for some reason - # should set a stoploss immediately and return False - caplog.clear() - trade.is_open = True - - canceled_stoploss_order = MagicMock(return_value={'id': '13434334', 'status': 'canceled'}) - mocker.patch(f'{EXMS}.fetch_stoploss_order', canceled_stoploss_order) - stoploss.reset_mock() - amount_before = trade.amount - - stop_order_dict.update({'id': "103_1"}) - - assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert stoploss.call_count == 1 - assert len(trade.open_sl_orders) == 1 - assert trade.open_sl_orders[-1].order_id == "103_1" - assert trade.amount == amount_before - - # Fourth case: when stoploss is set and it is hit - # should return true as a trade actually happened - caplog.clear() - stop_order_dict.update({'id': "103_1"}) - - trade = Trade.session.scalars(select(Trade)).first() - trade.is_short = is_short - trade.is_open = True - - stoploss_order_hit = MagicMock(return_value={ - 'id': "103_1", - 'status': 'closed', - 'type': 'stop_loss_limit', - 'price': 3, - 'average': 2, - 'filled': enter_order['amount'], - 'remaining': 0, - 'amount': enter_order['amount'], - }) - mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit) - assert freqtrade.handle_stoploss_on_exchange(trade) is True - assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog) - assert len(trade.open_sl_orders) == 0 - assert trade.is_open is False - caplog.clear() - - mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError()) - trade.is_open = True - freqtrade.handle_stoploss_on_exchange(trade) - assert log_has('Unable to place a stoploss order on exchange.', caplog) - assert len(trade.open_sl_orders) == 0 - - # Fifth case: fetch_order returns InvalidOrder - # It should try to add stoploss order - stop_order_dict.update({'id': "105"}) - stoploss.reset_mock() - mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=InvalidOrderException()) - mocker.patch(f'{EXMS}.create_stoploss', stoploss) - freqtrade.handle_stoploss_on_exchange(trade) - assert len(trade.open_sl_orders) == 1 - assert stoploss.call_count == 1 - - # Sixth case: Closed Trade - # Should not create new order - trade.is_open = False - trade.open_sl_orders[-1].ft_is_open = False - stoploss.reset_mock() - mocker.patch(f'{EXMS}.fetch_order') - mocker.patch(f'{EXMS}.create_stoploss', stoploss) - assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert trade.has_open_sl_orders is False - assert stoploss.call_count == 0 - - -@pytest.mark.parametrize("is_short", [False, True]) -def test_handle_stoploss_on_exchange_emergency(mocker, default_conf_usdt, fee, is_short, - limit_order) -> None: - stop_order_dict = {'id': "13434334"} - stoploss = MagicMock(return_value=stop_order_dict) - enter_order = limit_order[entry_side(is_short)] - exit_order = limit_order[exit_side(is_short)] - patch_RPCManager(mocker) - patch_exchange(mocker) - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock(return_value={ - 'bid': 1.9, - 'ask': 2.2, - 'last': 1.9 - }), - create_order=MagicMock(side_effect=[ - enter_order, - exit_order, - ]), - get_fee=fee, - create_stoploss=stoploss - ) - freqtrade = FreqtradeBot(default_conf_usdt) - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - - freqtrade.enter_positions() - trade = Trade.session.scalars(select(Trade)).first() - assert trade.is_short == is_short - assert trade.is_open - assert trade.has_open_sl_orders is False - - # emergency exit triggered - # Trailing stop should not act anymore - stoploss_order_cancelled = MagicMock(side_effect=[{ - 'id': "107", - 'status': 'canceled', - 'type': 'stop_loss_limit', - 'price': 3, - 'average': 2, - 'amount': enter_order['amount'], - 'filled': 0, - 'remaining': enter_order['amount'], - 'info': {'stopPrice': 22}, - }]) - trade.stoploss_last_update = dt_now() - timedelta(hours=1) - trade.stop_loss = 24 - trade.exit_reason = None - trade.orders.append( - Order( - ft_order_side='stoploss', - ft_pair=trade.pair, - ft_is_open=True, - ft_amount=trade.amount, - ft_price=trade.stop_loss, - order_id='107', - status='open', - ) - ) - freqtrade.config['trailing_stop'] = True - stoploss = MagicMock(side_effect=InvalidOrderException()) - assert trade.has_open_sl_orders is True - Trade.commit() - mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', - side_effect=InvalidOrderException()) - mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_cancelled) - mocker.patch(f'{EXMS}.create_stoploss', stoploss) - assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert trade.has_open_sl_orders is False - assert trade.is_open is False - assert trade.exit_reason == str(ExitType.EMERGENCY_EXIT) - - -@pytest.mark.parametrize("is_short", [False, True]) -def test_handle_stoploss_on_exchange_partial( - mocker, default_conf_usdt, fee, is_short, limit_order) -> None: - stop_order_dict = {'id': "101", "status": "open"} - stoploss = MagicMock(return_value=stop_order_dict) - enter_order = limit_order[entry_side(is_short)] - exit_order = limit_order[exit_side(is_short)] - patch_RPCManager(mocker) - patch_exchange(mocker) - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock(return_value={ - 'bid': 1.9, - 'ask': 2.2, - 'last': 1.9 - }), - create_order=MagicMock(side_effect=[ - enter_order, - exit_order, - ]), - get_fee=fee, - create_stoploss=stoploss - ) - freqtrade = FreqtradeBot(default_conf_usdt) - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - - freqtrade.enter_positions() - trade = Trade.session.scalars(select(Trade)).first() - trade.is_short = is_short - trade.is_open = True - - assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert stoploss.call_count == 1 - assert trade.has_open_sl_orders is True - assert trade.open_sl_orders[-1].order_id == "101" - assert trade.amount == 30 - stop_order_dict.update({'id': "102"}) - # Stoploss on exchange is cancelled on exchange, but filled partially. - # Must update trade amount to guarantee successful exit. - stoploss_order_hit = MagicMock(return_value={ - 'id': "101", - 'status': 'canceled', - 'type': 'stop_loss_limit', - 'price': 3, - 'average': 2, - 'filled': trade.amount / 2, - 'remaining': trade.amount / 2, - 'amount': enter_order['amount'], - }) - mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit) - assert freqtrade.handle_stoploss_on_exchange(trade) is False - # Stoploss filled partially ... - assert trade.amount == 15 - - assert trade.open_sl_orders[-1].order_id == "102" - - -@pytest.mark.parametrize("is_short", [False, True]) -def test_handle_stoploss_on_exchange_partial_cancel_here( - mocker, default_conf_usdt, fee, is_short, limit_order, caplog, time_machine) -> None: - stop_order_dict = {'id': "101", "status": "open"} - time_machine.move_to(dt_now()) - default_conf_usdt['trailing_stop'] = True - stoploss = MagicMock(return_value=stop_order_dict) - enter_order = limit_order[entry_side(is_short)] - exit_order = limit_order[exit_side(is_short)] - patch_RPCManager(mocker) - patch_exchange(mocker) - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock(return_value={ - 'bid': 1.9, - 'ask': 2.2, - 'last': 1.9 - }), - create_order=MagicMock(side_effect=[ - enter_order, - exit_order, - ]), - get_fee=fee, - create_stoploss=stoploss - ) - freqtrade = FreqtradeBot(default_conf_usdt) - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - - freqtrade.enter_positions() - trade = Trade.session.scalars(select(Trade)).first() - trade.is_short = is_short - trade.is_open = True - - assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert stoploss.call_count == 1 - assert trade.has_open_sl_orders is True - assert trade.open_sl_orders[-1].order_id == "101" - assert trade.amount == 30 - stop_order_dict.update({'id': "102"}) - # Stoploss on exchange is open. - # Freqtrade cancels the stop - but cancel returns a partial filled order. - stoploss_order_hit = MagicMock(return_value={ - 'id': "101", - 'status': 'open', - 'type': 'stop_loss_limit', - 'price': 3, - 'average': 2, - 'filled': 0, - 'remaining': trade.amount, - 'amount': enter_order['amount'], - }) - stoploss_order_cancel = MagicMock(return_value={ - 'id': "101", - 'status': 'canceled', - 'type': 'stop_loss_limit', - 'price': 3, - 'average': 2, - 'filled': trade.amount / 2, - 'remaining': trade.amount / 2, - 'amount': enter_order['amount'], - }) - mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit) - mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', stoploss_order_cancel) - time_machine.shift(timedelta(minutes=15)) - - assert freqtrade.handle_stoploss_on_exchange(trade) is False - # Canceled Stoploss filled partially ... - assert log_has_re('Cancelling current stoploss on exchange.*', caplog) - - assert trade.has_open_sl_orders is True - assert trade.open_sl_orders[-1].order_id == "102" - assert trade.amount == 15 - - -@pytest.mark.parametrize("is_short", [False, True]) -def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, is_short, - limit_order) -> None: - # Sixth case: stoploss order was cancelled but couldn't create new one - enter_order = limit_order[entry_side(is_short)] - exit_order = limit_order[exit_side(is_short)] - patch_RPCManager(mocker) - patch_exchange(mocker) - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock(return_value={ - 'bid': 1.9, - 'ask': 2.2, - 'last': 1.9 - }), - create_order=MagicMock(side_effect=[ - enter_order, - exit_order, - ]), - get_fee=fee, - ) - mocker.patch.multiple( - EXMS, - fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': '100'}), - create_stoploss=MagicMock(side_effect=ExchangeError()), - ) - freqtrade = FreqtradeBot(default_conf_usdt) - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - - freqtrade.enter_positions() - trade = Trade.session.scalars(select(Trade)).first() - assert trade.is_short == is_short - trade.is_open = True - trade.orders.append( - Order( - ft_order_side='stoploss', - ft_pair=trade.pair, - ft_is_open=True, - ft_amount=trade.amount, - ft_price=trade.stop_loss, - order_id='100', - status='open', - ) - ) - assert trade - - assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert log_has_re(r'All Stoploss orders are cancelled, but unable to recreate one\.', caplog) - assert trade.has_open_sl_orders is False - assert trade.is_open is True - - -@pytest.mark.parametrize("is_short", [False, True]) -def test_create_stoploss_order_invalid_order( - mocker, default_conf_usdt, caplog, fee, is_short, limit_order -): - open_order = limit_order[entry_side(is_short)] - order = limit_order[exit_side(is_short)] - rpc_mock = patch_RPCManager(mocker) - patch_exchange(mocker) - create_order_mock = MagicMock(side_effect=[ - open_order, - order, - ]) - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock(return_value={ - 'bid': 1.9, - 'ask': 2.2, - 'last': 1.9 - }), - create_order=create_order_mock, - get_fee=fee, - ) - mocker.patch.multiple( - EXMS, - fetch_order=MagicMock(return_value={'status': 'canceled'}), - create_stoploss=MagicMock(side_effect=InvalidOrderException()), - ) - freqtrade = FreqtradeBot(default_conf_usdt) - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - freqtrade.strategy.order_types['stoploss_on_exchange'] = True - - freqtrade.enter_positions() - trade = Trade.session.scalars(select(Trade)).first() - trade.is_short = is_short - caplog.clear() - rpc_mock.reset_mock() - freqtrade.create_stoploss_order(trade, 200) - assert trade.has_open_sl_orders is False - assert trade.exit_reason == ExitType.EMERGENCY_EXIT.value - assert log_has("Unable to place a stoploss order on exchange. ", caplog) - assert log_has("Exiting the trade forcefully", caplog) - - # Should call a market sell - assert create_order_mock.call_count == 2 - assert create_order_mock.call_args[1]['ordertype'] == 'market' - assert create_order_mock.call_args[1]['pair'] == trade.pair - assert create_order_mock.call_args[1]['amount'] == trade.amount - - # Rpc is sending first buy, then sell - assert rpc_mock.call_count == 2 - assert rpc_mock.call_args_list[0][0][0]['exit_reason'] == ExitType.EMERGENCY_EXIT.value - assert rpc_mock.call_args_list[0][0][0]['order_type'] == 'market' - assert rpc_mock.call_args_list[0][0][0]['type'] == 'exit' - assert rpc_mock.call_args_list[1][0][0]['type'] == 'exit_fill' - - -@pytest.mark.parametrize("is_short", [False, True]) -def test_create_stoploss_order_insufficient_funds( - mocker, default_conf_usdt, caplog, fee, limit_order, is_short -): - exit_order = limit_order[exit_side(is_short)]['id'] - freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - - mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds') - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock(return_value={ - 'bid': 1.9, - 'ask': 2.2, - 'last': 1.9 - }), - create_order=MagicMock(side_effect=[ - limit_order[entry_side(is_short)], - exit_order, - ]), - get_fee=fee, - fetch_order=MagicMock(return_value={'status': 'canceled'}), - ) - mocker.patch.multiple( - EXMS, - create_stoploss=MagicMock(side_effect=InsufficientFundsError()), - ) - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - freqtrade.strategy.order_types['stoploss_on_exchange'] = True - - freqtrade.enter_positions() - trade = Trade.session.scalars(select(Trade)).first() - trade.is_short = is_short - caplog.clear() - freqtrade.create_stoploss_order(trade, 200) - # stoploss_orderid was empty before - assert trade.has_open_sl_orders is False - assert mock_insuf.call_count == 1 - mock_insuf.reset_mock() - - freqtrade.create_stoploss_order(trade, 200) - # No change to stoploss-orderid - assert trade.has_open_sl_orders is False - assert mock_insuf.call_count == 1 - - -@pytest.mark.parametrize("is_short,bid,ask,stop_price,hang_price", [ - (False, [4.38, 4.16], [4.4, 4.17], ['2.0805', 4.4 * 0.95], 3), - (True, [1.09, 1.21], [1.1, 1.22], ['2.321', 1.09 * 1.05], 1.5), -]) -@pytest.mark.usefixtures("init_persistence") -def test_handle_stoploss_on_exchange_trailing( - mocker, default_conf_usdt, fee, is_short, bid, ask, limit_order, stop_price, hang_price, - time_machine, -) -> None: - # When trailing stoploss is set - enter_order = limit_order[entry_side(is_short)] - exit_order = limit_order[exit_side(is_short)] - stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'}) - start_dt = dt_now() - time_machine.move_to(start_dt, tick=False) - patch_RPCManager(mocker) - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock(return_value={ - 'bid': 2.19, - 'ask': 2.2, - 'last': 2.19, - }), - create_order=MagicMock(side_effect=[ - enter_order, - exit_order, - ]), - get_fee=fee, - ) - mocker.patch.multiple( - EXMS, - create_stoploss=stoploss, - stoploss_adjust=MagicMock(return_value=True), - ) - - # enabling TSL - default_conf_usdt['trailing_stop'] = True - - # disabling ROI - default_conf_usdt['minimal_roi']['0'] = 999999999 - - freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - - # enabling stoploss on exchange - freqtrade.strategy.order_types['stoploss_on_exchange'] = True - - # setting stoploss - freqtrade.strategy.stoploss = 0.05 if is_short else -0.05 - - # setting stoploss_on_exchange_interval to 60 seconds - freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 - - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - - freqtrade.enter_positions() - trade = Trade.session.scalars(select(Trade)).first() - trade.is_short = is_short - trade.is_open = True - assert trade.has_open_sl_orders is False - trade.stoploss_last_update = dt_now() - timedelta(minutes=20) - trade.orders.append( - Order( - ft_order_side='stoploss', - ft_pair=trade.pair, - ft_is_open=True, - ft_amount=trade.amount, - ft_price=trade.stop_loss, - order_id='100', - order_date=dt_now() - timedelta(minutes=20), - ) - ) - - stoploss_order_hanging = { - 'id': '100', - 'status': 'open', - 'type': 'stop_loss_limit', - 'price': hang_price, - 'average': 2, - 'fee': {}, - 'amount': 0, - 'info': { - 'stopPrice': stop_price[0] - } - } - stoploss_order_cancel = deepcopy(stoploss_order_hanging) - stoploss_order_cancel['status'] = 'canceled' - - mocker.patch(f'{EXMS}.fetch_stoploss_order', return_value=stoploss_order_hanging) - mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value=stoploss_order_cancel) - - # stoploss initially at 5% - assert freqtrade.handle_trade(trade) is False - assert freqtrade.handle_stoploss_on_exchange(trade) is False - - assert len(trade.open_sl_orders) == 1 - - assert trade.open_sl_orders[-1].order_id == '13434334' - - # price jumped 2x - mocker.patch( - f'{EXMS}.fetch_ticker', - MagicMock(return_value={ - 'bid': bid[0], - 'ask': ask[0], - 'last': bid[0], - }) - ) - - cancel_order_mock = MagicMock(return_value={ - 'id': '13434334', 'status': 'canceled', 'fee': {}, 'amount': trade.amount}) - stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'}) - mocker.patch(f'{EXMS}.fetch_stoploss_order') - mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) - mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock) - - # stoploss should not be updated as the interval is 60 seconds - assert freqtrade.handle_trade(trade) is False - assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert len(trade.open_sl_orders) == 1 - cancel_order_mock.assert_not_called() - stoploss_order_mock.assert_not_called() - - # Move time by 10s ... so stoploss order should be replaced. - time_machine.move_to(start_dt + timedelta(minutes=10), tick=False) - - assert freqtrade.handle_trade(trade) is False - assert trade.stop_loss == stop_price[1] - - assert freqtrade.handle_stoploss_on_exchange(trade) is False - - cancel_order_mock.assert_called_once_with('13434334', 'ETH/USDT') - stoploss_order_mock.assert_called_once_with( - amount=30, - pair='ETH/USDT', - order_types=freqtrade.strategy.order_types, - stop_price=stop_price[1], - side=exit_side(is_short), - leverage=1.0 - ) - - # price fell below stoploss, so dry-run sells trade. - mocker.patch( - f'{EXMS}.fetch_ticker', - MagicMock(return_value={ - 'bid': bid[1], - 'ask': ask[1], - 'last': bid[1], - }) - ) - mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', - return_value={'id': 'so1', 'status': 'canceled'}) - assert len(trade.open_sl_orders) == 1 - assert trade.open_sl_orders[-1].order_id == 'so1' - - assert freqtrade.handle_trade(trade) is True - assert trade.is_open is False - assert trade.has_open_sl_orders is False - - -@pytest.mark.parametrize("is_short", [False, True]) -def test_handle_stoploss_on_exchange_trailing_error( - mocker, default_conf_usdt, fee, caplog, limit_order, is_short, time_machine -) -> None: - time_machine.move_to(dt_now() - timedelta(minutes=601)) - enter_order = limit_order[entry_side(is_short)] - exit_order = limit_order[exit_side(is_short)] - # When trailing stoploss is set - stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'}) - patch_exchange(mocker) - - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock(return_value={ - 'bid': 1.9, - 'ask': 2.2, - 'last': 1.9 - }), - create_order=MagicMock(side_effect=[ - {'id': enter_order['id']}, - {'id': exit_order['id']}, - ]), - get_fee=fee, - create_stoploss=stoploss, - stoploss_adjust=MagicMock(return_value=True), - ) - - # enabling TSL - default_conf_usdt['trailing_stop'] = True - - freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - # enabling stoploss on exchange - freqtrade.strategy.order_types['stoploss_on_exchange'] = True - - # setting stoploss - freqtrade.strategy.stoploss = 0.05 if is_short else -0.05 - - # setting stoploss_on_exchange_interval to 60 seconds - freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - freqtrade.enter_positions() - trade = Trade.session.scalars(select(Trade)).first() - trade.is_short = is_short - trade.is_open = True - trade.stop_loss = 0.2 - - stoploss_order_hanging = { - 'id': "abcd", - 'status': 'open', - 'type': 'stop_loss_limit', - 'price': 3, - 'average': 2, - 'info': { - 'stopPrice': '0.1' - } - } - trade.orders.append( - Order( - ft_order_side='stoploss', - ft_pair=trade.pair, - ft_is_open=True, - ft_amount=trade.amount, - ft_price=3, - order_id='abcd', - order_date=dt_now(), - ) - ) - mocker.patch(f'{EXMS}.cancel_stoploss_order', - side_effect=InvalidOrderException()) - mocker.patch(f'{EXMS}.fetch_stoploss_order', - return_value=stoploss_order_hanging) - time_machine.shift(timedelta(minutes=50)) - freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) - assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog) - - # Still try to create order - assert stoploss.call_count == 1 - # TODO: Is this actually correct ? This will create a new order every time, - assert len(trade.open_sl_orders) == 2 - - # Fail creating stoploss order - caplog.clear() - cancel_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order') - mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError()) - time_machine.shift(timedelta(minutes=50)) - freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) - assert cancel_mock.call_count == 2 - assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog) - - -def test_stoploss_on_exchange_price_rounding( - mocker, default_conf_usdt, fee, open_trade_usdt) -> None: - patch_RPCManager(mocker) - mocker.patch.multiple( - EXMS, - get_fee=fee, - ) - price_mock = MagicMock(side_effect=lambda p, s, **kwargs: int(s)) - stoploss_mock = MagicMock(return_value={'id': '13434334'}) - adjust_mock = MagicMock(return_value=False) - mocker.patch.multiple( - EXMS, - create_stoploss=stoploss_mock, - stoploss_adjust=adjust_mock, - price_to_precision=price_mock, - ) - freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - open_trade_usdt.stop_loss = 222.55 - - freqtrade.handle_trailing_stoploss_on_exchange(open_trade_usdt, {}) - assert price_mock.call_count == 1 - assert adjust_mock.call_count == 1 - assert adjust_mock.call_args_list[0][0][0] == 222 - - -@pytest.mark.parametrize("is_short", [False, True]) -@pytest.mark.usefixtures("init_persistence") -def test_handle_stoploss_on_exchange_custom_stop( - mocker, default_conf_usdt, fee, is_short, limit_order -) -> None: - enter_order = limit_order[entry_side(is_short)] - exit_order = limit_order[exit_side(is_short)] - # When trailing stoploss is set - stoploss = MagicMock(return_value={'id': 13434334, 'status': 'open'}) - patch_RPCManager(mocker) - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock(return_value={ - 'bid': 1.9, - 'ask': 2.2, - 'last': 1.9 - }), - create_order=MagicMock(side_effect=[ - enter_order, - exit_order, - ]), - get_fee=fee, - is_cancel_order_result_suitable=MagicMock(return_value=True), - ) - mocker.patch.multiple( - EXMS, - create_stoploss=stoploss, - stoploss_adjust=MagicMock(return_value=True), - ) - - # enabling TSL - default_conf_usdt['use_custom_stoploss'] = True - - # disabling ROI - default_conf_usdt['minimal_roi']['0'] = 999999999 - - freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - - # enabling stoploss on exchange - freqtrade.strategy.order_types['stoploss_on_exchange'] = True - - # setting stoploss - freqtrade.strategy.custom_stoploss = lambda *args, **kwargs: -0.04 - - # setting stoploss_on_exchange_interval to 60 seconds - freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 - - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - - freqtrade.enter_positions() - trade = Trade.session.scalars(select(Trade)).first() - trade.is_short = is_short - trade.is_open = True - trade.orders.append( - Order( - ft_order_side='stoploss', - ft_pair=trade.pair, - ft_is_open=True, - ft_amount=trade.amount, - ft_price=trade.stop_loss, - order_date=dt_now() - timedelta(minutes=601), - order_id='100', - ) - ) - Trade.commit() - slo = { - 'id': '100', - 'status': 'open', - 'type': 'stop_loss_limit', - 'price': 3, - 'average': 2, - 'info': { - 'stopPrice': '2.0805' - } - } - slo_canceled = deepcopy(slo) - slo_canceled.update({'status': 'canceled'}) - - def fetch_stoploss_order_mock(order_id, *args, **kwargs): - x = deepcopy(slo) - x['id'] = order_id - return x - - mocker.patch(f'{EXMS}.fetch_stoploss_order', MagicMock(fetch_stoploss_order_mock)) - mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value=slo_canceled) - - assert freqtrade.handle_trade(trade) is False - assert freqtrade.handle_stoploss_on_exchange(trade) is False - - # price jumped 2x - mocker.patch( - f'{EXMS}.fetch_ticker', - MagicMock(return_value={ - 'bid': 4.38 if not is_short else 1.9 / 2, - 'ask': 4.4 if not is_short else 2.2 / 2, - 'last': 4.38 if not is_short else 1.9 / 2, - }) - ) - - cancel_order_mock = MagicMock() - stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'}) - mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) - mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock) - - # stoploss should not be updated as the interval is 60 seconds - assert freqtrade.handle_trade(trade) is False - assert freqtrade.handle_stoploss_on_exchange(trade) is False - cancel_order_mock.assert_not_called() - stoploss_order_mock.assert_not_called() - - assert freqtrade.handle_trade(trade) is False - assert trade.stop_loss == 4.4 * 0.96 if not is_short else 1.1 - assert trade.stop_loss_pct == -0.04 if not is_short else 0.04 - - # setting stoploss_on_exchange_interval to 0 seconds - freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 - cancel_order_mock.assert_not_called() - stoploss_order_mock.assert_not_called() - - assert freqtrade.handle_stoploss_on_exchange(trade) is False - - cancel_order_mock.assert_called_once_with('13434334', 'ETH/USDT') - # Long uses modified ask - offset, short modified bid + offset - stoploss_order_mock.assert_called_once_with( - amount=pytest.approx(trade.amount), - pair='ETH/USDT', - order_types=freqtrade.strategy.order_types, - stop_price=4.4 * 0.96 if not is_short else 0.95 * 1.04, - side=exit_side(is_short), - leverage=1.0 - ) - - # price fell below stoploss, so dry-run sells trade. - mocker.patch( - f'{EXMS}.fetch_ticker', - MagicMock(return_value={ - 'bid': 4.17, - 'ask': 4.19, - 'last': 4.17 - }) - ) - assert freqtrade.handle_trade(trade) is True - - -def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_order) -> None: - - enter_order = limit_order['buy'] - exit_order = limit_order['sell'] - enter_order['average'] = 2.19 - # When trailing stoploss is set - stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'}) - patch_RPCManager(mocker) - patch_exchange(mocker) - patch_edge(mocker) - edge_conf['max_open_trades'] = float('inf') - edge_conf['dry_run_wallet'] = 999.9 - edge_conf['exchange']['name'] = 'binance' - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock(return_value={ - 'bid': 2.19, - 'ask': 2.2, - 'last': 2.19 - }), - create_order=MagicMock(side_effect=[ - enter_order, - exit_order, - ]), - get_fee=fee, - create_stoploss=stoploss, - ) - - # enabling TSL - edge_conf['trailing_stop'] = True - edge_conf['trailing_stop_positive'] = 0.01 - edge_conf['trailing_stop_positive_offset'] = 0.011 - - # disabling ROI - edge_conf['minimal_roi']['0'] = 999999999 - - freqtrade = FreqtradeBot(edge_conf) - - # enabling stoploss on exchange - freqtrade.strategy.order_types['stoploss_on_exchange'] = True - - # setting stoploss - freqtrade.strategy.stoploss = -0.02 - - # setting stoploss_on_exchange_interval to 0 seconds - freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 - - patch_get_signal(freqtrade) - - freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist) - - freqtrade.enter_positions() - trade = Trade.session.scalars(select(Trade)).first() - trade.is_open = True - - trade.stoploss_last_update = dt_now() - trade.orders.append( - Order( - ft_order_side='stoploss', - ft_pair=trade.pair, - ft_is_open=True, - ft_amount=trade.amount, - ft_price=trade.stop_loss, - order_id='100', - ) - ) - - stoploss_order_hanging = MagicMock(return_value={ - 'id': '100', - 'status': 'open', - 'type': 'stop_loss_limit', - 'price': 3, - 'average': 2, - 'stopPrice': '2.178' - }) - - mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hanging) - - # stoploss initially at 20% as edge dictated it. - assert freqtrade.handle_trade(trade) is False - assert freqtrade.handle_stoploss_on_exchange(trade) is False - assert pytest.approx(trade.stop_loss) == 1.76 - - cancel_order_mock = MagicMock() - stoploss_order_mock = MagicMock() - mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) - mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock) - - # price goes down 5% - mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ - 'bid': 2.19 * 0.95, - 'ask': 2.2 * 0.95, - 'last': 2.19 * 0.95 - })) - assert freqtrade.handle_trade(trade) is False - assert freqtrade.handle_stoploss_on_exchange(trade) is False - - # stoploss should remain the same - assert pytest.approx(trade.stop_loss) == 1.76 - - # stoploss on exchange should not be canceled - cancel_order_mock.assert_not_called() - - # price jumped 2x - mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ - 'bid': 4.38, - 'ask': 4.4, - 'last': 4.38 - })) - - assert freqtrade.handle_trade(trade) is False - assert freqtrade.handle_stoploss_on_exchange(trade) is False - - # stoploss should be set to 1% as trailing is on - assert trade.stop_loss == 4.4 * 0.99 - cancel_order_mock.assert_called_once_with('100', 'NEO/BTC') - stoploss_order_mock.assert_called_once_with( - amount=30, - pair='NEO/BTC', - order_types=freqtrade.strategy.order_types, - stop_price=4.4 * 0.99, - side='sell', - leverage=1.0 - ) - - @pytest.mark.parametrize('return_value,side_effect,log_message', [ (False, None, 'Found no enter signals for whitelisted currencies. Trying again...'), (None, DependencyException, 'Unable to create trade for ETH/USDT: ') @@ -3988,257 +2923,7 @@ def test_execute_trade_exit_custom_exit_price( } == last_msg -@pytest.mark.parametrize("is_short", [False, True]) -def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( - default_conf_usdt, ticker_usdt, fee, is_short, ticker_usdt_sell_down, - ticker_usdt_sell_up, mocker) -> None: - rpc_mock = patch_RPCManager(mocker) - patch_exchange(mocker) - mocker.patch.multiple( - EXMS, - fetch_ticker=ticker_usdt, - get_fee=fee, - _dry_is_price_crossed=MagicMock(return_value=False), - ) - patch_whitelist(mocker, default_conf_usdt) - freqtrade = FreqtradeBot(default_conf_usdt) - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - # Create some test data - freqtrade.enter_positions() - - trade = Trade.session.scalars(select(Trade)).first() - assert trade.is_short == is_short - assert trade - - # Decrease the price and sell it - mocker.patch.multiple( - EXMS, - fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down - ) - - default_conf_usdt['dry_run'] = True - freqtrade.strategy.order_types['stoploss_on_exchange'] = True - # Setting trade stoploss to 0.01 - - trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99 - freqtrade.execute_trade_exit( - trade=trade, limit=trade.stop_loss, - exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)) - - assert rpc_mock.call_count == 2 - last_msg = rpc_mock.call_args_list[-1][0][0] - - assert { - 'type': RPCMessageType.EXIT, - 'trade_id': 1, - 'exchange': 'Binance', - 'pair': 'ETH/USDT', - 'direction': 'Short' if trade.is_short else 'Long', - 'leverage': 1.0, - 'gain': 'loss', - 'limit': 2.02 if is_short else 1.98, - 'order_rate': 2.02 if is_short else 1.98, - 'amount': pytest.approx(29.70297029 if is_short else 30.0), - 'order_type': 'limit', - 'buy_tag': None, - 'enter_tag': None, - 'open_rate': 2.02 if is_short else 2.0, - 'current_rate': 2.2 if is_short else 2.0, - 'profit_amount': -0.3 if is_short else -0.8985, - 'profit_ratio': -0.00501253 if is_short else -0.01493766, - 'stake_currency': 'USDT', - 'quote_currency': 'USDT', - 'fiat_currency': 'USD', - 'base_currency': 'ETH', - 'exit_reason': ExitType.STOP_LOSS.value, - 'open_date': ANY, - 'close_date': ANY, - 'close_rate': ANY, - 'sub_trade': False, - 'cumulative_profit': 0.0, - 'stake_amount': pytest.approx(60), - 'is_final_exit': False, - 'final_profit_ratio': None, - } == last_msg - - -def test_execute_trade_exit_sloe_cancel_exception( - mocker, default_conf_usdt, ticker_usdt, fee, caplog) -> None: - freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=InvalidOrderException()) - mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=300)) - create_order_mock = MagicMock(side_effect=[ - {'id': '12345554'}, - {'id': '12345555'}, - ]) - patch_exchange(mocker) - mocker.patch.multiple( - EXMS, - fetch_ticker=ticker_usdt, - get_fee=fee, - create_order=create_order_mock, - ) - - freqtrade.strategy.order_types['stoploss_on_exchange'] = True - patch_get_signal(freqtrade) - freqtrade.enter_positions() - - trade = Trade.session.scalars(select(Trade)).first() - PairLock.session = MagicMock() - - freqtrade.config['dry_run'] = False - trade.orders.append( - Order( - ft_order_side='stoploss', - ft_pair=trade.pair, - ft_is_open=True, - ft_amount=trade.amount, - ft_price=trade.stop_loss, - order_id='abcd', - status='open', - ) - ) - - freqtrade.execute_trade_exit(trade=trade, limit=1234, - exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)) - assert create_order_mock.call_count == 2 - assert log_has('Could not cancel stoploss order abcd for pair ETH/USDT', caplog) - - -@pytest.mark.parametrize("is_short", [False, True]) -def test_execute_trade_exit_with_stoploss_on_exchange( - default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, mocker) -> None: - - default_conf_usdt['exchange']['name'] = 'binance' - rpc_mock = patch_RPCManager(mocker) - patch_exchange(mocker) - stoploss = MagicMock(return_value={ - 'id': 123, - 'status': 'open', - 'info': { - 'foo': 'bar' - } - }) - mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_order_fee') - - cancel_order = MagicMock(return_value=True) - mocker.patch.multiple( - EXMS, - fetch_ticker=ticker_usdt, - get_fee=fee, - amount_to_precision=lambda s, x, y: y, - price_to_precision=lambda s, x, y: y, - create_stoploss=stoploss, - cancel_stoploss_order=cancel_order, - _dry_is_price_crossed=MagicMock(side_effect=[True, False]), - ) - - freqtrade = FreqtradeBot(default_conf_usdt) - freqtrade.strategy.order_types['stoploss_on_exchange'] = True - patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - - # Create some test data - freqtrade.enter_positions() - - trade = Trade.session.scalars(select(Trade)).first() - trade.is_short = is_short - assert trade - trades = [trade] - - freqtrade.manage_open_orders() - freqtrade.exit_positions(trades) - - # Increase the price and sell it - mocker.patch.multiple( - EXMS, - fetch_ticker=ticker_usdt_sell_up - ) - - freqtrade.execute_trade_exit( - trade=trade, - limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'], - exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS) - ) - - trade = Trade.session.scalars(select(Trade)).first() - trade.is_short = is_short - assert trade - assert cancel_order.call_count == 1 - assert rpc_mock.call_count == 4 - - -@pytest.mark.parametrize("is_short", [False, True]) -def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( - default_conf_usdt, ticker_usdt, fee, mocker, is_short) -> None: - default_conf_usdt['exchange']['name'] = 'binance' - rpc_mock = patch_RPCManager(mocker) - patch_exchange(mocker) - mocker.patch.multiple( - EXMS, - fetch_ticker=ticker_usdt, - get_fee=fee, - amount_to_precision=lambda s, x, y: y, - price_to_precision=lambda s, x, y: y, - _dry_is_price_crossed=MagicMock(side_effect=[False, True]), - ) - - stoploss = MagicMock(return_value={ - 'id': 123, - 'info': { - 'foo': 'bar' - } - }) - - mocker.patch(f'{EXMS}.create_stoploss', stoploss) - - freqtrade = FreqtradeBot(default_conf_usdt) - freqtrade.strategy.order_types['stoploss_on_exchange'] = True - patch_get_signal(freqtrade, enter_long=not is_short, enter_short=is_short) - - # Create some test data - freqtrade.enter_positions() - freqtrade.manage_open_orders() - trade = Trade.session.scalars(select(Trade)).first() - trades = [trade] - assert trade.has_open_sl_orders is False - - freqtrade.exit_positions(trades) - assert trade - assert trade.has_open_sl_orders is True - assert not trade.has_open_orders - - # Assuming stoploss on exchange is hit - # trade should be sold at the price of stoploss, with exit_reason STOPLOSS_ON_EXCHANGE - stoploss_executed = MagicMock(return_value={ - "id": "123", - "timestamp": 1542707426845, - "datetime": "2018-11-20T09:50:26.845Z", - "lastTradeTimestamp": None, - "symbol": "BTC/USDT", - "type": "stop_loss_limit", - "side": "buy" if is_short else "sell", - "price": 1.08801, - "amount": trade.amount, - "cost": 1.08801 * trade.amount, - "average": 1.08801, - "filled": trade.amount, - "remaining": 0.0, - "status": "closed", - "fee": None, - "trades": None - }) - mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_executed) - - freqtrade.exit_positions(trades) - assert trade.has_open_sl_orders is False - assert trade.is_open is False - assert trade.exit_reason == ExitType.STOPLOSS_ON_EXCHANGE.value - assert rpc_mock.call_count == 4 - assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.ENTRY - assert rpc_mock.call_args_list[1][0][0]['amount'] > 20 - assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.ENTRY_FILL - assert rpc_mock.call_args_list[3][0][0]['type'] == RPCMessageType.EXIT_FILL @pytest.mark.parametrize( diff --git a/tests/freqtradebot/test_stoploss_on_exchange.py b/tests/freqtradebot/test_stoploss_on_exchange.py new file mode 100644 index 000000000..325fe549f --- /dev/null +++ b/tests/freqtradebot/test_stoploss_on_exchange.py @@ -0,0 +1,1334 @@ +from copy import deepcopy +from datetime import timedelta +from unittest.mock import ANY, MagicMock + +import pytest +from sqlalchemy import select + +from freqtrade.enums import ExitCheckTuple, ExitType, RPCMessageType +from freqtrade.exceptions import ExchangeError, InsufficientFundsError, InvalidOrderException +from freqtrade.freqtradebot import FreqtradeBot +from freqtrade.persistence import Order, Trade +from freqtrade.persistence.models import PairLock +from freqtrade.util.datetime_helpers import dt_now +from tests.conftest import (EXMS, get_patched_freqtradebot, log_has, log_has_re, patch_edge, + patch_exchange, patch_get_signal, patch_whitelist) +from tests.conftest_trades import entry_side, exit_side +from tests.freqtradebot.test_freqtradebot import patch_RPCManager + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short, fee) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 1.9, + 'ask': 2.2, + 'last': 1.9 + }), + create_order=MagicMock(return_value=limit_order[entry_side(is_short)]), + get_fee=fee, + ) + order = limit_order[entry_side(is_short)] + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) + mocker.patch(f'{EXMS}.fetch_order', return_value=order) + mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) + + stoploss = MagicMock(return_value={'id': 13434334}) + mocker.patch(f'{EXMS}.create_stoploss', stoploss) + + freqtrade = FreqtradeBot(default_conf_usdt) + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() + trade.is_short = is_short + trade.is_open = True + trades = [trade] + + freqtrade.exit_positions(trades) + assert trade.has_open_sl_orders is True + assert stoploss.call_count == 1 + assert trade.is_open is True + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_short, + limit_order) -> None: + stop_order_dict = {'id': "13434334"} + stoploss = MagicMock(return_value=stop_order_dict) + enter_order = limit_order[entry_side(is_short)] + exit_order = limit_order[exit_side(is_short)] + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 1.9, + 'ask': 2.2, + 'last': 1.9 + }), + create_order=MagicMock(side_effect=[ + enter_order, + exit_order, + ]), + get_fee=fee, + create_stoploss=stoploss + ) + freqtrade = FreqtradeBot(default_conf_usdt) + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + + # First case: when stoploss is not yet set but the order is open + # should get the stoploss order id immediately + # and should return false as no trade actually happened + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() + assert trade.is_short == is_short + assert trade.is_open + assert trade.has_open_sl_orders is False + + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert stoploss.call_count == 1 + assert trade.open_sl_orders[-1].order_id == "13434334" + + # Second case: when stoploss is set but it is not yet hit + # should do nothing and return false + trade.is_open = True + + hanging_stoploss_order = MagicMock(return_value={'id': '13434334', 'status': 'open'}) + mocker.patch(f'{EXMS}.fetch_stoploss_order', hanging_stoploss_order) + + assert freqtrade.handle_stoploss_on_exchange(trade) is False + hanging_stoploss_order.assert_called_once_with('13434334', trade.pair) + assert len(trade.open_sl_orders) == 1 + assert trade.open_sl_orders[-1].order_id == "13434334" + + # Third case: when stoploss was set but it was canceled for some reason + # should set a stoploss immediately and return False + caplog.clear() + trade.is_open = True + + canceled_stoploss_order = MagicMock(return_value={'id': '13434334', 'status': 'canceled'}) + mocker.patch(f'{EXMS}.fetch_stoploss_order', canceled_stoploss_order) + stoploss.reset_mock() + amount_before = trade.amount + + stop_order_dict.update({'id': "103_1"}) + + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert stoploss.call_count == 1 + assert len(trade.open_sl_orders) == 1 + assert trade.open_sl_orders[-1].order_id == "103_1" + assert trade.amount == amount_before + + # Fourth case: when stoploss is set and it is hit + # should return true as a trade actually happened + caplog.clear() + stop_order_dict.update({'id': "103_1"}) + + trade = Trade.session.scalars(select(Trade)).first() + trade.is_short = is_short + trade.is_open = True + + stoploss_order_hit = MagicMock(return_value={ + 'id': "103_1", + 'status': 'closed', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'filled': enter_order['amount'], + 'remaining': 0, + 'amount': enter_order['amount'], + }) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit) + assert freqtrade.handle_stoploss_on_exchange(trade) is True + assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog) + assert len(trade.open_sl_orders) == 0 + assert trade.is_open is False + caplog.clear() + + mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError()) + trade.is_open = True + freqtrade.handle_stoploss_on_exchange(trade) + assert log_has('Unable to place a stoploss order on exchange.', caplog) + assert len(trade.open_sl_orders) == 0 + + # Fifth case: fetch_order returns InvalidOrder + # It should try to add stoploss order + stop_order_dict.update({'id': "105"}) + stoploss.reset_mock() + mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=InvalidOrderException()) + mocker.patch(f'{EXMS}.create_stoploss', stoploss) + freqtrade.handle_stoploss_on_exchange(trade) + assert len(trade.open_sl_orders) == 1 + assert stoploss.call_count == 1 + + # Sixth case: Closed Trade + # Should not create new order + trade.is_open = False + trade.open_sl_orders[-1].ft_is_open = False + stoploss.reset_mock() + mocker.patch(f'{EXMS}.fetch_order') + mocker.patch(f'{EXMS}.create_stoploss', stoploss) + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert trade.has_open_sl_orders is False + assert stoploss.call_count == 0 + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_handle_stoploss_on_exchange_emergency(mocker, default_conf_usdt, fee, is_short, + limit_order) -> None: + stop_order_dict = {'id': "13434334"} + stoploss = MagicMock(return_value=stop_order_dict) + enter_order = limit_order[entry_side(is_short)] + exit_order = limit_order[exit_side(is_short)] + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 1.9, + 'ask': 2.2, + 'last': 1.9 + }), + create_order=MagicMock(side_effect=[ + enter_order, + exit_order, + ]), + get_fee=fee, + create_stoploss=stoploss + ) + freqtrade = FreqtradeBot(default_conf_usdt) + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() + assert trade.is_short == is_short + assert trade.is_open + assert trade.has_open_sl_orders is False + + # emergency exit triggered + # Trailing stop should not act anymore + stoploss_order_cancelled = MagicMock(side_effect=[{ + 'id': "107", + 'status': 'canceled', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'amount': enter_order['amount'], + 'filled': 0, + 'remaining': enter_order['amount'], + 'info': {'stopPrice': 22}, + }]) + trade.stoploss_last_update = dt_now() - timedelta(hours=1) + trade.stop_loss = 24 + trade.exit_reason = None + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_id='107', + status='open', + ) + ) + freqtrade.config['trailing_stop'] = True + stoploss = MagicMock(side_effect=InvalidOrderException()) + assert trade.has_open_sl_orders is True + Trade.commit() + mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', + side_effect=InvalidOrderException()) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_cancelled) + mocker.patch(f'{EXMS}.create_stoploss', stoploss) + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert trade.has_open_sl_orders is False + assert trade.is_open is False + assert trade.exit_reason == str(ExitType.EMERGENCY_EXIT) + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_handle_stoploss_on_exchange_partial( + mocker, default_conf_usdt, fee, is_short, limit_order) -> None: + stop_order_dict = {'id': "101", "status": "open"} + stoploss = MagicMock(return_value=stop_order_dict) + enter_order = limit_order[entry_side(is_short)] + exit_order = limit_order[exit_side(is_short)] + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 1.9, + 'ask': 2.2, + 'last': 1.9 + }), + create_order=MagicMock(side_effect=[ + enter_order, + exit_order, + ]), + get_fee=fee, + create_stoploss=stoploss + ) + freqtrade = FreqtradeBot(default_conf_usdt) + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() + trade.is_short = is_short + trade.is_open = True + + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert stoploss.call_count == 1 + assert trade.has_open_sl_orders is True + assert trade.open_sl_orders[-1].order_id == "101" + assert trade.amount == 30 + stop_order_dict.update({'id': "102"}) + # Stoploss on exchange is cancelled on exchange, but filled partially. + # Must update trade amount to guarantee successful exit. + stoploss_order_hit = MagicMock(return_value={ + 'id': "101", + 'status': 'canceled', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'filled': trade.amount / 2, + 'remaining': trade.amount / 2, + 'amount': enter_order['amount'], + }) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit) + assert freqtrade.handle_stoploss_on_exchange(trade) is False + # Stoploss filled partially ... + assert trade.amount == 15 + + assert trade.open_sl_orders[-1].order_id == "102" + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_handle_stoploss_on_exchange_partial_cancel_here( + mocker, default_conf_usdt, fee, is_short, limit_order, caplog, time_machine) -> None: + stop_order_dict = {'id': "101", "status": "open"} + time_machine.move_to(dt_now()) + default_conf_usdt['trailing_stop'] = True + stoploss = MagicMock(return_value=stop_order_dict) + enter_order = limit_order[entry_side(is_short)] + exit_order = limit_order[exit_side(is_short)] + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 1.9, + 'ask': 2.2, + 'last': 1.9 + }), + create_order=MagicMock(side_effect=[ + enter_order, + exit_order, + ]), + get_fee=fee, + create_stoploss=stoploss + ) + freqtrade = FreqtradeBot(default_conf_usdt) + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() + trade.is_short = is_short + trade.is_open = True + + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert stoploss.call_count == 1 + assert trade.has_open_sl_orders is True + assert trade.open_sl_orders[-1].order_id == "101" + assert trade.amount == 30 + stop_order_dict.update({'id': "102"}) + # Stoploss on exchange is open. + # Freqtrade cancels the stop - but cancel returns a partial filled order. + stoploss_order_hit = MagicMock(return_value={ + 'id': "101", + 'status': 'open', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'filled': 0, + 'remaining': trade.amount, + 'amount': enter_order['amount'], + }) + stoploss_order_cancel = MagicMock(return_value={ + 'id': "101", + 'status': 'canceled', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'filled': trade.amount / 2, + 'remaining': trade.amount / 2, + 'amount': enter_order['amount'], + }) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit) + mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', stoploss_order_cancel) + time_machine.shift(timedelta(minutes=15)) + + assert freqtrade.handle_stoploss_on_exchange(trade) is False + # Canceled Stoploss filled partially ... + assert log_has_re('Cancelling current stoploss on exchange.*', caplog) + + assert trade.has_open_sl_orders is True + assert trade.open_sl_orders[-1].order_id == "102" + assert trade.amount == 15 + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, is_short, + limit_order) -> None: + # Sixth case: stoploss order was cancelled but couldn't create new one + enter_order = limit_order[entry_side(is_short)] + exit_order = limit_order[exit_side(is_short)] + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 1.9, + 'ask': 2.2, + 'last': 1.9 + }), + create_order=MagicMock(side_effect=[ + enter_order, + exit_order, + ]), + get_fee=fee, + ) + mocker.patch.multiple( + EXMS, + fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': '100'}), + create_stoploss=MagicMock(side_effect=ExchangeError()), + ) + freqtrade = FreqtradeBot(default_conf_usdt) + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() + assert trade.is_short == is_short + trade.is_open = True + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_id='100', + status='open', + ) + ) + assert trade + + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert log_has_re(r'All Stoploss orders are cancelled, but unable to recreate one\.', caplog) + assert trade.has_open_sl_orders is False + assert trade.is_open is True + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_create_stoploss_order_invalid_order( + mocker, default_conf_usdt, caplog, fee, is_short, limit_order +): + open_order = limit_order[entry_side(is_short)] + order = limit_order[exit_side(is_short)] + rpc_mock = patch_RPCManager(mocker) + patch_exchange(mocker) + create_order_mock = MagicMock(side_effect=[ + open_order, + order, + ]) + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 1.9, + 'ask': 2.2, + 'last': 1.9 + }), + create_order=create_order_mock, + get_fee=fee, + ) + mocker.patch.multiple( + EXMS, + fetch_order=MagicMock(return_value={'status': 'canceled'}), + create_stoploss=MagicMock(side_effect=InvalidOrderException()), + ) + freqtrade = FreqtradeBot(default_conf_usdt) + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() + trade.is_short = is_short + caplog.clear() + rpc_mock.reset_mock() + freqtrade.create_stoploss_order(trade, 200) + assert trade.has_open_sl_orders is False + assert trade.exit_reason == ExitType.EMERGENCY_EXIT.value + assert log_has("Unable to place a stoploss order on exchange. ", caplog) + assert log_has("Exiting the trade forcefully", caplog) + + # Should call a market sell + assert create_order_mock.call_count == 2 + assert create_order_mock.call_args[1]['ordertype'] == 'market' + assert create_order_mock.call_args[1]['pair'] == trade.pair + assert create_order_mock.call_args[1]['amount'] == trade.amount + + # Rpc is sending first buy, then sell + assert rpc_mock.call_count == 2 + assert rpc_mock.call_args_list[0][0][0]['exit_reason'] == ExitType.EMERGENCY_EXIT.value + assert rpc_mock.call_args_list[0][0][0]['order_type'] == 'market' + assert rpc_mock.call_args_list[0][0][0]['type'] == 'exit' + assert rpc_mock.call_args_list[1][0][0]['type'] == 'exit_fill' + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_create_stoploss_order_insufficient_funds( + mocker, default_conf_usdt, caplog, fee, limit_order, is_short +): + exit_order = limit_order[exit_side(is_short)]['id'] + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + + mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds') + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 1.9, + 'ask': 2.2, + 'last': 1.9 + }), + create_order=MagicMock(side_effect=[ + limit_order[entry_side(is_short)], + exit_order, + ]), + get_fee=fee, + fetch_order=MagicMock(return_value={'status': 'canceled'}), + ) + mocker.patch.multiple( + EXMS, + create_stoploss=MagicMock(side_effect=InsufficientFundsError()), + ) + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() + trade.is_short = is_short + caplog.clear() + freqtrade.create_stoploss_order(trade, 200) + # stoploss_orderid was empty before + assert trade.has_open_sl_orders is False + assert mock_insuf.call_count == 1 + mock_insuf.reset_mock() + + freqtrade.create_stoploss_order(trade, 200) + # No change to stoploss-orderid + assert trade.has_open_sl_orders is False + assert mock_insuf.call_count == 1 + + +@pytest.mark.parametrize("is_short,bid,ask,stop_price,hang_price", [ + (False, [4.38, 4.16], [4.4, 4.17], ['2.0805', 4.4 * 0.95], 3), + (True, [1.09, 1.21], [1.1, 1.22], ['2.321', 1.09 * 1.05], 1.5), +]) +@pytest.mark.usefixtures("init_persistence") +def test_handle_stoploss_on_exchange_trailing( + mocker, default_conf_usdt, fee, is_short, bid, ask, limit_order, stop_price, hang_price, + time_machine, +) -> None: + # When trailing stoploss is set + enter_order = limit_order[entry_side(is_short)] + exit_order = limit_order[exit_side(is_short)] + stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'}) + start_dt = dt_now() + time_machine.move_to(start_dt, tick=False) + patch_RPCManager(mocker) + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 2.19, + 'ask': 2.2, + 'last': 2.19, + }), + create_order=MagicMock(side_effect=[ + enter_order, + exit_order, + ]), + get_fee=fee, + ) + mocker.patch.multiple( + EXMS, + create_stoploss=stoploss, + stoploss_adjust=MagicMock(return_value=True), + ) + + # enabling TSL + default_conf_usdt['trailing_stop'] = True + + # disabling ROI + default_conf_usdt['minimal_roi']['0'] = 999999999 + + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + + # enabling stoploss on exchange + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + + # setting stoploss + freqtrade.strategy.stoploss = 0.05 if is_short else -0.05 + + # setting stoploss_on_exchange_interval to 60 seconds + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 + + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() + trade.is_short = is_short + trade.is_open = True + assert trade.has_open_sl_orders is False + trade.stoploss_last_update = dt_now() - timedelta(minutes=20) + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_id='100', + order_date=dt_now() - timedelta(minutes=20), + ) + ) + + stoploss_order_hanging = { + 'id': '100', + 'status': 'open', + 'type': 'stop_loss_limit', + 'price': hang_price, + 'average': 2, + 'fee': {}, + 'amount': 0, + 'info': { + 'stopPrice': stop_price[0] + } + } + stoploss_order_cancel = deepcopy(stoploss_order_hanging) + stoploss_order_cancel['status'] = 'canceled' + + mocker.patch(f'{EXMS}.fetch_stoploss_order', return_value=stoploss_order_hanging) + mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value=stoploss_order_cancel) + + # stoploss initially at 5% + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + + assert len(trade.open_sl_orders) == 1 + + assert trade.open_sl_orders[-1].order_id == '13434334' + + # price jumped 2x + mocker.patch( + f'{EXMS}.fetch_ticker', + MagicMock(return_value={ + 'bid': bid[0], + 'ask': ask[0], + 'last': bid[0], + }) + ) + + cancel_order_mock = MagicMock(return_value={ + 'id': '13434334', 'status': 'canceled', 'fee': {}, 'amount': trade.amount}) + stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'}) + mocker.patch(f'{EXMS}.fetch_stoploss_order') + mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) + mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock) + + # stoploss should not be updated as the interval is 60 seconds + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert len(trade.open_sl_orders) == 1 + cancel_order_mock.assert_not_called() + stoploss_order_mock.assert_not_called() + + # Move time by 10s ... so stoploss order should be replaced. + time_machine.move_to(start_dt + timedelta(minutes=10), tick=False) + + assert freqtrade.handle_trade(trade) is False + assert trade.stop_loss == stop_price[1] + + assert freqtrade.handle_stoploss_on_exchange(trade) is False + + cancel_order_mock.assert_called_once_with('13434334', 'ETH/USDT') + stoploss_order_mock.assert_called_once_with( + amount=30, + pair='ETH/USDT', + order_types=freqtrade.strategy.order_types, + stop_price=stop_price[1], + side=exit_side(is_short), + leverage=1.0 + ) + + # price fell below stoploss, so dry-run sells trade. + mocker.patch( + f'{EXMS}.fetch_ticker', + MagicMock(return_value={ + 'bid': bid[1], + 'ask': ask[1], + 'last': bid[1], + }) + ) + mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', + return_value={'id': 'so1', 'status': 'canceled'}) + assert len(trade.open_sl_orders) == 1 + assert trade.open_sl_orders[-1].order_id == 'so1' + + assert freqtrade.handle_trade(trade) is True + assert trade.is_open is False + assert trade.has_open_sl_orders is False + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_handle_stoploss_on_exchange_trailing_error( + mocker, default_conf_usdt, fee, caplog, limit_order, is_short, time_machine +) -> None: + time_machine.move_to(dt_now() - timedelta(minutes=601)) + enter_order = limit_order[entry_side(is_short)] + exit_order = limit_order[exit_side(is_short)] + # When trailing stoploss is set + stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'}) + patch_exchange(mocker) + + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 1.9, + 'ask': 2.2, + 'last': 1.9 + }), + create_order=MagicMock(side_effect=[ + {'id': enter_order['id']}, + {'id': exit_order['id']}, + ]), + get_fee=fee, + create_stoploss=stoploss, + stoploss_adjust=MagicMock(return_value=True), + ) + + # enabling TSL + default_conf_usdt['trailing_stop'] = True + + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + # enabling stoploss on exchange + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + + # setting stoploss + freqtrade.strategy.stoploss = 0.05 if is_short else -0.05 + + # setting stoploss_on_exchange_interval to 60 seconds + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() + trade.is_short = is_short + trade.is_open = True + trade.stop_loss = 0.2 + + stoploss_order_hanging = { + 'id': "abcd", + 'status': 'open', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'info': { + 'stopPrice': '0.1' + } + } + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=3, + order_id='abcd', + order_date=dt_now(), + ) + ) + mocker.patch(f'{EXMS}.cancel_stoploss_order', + side_effect=InvalidOrderException()) + mocker.patch(f'{EXMS}.fetch_stoploss_order', + return_value=stoploss_order_hanging) + time_machine.shift(timedelta(minutes=50)) + freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) + assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog) + + # Still try to create order + assert stoploss.call_count == 1 + # TODO: Is this actually correct ? This will create a new order every time, + assert len(trade.open_sl_orders) == 2 + + # Fail creating stoploss order + caplog.clear() + cancel_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order') + mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError()) + time_machine.shift(timedelta(minutes=50)) + freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) + assert cancel_mock.call_count == 2 + assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog) + + +def test_stoploss_on_exchange_price_rounding( + mocker, default_conf_usdt, fee, open_trade_usdt) -> None: + patch_RPCManager(mocker) + mocker.patch.multiple( + EXMS, + get_fee=fee, + ) + price_mock = MagicMock(side_effect=lambda p, s, **kwargs: int(s)) + stoploss_mock = MagicMock(return_value={'id': '13434334'}) + adjust_mock = MagicMock(return_value=False) + mocker.patch.multiple( + EXMS, + create_stoploss=stoploss_mock, + stoploss_adjust=adjust_mock, + price_to_precision=price_mock, + ) + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + open_trade_usdt.stop_loss = 222.55 + + freqtrade.handle_trailing_stoploss_on_exchange(open_trade_usdt, {}) + assert price_mock.call_count == 1 + assert adjust_mock.call_count == 1 + assert adjust_mock.call_args_list[0][0][0] == 222 + + +@pytest.mark.parametrize("is_short", [False, True]) +@pytest.mark.usefixtures("init_persistence") +def test_handle_stoploss_on_exchange_custom_stop( + mocker, default_conf_usdt, fee, is_short, limit_order +) -> None: + enter_order = limit_order[entry_side(is_short)] + exit_order = limit_order[exit_side(is_short)] + # When trailing stoploss is set + stoploss = MagicMock(return_value={'id': 13434334, 'status': 'open'}) + patch_RPCManager(mocker) + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 1.9, + 'ask': 2.2, + 'last': 1.9 + }), + create_order=MagicMock(side_effect=[ + enter_order, + exit_order, + ]), + get_fee=fee, + is_cancel_order_result_suitable=MagicMock(return_value=True), + ) + mocker.patch.multiple( + EXMS, + create_stoploss=stoploss, + stoploss_adjust=MagicMock(return_value=True), + ) + + # enabling TSL + default_conf_usdt['use_custom_stoploss'] = True + + # disabling ROI + default_conf_usdt['minimal_roi']['0'] = 999999999 + + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + + # enabling stoploss on exchange + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + + # setting stoploss + freqtrade.strategy.custom_stoploss = lambda *args, **kwargs: -0.04 + + # setting stoploss_on_exchange_interval to 60 seconds + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 + + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() + trade.is_short = is_short + trade.is_open = True + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_date=dt_now() - timedelta(minutes=601), + order_id='100', + ) + ) + Trade.commit() + slo = { + 'id': '100', + 'status': 'open', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'info': { + 'stopPrice': '2.0805' + } + } + slo_canceled = deepcopy(slo) + slo_canceled.update({'status': 'canceled'}) + + def fetch_stoploss_order_mock(order_id, *args, **kwargs): + x = deepcopy(slo) + x['id'] = order_id + return x + + mocker.patch(f'{EXMS}.fetch_stoploss_order', MagicMock(fetch_stoploss_order_mock)) + mocker.patch(f'{EXMS}.cancel_stoploss_order', return_value=slo_canceled) + + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + + # price jumped 2x + mocker.patch( + f'{EXMS}.fetch_ticker', + MagicMock(return_value={ + 'bid': 4.38 if not is_short else 1.9 / 2, + 'ask': 4.4 if not is_short else 2.2 / 2, + 'last': 4.38 if not is_short else 1.9 / 2, + }) + ) + + cancel_order_mock = MagicMock() + stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'}) + mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) + mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock) + + # stoploss should not be updated as the interval is 60 seconds + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + cancel_order_mock.assert_not_called() + stoploss_order_mock.assert_not_called() + + assert freqtrade.handle_trade(trade) is False + assert trade.stop_loss == 4.4 * 0.96 if not is_short else 1.1 + assert trade.stop_loss_pct == -0.04 if not is_short else 0.04 + + # setting stoploss_on_exchange_interval to 0 seconds + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 + cancel_order_mock.assert_not_called() + stoploss_order_mock.assert_not_called() + + assert freqtrade.handle_stoploss_on_exchange(trade) is False + + cancel_order_mock.assert_called_once_with('13434334', 'ETH/USDT') + # Long uses modified ask - offset, short modified bid + offset + stoploss_order_mock.assert_called_once_with( + amount=pytest.approx(trade.amount), + pair='ETH/USDT', + order_types=freqtrade.strategy.order_types, + stop_price=4.4 * 0.96 if not is_short else 0.95 * 1.04, + side=exit_side(is_short), + leverage=1.0 + ) + + # price fell below stoploss, so dry-run sells trade. + mocker.patch( + f'{EXMS}.fetch_ticker', + MagicMock(return_value={ + 'bid': 4.17, + 'ask': 4.19, + 'last': 4.17 + }) + ) + assert freqtrade.handle_trade(trade) is True + + +def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_order) -> None: + + enter_order = limit_order['buy'] + exit_order = limit_order['sell'] + enter_order['average'] = 2.19 + # When trailing stoploss is set + stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'}) + patch_RPCManager(mocker) + patch_exchange(mocker) + patch_edge(mocker) + edge_conf['max_open_trades'] = float('inf') + edge_conf['dry_run_wallet'] = 999.9 + edge_conf['exchange']['name'] = 'binance' + mocker.patch.multiple( + EXMS, + fetch_ticker=MagicMock(return_value={ + 'bid': 2.19, + 'ask': 2.2, + 'last': 2.19 + }), + create_order=MagicMock(side_effect=[ + enter_order, + exit_order, + ]), + get_fee=fee, + create_stoploss=stoploss, + ) + + # enabling TSL + edge_conf['trailing_stop'] = True + edge_conf['trailing_stop_positive'] = 0.01 + edge_conf['trailing_stop_positive_offset'] = 0.011 + + # disabling ROI + edge_conf['minimal_roi']['0'] = 999999999 + + freqtrade = FreqtradeBot(edge_conf) + + # enabling stoploss on exchange + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + + # setting stoploss + freqtrade.strategy.stoploss = -0.02 + + # setting stoploss_on_exchange_interval to 0 seconds + freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0 + + patch_get_signal(freqtrade) + + freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist) + + freqtrade.enter_positions() + trade = Trade.session.scalars(select(Trade)).first() + trade.is_open = True + + trade.stoploss_last_update = dt_now() + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_id='100', + ) + ) + + stoploss_order_hanging = MagicMock(return_value={ + 'id': '100', + 'status': 'open', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'stopPrice': '2.178' + }) + + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hanging) + + # stoploss initially at 20% as edge dictated it. + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert pytest.approx(trade.stop_loss) == 1.76 + + cancel_order_mock = MagicMock() + stoploss_order_mock = MagicMock() + mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) + mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock) + + # price goes down 5% + mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ + 'bid': 2.19 * 0.95, + 'ask': 2.2 * 0.95, + 'last': 2.19 * 0.95 + })) + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + + # stoploss should remain the same + assert pytest.approx(trade.stop_loss) == 1.76 + + # stoploss on exchange should not be canceled + cancel_order_mock.assert_not_called() + + # price jumped 2x + mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ + 'bid': 4.38, + 'ask': 4.4, + 'last': 4.38 + })) + + assert freqtrade.handle_trade(trade) is False + assert freqtrade.handle_stoploss_on_exchange(trade) is False + + # stoploss should be set to 1% as trailing is on + assert trade.stop_loss == 4.4 * 0.99 + cancel_order_mock.assert_called_once_with('100', 'NEO/BTC') + stoploss_order_mock.assert_called_once_with( + amount=30, + pair='NEO/BTC', + order_types=freqtrade.strategy.order_types, + stop_price=4.4 * 0.99, + side='sell', + leverage=1.0 + ) + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( + default_conf_usdt, ticker_usdt, fee, is_short, ticker_usdt_sell_down, + ticker_usdt_sell_up, mocker) -> None: + rpc_mock = patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + EXMS, + fetch_ticker=ticker_usdt, + get_fee=fee, + _dry_is_price_crossed=MagicMock(return_value=False), + ) + patch_whitelist(mocker, default_conf_usdt) + freqtrade = FreqtradeBot(default_conf_usdt) + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + + # Create some test data + freqtrade.enter_positions() + + trade = Trade.session.scalars(select(Trade)).first() + assert trade.is_short == is_short + assert trade + + # Decrease the price and sell it + mocker.patch.multiple( + EXMS, + fetch_ticker=ticker_usdt_sell_up if is_short else ticker_usdt_sell_down + ) + + default_conf_usdt['dry_run'] = True + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + # Setting trade stoploss to 0.01 + + trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99 + freqtrade.execute_trade_exit( + trade=trade, limit=trade.stop_loss, + exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)) + + assert rpc_mock.call_count == 2 + last_msg = rpc_mock.call_args_list[-1][0][0] + + assert { + 'type': RPCMessageType.EXIT, + 'trade_id': 1, + 'exchange': 'Binance', + 'pair': 'ETH/USDT', + 'direction': 'Short' if trade.is_short else 'Long', + 'leverage': 1.0, + 'gain': 'loss', + 'limit': 2.02 if is_short else 1.98, + 'order_rate': 2.02 if is_short else 1.98, + 'amount': pytest.approx(29.70297029 if is_short else 30.0), + 'order_type': 'limit', + 'buy_tag': None, + 'enter_tag': None, + 'open_rate': 2.02 if is_short else 2.0, + 'current_rate': 2.2 if is_short else 2.0, + 'profit_amount': -0.3 if is_short else -0.8985, + 'profit_ratio': -0.00501253 if is_short else -0.01493766, + 'stake_currency': 'USDT', + 'quote_currency': 'USDT', + 'fiat_currency': 'USD', + 'base_currency': 'ETH', + 'exit_reason': ExitType.STOP_LOSS.value, + 'open_date': ANY, + 'close_date': ANY, + 'close_rate': ANY, + 'sub_trade': False, + 'cumulative_profit': 0.0, + 'stake_amount': pytest.approx(60), + 'is_final_exit': False, + 'final_profit_ratio': None, + } == last_msg + + +def test_execute_trade_exit_sloe_cancel_exception( + mocker, default_conf_usdt, ticker_usdt, fee, caplog) -> None: + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=InvalidOrderException()) + mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=300)) + create_order_mock = MagicMock(side_effect=[ + {'id': '12345554'}, + {'id': '12345555'}, + ]) + patch_exchange(mocker) + mocker.patch.multiple( + EXMS, + fetch_ticker=ticker_usdt, + get_fee=fee, + create_order=create_order_mock, + ) + + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + patch_get_signal(freqtrade) + freqtrade.enter_positions() + + trade = Trade.session.scalars(select(Trade)).first() + PairLock.session = MagicMock() + + freqtrade.config['dry_run'] = False + trade.orders.append( + Order( + ft_order_side='stoploss', + ft_pair=trade.pair, + ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.stop_loss, + order_id='abcd', + status='open', + ) + ) + + freqtrade.execute_trade_exit(trade=trade, limit=1234, + exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)) + assert create_order_mock.call_count == 2 + assert log_has('Could not cancel stoploss order abcd for pair ETH/USDT', caplog) + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_execute_trade_exit_with_stoploss_on_exchange( + default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, is_short, mocker) -> None: + + default_conf_usdt['exchange']['name'] = 'binance' + rpc_mock = patch_RPCManager(mocker) + patch_exchange(mocker) + stoploss = MagicMock(return_value={ + 'id': 123, + 'status': 'open', + 'info': { + 'foo': 'bar' + } + }) + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_order_fee') + + cancel_order = MagicMock(return_value=True) + mocker.patch.multiple( + EXMS, + fetch_ticker=ticker_usdt, + get_fee=fee, + amount_to_precision=lambda s, x, y: y, + price_to_precision=lambda s, x, y: y, + create_stoploss=stoploss, + cancel_stoploss_order=cancel_order, + _dry_is_price_crossed=MagicMock(side_effect=[True, False]), + ) + + freqtrade = FreqtradeBot(default_conf_usdt) + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + + # Create some test data + freqtrade.enter_positions() + + trade = Trade.session.scalars(select(Trade)).first() + trade.is_short = is_short + assert trade + trades = [trade] + + freqtrade.manage_open_orders() + freqtrade.exit_positions(trades) + + # Increase the price and sell it + mocker.patch.multiple( + EXMS, + fetch_ticker=ticker_usdt_sell_up + ) + + freqtrade.execute_trade_exit( + trade=trade, + limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'], + exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS) + ) + + trade = Trade.session.scalars(select(Trade)).first() + trade.is_short = is_short + assert trade + assert cancel_order.call_count == 1 + assert rpc_mock.call_count == 4 + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_may_execute_trade_exit_after_stoploss_on_exchange_hit( + default_conf_usdt, ticker_usdt, fee, mocker, is_short) -> None: + default_conf_usdt['exchange']['name'] = 'binance' + rpc_mock = patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + EXMS, + fetch_ticker=ticker_usdt, + get_fee=fee, + amount_to_precision=lambda s, x, y: y, + price_to_precision=lambda s, x, y: y, + _dry_is_price_crossed=MagicMock(side_effect=[False, True]), + ) + + stoploss = MagicMock(return_value={ + 'id': 123, + 'info': { + 'foo': 'bar' + } + }) + + mocker.patch(f'{EXMS}.create_stoploss', stoploss) + + freqtrade = FreqtradeBot(default_conf_usdt) + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + patch_get_signal(freqtrade, enter_long=not is_short, enter_short=is_short) + + # Create some test data + freqtrade.enter_positions() + freqtrade.manage_open_orders() + trade = Trade.session.scalars(select(Trade)).first() + trades = [trade] + assert trade.has_open_sl_orders is False + + freqtrade.exit_positions(trades) + assert trade + assert trade.has_open_sl_orders is True + assert not trade.has_open_orders + + # Assuming stoploss on exchange is hit + # trade should be sold at the price of stoploss, with exit_reason STOPLOSS_ON_EXCHANGE + stoploss_executed = MagicMock(return_value={ + "id": "123", + "timestamp": 1542707426845, + "datetime": "2018-11-20T09:50:26.845Z", + "lastTradeTimestamp": None, + "symbol": "BTC/USDT", + "type": "stop_loss_limit", + "side": "buy" if is_short else "sell", + "price": 1.08801, + "amount": trade.amount, + "cost": 1.08801 * trade.amount, + "average": 1.08801, + "filled": trade.amount, + "remaining": 0.0, + "status": "closed", + "fee": None, + "trades": None + }) + mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_executed) + + freqtrade.exit_positions(trades) + assert trade.has_open_sl_orders is False + assert trade.is_open is False + assert trade.exit_reason == ExitType.STOPLOSS_ON_EXCHANGE.value + assert rpc_mock.call_count == 4 + assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.ENTRY + assert rpc_mock.call_args_list[1][0][0]['amount'] > 20 + assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.ENTRY_FILL + assert rpc_mock.call_args_list[3][0][0]['type'] == RPCMessageType.EXIT_FILL From c5948693a30bedc64a86f003d101927d70585f2d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Feb 2024 08:14:17 +0100 Subject: [PATCH 119/247] enable sub-minute backtest detail timeframes closes #9635 --- freqtrade/optimize/backtesting.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 43aa00a65..493c7567f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -23,7 +23,7 @@ from freqtrade.enums import (BacktestState, CandleType, ExitCheckTuple, ExitType TradingMode) from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import (amount_to_contract_precision, price_to_precision, - timeframe_to_minutes, timeframe_to_seconds) + timeframe_to_seconds) from freqtrade.exchange.exchange import Exchange from freqtrade.mixins import LoggingMixin from freqtrade.optimize.backtest_caching import get_strategy_run_id @@ -117,8 +117,9 @@ class Backtesting: raise OperationalException("Timeframe needs to be set in either " "configuration or as cli argument `--timeframe 5m`") self.timeframe = str(self.config.get('timeframe')) - self.timeframe_min = timeframe_to_minutes(self.timeframe) - self.timeframe_td = timedelta(minutes=self.timeframe_min) + self.timeframe_secs = timeframe_to_seconds(self.timeframe) + self.timeframe_min = self.timeframe_secs // 60 + self.timeframe_td = timedelta(seconds=self.timeframe_secs) self.disable_database_use() self.init_backtest_detail() self.pairlists = PairListManager(self.exchange, self.config, self.dataprovider) @@ -185,9 +186,9 @@ class Backtesting: # Load detail timeframe if specified self.timeframe_detail = str(self.config.get('timeframe_detail', '')) if self.timeframe_detail: - timeframe_detail_min = timeframe_to_minutes(self.timeframe_detail) - self.timeframe_detail_td = timedelta(minutes=timeframe_detail_min) - if self.timeframe_min <= timeframe_detail_min: + timeframe_detail_secs = timeframe_to_seconds(self.timeframe_detail) + self.timeframe_detail_td = timedelta(seconds=timeframe_detail_secs) + if self.timeframe_secs <= timeframe_detail_secs: raise OperationalException( "Detail timeframe must be smaller than strategy timeframe.") From 3711fa509819cf75f4fd9a6c0dea1e5b7a8da096 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Feb 2024 13:10:42 +0100 Subject: [PATCH 120/247] Improve formatting of leverage closes #9759 --- freqtrade/rpc/telegram.py | 4 ++-- tests/rpc/test_rpc_telegram.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e2fbe1529..f42e3ab51 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -353,7 +353,7 @@ class Telegram(RPCHandler): message += f"*Amount:* `{round_value(msg['amount'], 8)}`\n" message += f"*Direction:* `{msg['direction']}" if msg.get('leverage') and msg.get('leverage', 1.0) != 1.0: - message += f" ({msg['leverage']:.1g}x)" + message += f" ({msg['leverage']:.3g}x)" message += "`\n" message += f"*Open Rate:* `{fmt_coin(msg['open_rate'], msg['quote_currency'])}`\n" if msg['type'] == RPCMessageType.ENTRY and msg['current_rate']: @@ -371,7 +371,7 @@ class Telegram(RPCHandler): microsecond=0) - msg['open_date'].replace(microsecond=0) duration_min = duration.total_seconds() / 60 - leverage_text = (f" ({msg['leverage']:.1g}x)" + leverage_text = (f" ({msg['leverage']:.3g}x)" if msg.get('leverage') and msg.get('leverage', 1.0) != 1.0 else "") diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f85b19a01..2e6852876 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -2017,7 +2017,7 @@ def test_send_msg_enter_notification(default_conf, mocker, caplog, message_type, telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram.send_msg(msg) - leverage_text = f' ({leverage:.1g}x)' if leverage and leverage != 1.0 else '' + leverage_text = f' ({leverage:.3g}x)' if leverage and leverage != 1.0 else '' assert msg_mock.call_args[0][0] == ( f'\N{LARGE BLUE CIRCLE} *Binance (dry):* New Trade (#1)\n' @@ -2126,7 +2126,7 @@ def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, en 'amount': 1333.3333333333335, 'open_date': dt_now() - timedelta(hours=1) }) - leverage_text = f' ({leverage:.1g}x)' if leverage != 1.0 else '' + leverage_text = f' ({leverage:.3g}x)' if leverage != 1.0 else '' assert msg_mock.call_args[0][0] == ( f'\N{CHECK MARK} *Binance (dry):* New Trade filled (#1)\n' f'*Pair:* `ETH/BTC`\n' @@ -2365,7 +2365,7 @@ def test_send_msg_exit_fill_notification(default_conf, mocker, direction, 'close_date': dt_now(), }) - leverage_text = f' ({leverage:.1g}x)`\n' if leverage and leverage != 1.0 else '`\n' + leverage_text = f' ({leverage:.3g}x)`\n' if leverage and leverage != 1.0 else '`\n' assert msg_mock.call_args[0][0] == ( '\N{WARNING SIGN} *Binance (dry):* Exited KEY/ETH (#1)\n' '*Profit:* `-57.41% (loss: -0.05746 ETH)`\n' @@ -2458,7 +2458,7 @@ def test_send_msg_buy_notification_no_fiat( 'open_date': dt_now() - timedelta(hours=1) }) - leverage_text = f' ({leverage:.1g}x)' if leverage and leverage != 1.0 else '' + leverage_text = f' ({leverage:.3g}x)' if leverage and leverage != 1.0 else '' assert msg_mock.call_args[0][0] == ( f'\N{LARGE BLUE CIRCLE} *Binance:* New Trade (#1)\n' '*Pair:* `ETH/BTC`\n' @@ -2510,7 +2510,7 @@ def test_send_msg_exit_notification_no_fiat( 'close_date': dt_now(), }) - leverage_text = f' ({leverage:.1g}x)' if leverage and leverage != 1.0 else '' + leverage_text = f' ({leverage:.3g}x)' if leverage and leverage != 1.0 else '' assert msg_mock.call_args[0][0] == ( '\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n' '*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH)`\n' From 2989b427603b3e4b7c331335e5dfc4ba9de41236 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Feb 2024 13:16:32 +0100 Subject: [PATCH 121/247] Update cached binance leverage tiers --- .../exchange/binance_leverage_tiers.json | 6360 +++++++---------- 1 file changed, 2554 insertions(+), 3806 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 3a6e6b0a1..36c2e5996 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -227,104 +227,6 @@ } } ], - "1000LUNC/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "1000LUNC/USDT:USDT": [ { "tier": 1.0, @@ -569,7 +471,7 @@ } } ], - "1000SATS/USDT:USDT": [ + "1000RATS/USDT:USDT": [ { "tier": 1.0, "currency": "USDT", @@ -683,91 +585,91 @@ } } ], - "1000SHIB/BUSD:BUSD": [ + "1000SATS/USDT:USDT": [ { "tier": 1.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "10", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "0.0" } }, { "tier": 2.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", + "initialLeverage": "20", + "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.025", - "cum": "25.0" + "cum": "50.0" } }, { "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", + "initialLeverage": "10", + "notionalCap": "200000", + "notionalFloor": "50000", "maintMarginRatio": "0.05", - "cum": "650.0" + "cum": "1300.0" } }, { "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "11300.0" } }, { "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1500000.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1500000", - "notionalFloor": "250000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "23800.0" } }, { "tier": 6.0, - "currency": "BUSD", - "minNotional": 1500000.0, + "currency": "USDT", + "minNotional": 1000000.0, "maxNotional": 3000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, @@ -775,25 +677,25 @@ "bracket": "6", "initialLeverage": "2", "notionalCap": "3000000", - "notionalFloor": "1500000", + "notionalFloor": "1000000", "maintMarginRatio": "0.25", - "cum": "199400.0" + "cum": "148800.0" } }, { "tier": 7.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 3000000.0, - "maxNotional": 3500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "3500000", + "notionalCap": "5000000", "notionalFloor": "3000000", "maintMarginRatio": "0.5", - "cum": "949400.0" + "cum": "898800.0" } } ], @@ -1269,6 +1171,120 @@ } } ], + "ACE/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "675.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "ACH/USDT:USDT": [ { "tier": 1.0, @@ -1367,104 +1383,6 @@ } } ], - "ADA/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "0", - "maintMarginRatio": "0.025", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "500000", - "notionalFloor": "100000", - "maintMarginRatio": "0.05", - "cum": "2500.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "3", - "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.1", - "cum": "27500.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, - "info": { - "bracket": "4", - "initialLeverage": "3", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.15", - "cum": "77500.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.25", - "cum": "277500.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 5000000.0, - "maxNotional": 5500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "5500000", - "notionalFloor": "5000000", - "maintMarginRatio": "0.5", - "cum": "1527500.0" - } - } - ], "ADA/USDT:USDT": [ { "tier": 1.0, @@ -1627,104 +1545,6 @@ } } ], - "AGIX/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "AGIX/USDT:USDT": [ { "tier": 1.0, @@ -1953,6 +1773,120 @@ } } ], + "AI/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "675.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "ALGO/USDT:USDT": [ { "tier": 1.0, @@ -2263,101 +2197,117 @@ } } ], - "AMB/BUSD:BUSD": [ + "ALT/USDT:USDT": [ { "tier": 1.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "8", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "0.0" } }, { "tier": 2.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "7", + "initialLeverage": "20", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", - "cum": "25.0" + "cum": "50.0" } }, { "tier": 3.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "6", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", - "cum": "650.0" + "cum": "675.0" } }, { "tier": 4.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 100000.0, - "maxNotional": 250000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", + "notionalCap": "200000", "notionalFloor": "100000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "5675.0" } }, { "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "10675.0" } }, { "tier": 6.0, - "currency": "BUSD", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 1500000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "1500000", + "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "323175.0" } } ], @@ -2475,104 +2425,6 @@ } } ], - "ANC/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, - "info": { - "bracket": "2", - "initialLeverage": "7", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "ANKR/USDT:USDT": [ { "tier": 1.0, @@ -2785,104 +2637,6 @@ } } ], - "APE/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 1200000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1200000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "APE/USDT:USDT": [ { "tier": 1.0, @@ -3159,120 +2913,6 @@ } } ], - "APT/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1500000", - "maintMarginRatio": "0.25", - "cum": "199400.0" - } - }, - { - "tier": 7.0, - "currency": "BUSD", - "minNotional": 3000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "4000000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.5", - "cum": "949400.0" - } - } - ], "APT/USDT:USDT": [ { "tier": 1.0, @@ -4299,101 +3939,117 @@ } } ], - "AUCTION/BUSD:BUSD": [ + "AUCTION/USDT:USDT": [ { "tier": 1.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "8", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "0.0" } }, { "tier": 2.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "7", + "initialLeverage": "20", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", - "cum": "25.0" + "cum": "50.0" } }, { "tier": 3.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "6", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", - "cum": "650.0" + "cum": "675.0" } }, { "tier": 4.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 100000.0, - "maxNotional": 250000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", + "notionalCap": "200000", "notionalFloor": "100000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "5675.0" } }, { "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "10675.0" } }, { "tier": 6.0, - "currency": "BUSD", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 1500000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", - "notionalCap": "1500000", + "notionalCap": "2000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "323175.0" } } ], @@ -4495,120 +4151,6 @@ } } ], - "AVAX/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, - "info": { - "bracket": "2", - "initialLeverage": "7", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1500000", - "maintMarginRatio": "0.25", - "cum": "199400.0" - } - }, - { - "tier": 7.0, - "currency": "BUSD", - "minNotional": 3000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "4000000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.5", - "cum": "949400.0" - } - } - ], "AVAX/USDT:USDT": [ { "tier": 1.0, @@ -5118,10 +4660,10 @@ "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", + "initialLeverage": "4", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.125", @@ -5132,16 +4674,32 @@ "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1500000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "136925.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1500000.0, "maxNotional": 3000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "386925.0" + "cum": "511925.0" } } ], @@ -6324,10 +5882,10 @@ "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "4", - "initialLeverage": "2", + "initialLeverage": "4", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.125", @@ -6338,121 +5896,39 @@ "tier": 5.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "5", - "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386875.0" - } - } - ], - "BNB/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 11.0, - "info": { - "bracket": "1", - "initialLeverage": "11", - "notionalCap": "100000", - "notionalFloor": "0", - "maintMarginRatio": "0.025", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": "2", - "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "100000", - "maintMarginRatio": "0.05", - "cum": "2500.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "3", - "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.1", - "cum": "27500.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, - "info": { - "bracket": "4", - "initialLeverage": "3", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.15", - "cum": "77500.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "1500000", + "notionalFloor": "1000000", "maintMarginRatio": "0.25", - "cum": "277500.0" + "cum": "136875.0" } }, { "tier": 6.0, - "currency": "BUSD", - "minNotional": 5000000.0, - "maxNotional": 5500000.0, + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "5500000", - "notionalFloor": "5000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "1527500.0" + "cum": "511875.0" } } ], - "BNB/USDT:USDT": [ + "BNB/USDC:USDC": [ { "tier": 1.0, - "currency": "USDT", + "currency": "USDC", "minNotional": 0.0, "maxNotional": 5000.0, "maintenanceMarginRate": 0.005, @@ -6468,7 +5944,7 @@ }, { "tier": 2.0, - "currency": "USDT", + "currency": "USDC", "minNotional": 5000.0, "maxNotional": 10000.0, "maintenanceMarginRate": 0.006, @@ -6484,7 +5960,7 @@ }, { "tier": 3.0, - "currency": "USDT", + "currency": "USDC", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, @@ -6500,7 +5976,7 @@ }, { "tier": 4.0, - "currency": "USDT", + "currency": "USDC", "minNotional": 50000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.02, @@ -6516,7 +5992,7 @@ }, { "tier": 5.0, - "currency": "USDT", + "currency": "USDC", "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.05, @@ -6532,7 +6008,7 @@ }, { "tier": 6.0, - "currency": "USDT", + "currency": "USDC", "minNotional": 1000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.1, @@ -6548,7 +6024,7 @@ }, { "tier": 7.0, - "currency": "USDT", + "currency": "USDC", "minNotional": 5000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.125, @@ -6564,7 +6040,7 @@ }, { "tier": 8.0, - "currency": "USDT", + "currency": "USDC", "minNotional": 10000000.0, "maxNotional": 20000000.0, "maintenanceMarginRate": 0.15, @@ -6580,7 +6056,7 @@ }, { "tier": 9.0, - "currency": "USDT", + "currency": "USDC", "minNotional": 20000000.0, "maxNotional": 30000000.0, "maintenanceMarginRate": 0.25, @@ -6596,7 +6072,7 @@ }, { "tier": 10.0, - "currency": "USDT", + "currency": "USDC", "minNotional": 30000000.0, "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, @@ -6611,6 +6087,168 @@ } } ], + "BNB/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, + "info": { + "bracket": "1", + "initialLeverage": "75", + "notionalCap": "10000", + "notionalFloor": "0", + "maintMarginRatio": "0.005", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, + "info": { + "bracket": "2", + "initialLeverage": "50", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.006", + "cum": "10.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 40.0, + "info": { + "bracket": "3", + "initialLeverage": "40", + "notionalCap": "100000", + "notionalFloor": "50000", + "maintMarginRatio": "0.01", + "cum": "210.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": "4", + "initialLeverage": "25", + "notionalCap": "500000", + "notionalFloor": "100000", + "maintMarginRatio": "0.02", + "cum": "1210.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "5", + "initialLeverage": "10", + "notionalCap": "2000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.05", + "cum": "16210.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "6000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.1", + "cum": "116210.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 6000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "10000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.125", + "cum": "266210.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 10000000.0, + "maxNotional": 20000000.0, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3.0, + "info": { + "bracket": "8", + "initialLeverage": "3", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.15", + "cum": "516210.0" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 20000000.0, + "maxNotional": 30000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "30000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "2516210.0" + } + }, + { + "tier": 10.0, + "currency": "USDT", + "minNotional": 30000000.0, + "maxNotional": 50000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "10", + "initialLeverage": "1", + "notionalCap": "50000000", + "notionalFloor": "30000000", + "maintMarginRatio": "0.5", + "cum": "10016210.0" + } + } + ], "BNT/USDT:USDT": [ { "tier": 1.0, @@ -7051,17 +6689,17 @@ } } ], - "BTC/BUSD:BUSD": [ + "BTC/USDC:USDC": [ { "tier": 1.0, - "currency": "BUSD", + "currency": "USDC", "minNotional": 0.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.004, - "maxLeverage": 30.0, + "maxLeverage": 125.0, "info": { "bracket": "1", - "initialLeverage": "30", + "initialLeverage": "125", "notionalCap": "50000", "notionalFloor": "0", "maintMarginRatio": "0.004", @@ -7070,15 +6708,15 @@ }, { "tier": 2.0, - "currency": "BUSD", + "currency": "USDC", "minNotional": 50000.0, - "maxNotional": 250000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.005, - "maxLeverage": 25.0, + "maxLeverage": 100.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "250000", + "initialLeverage": "100", + "notionalCap": "500000", "notionalFloor": "50000", "maintMarginRatio": "0.005", "cum": "50.0" @@ -7086,130 +6724,130 @@ }, { "tier": 3.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "currency": "USDC", + "minNotional": 500000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maxLeverage": 50.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "1000000", - "notionalFloor": "250000", + "initialLeverage": "50", + "notionalCap": "10000000", + "notionalFloor": "500000", "maintMarginRatio": "0.01", - "cum": "1300.0" + "cum": "2550.0" } }, { "tier": 4.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 7500000.0, + "currency": "USDC", + "minNotional": 10000000.0, + "maxNotional": 80000000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 15.0, + "maxLeverage": 20.0, "info": { "bracket": "4", - "initialLeverage": "15", - "notionalCap": "7500000", - "notionalFloor": "1000000", + "initialLeverage": "20", + "notionalCap": "80000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.025", - "cum": "16300.0" + "cum": "152550.0" } }, { "tier": 5.0, - "currency": "BUSD", - "minNotional": 7500000.0, - "maxNotional": 40000000.0, + "currency": "USDC", + "minNotional": 80000000.0, + "maxNotional": 150000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "40000000", - "notionalFloor": "7500000", + "notionalCap": "150000000", + "notionalFloor": "80000000", "maintMarginRatio": "0.05", - "cum": "203800.0" + "cum": "2152550.0" } }, { "tier": 6.0, - "currency": "BUSD", - "minNotional": 40000000.0, - "maxNotional": 100000000.0, + "currency": "USDC", + "minNotional": 150000000.0, + "maxNotional": 300000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "100000000", - "notionalFloor": "40000000", + "notionalCap": "300000000", + "notionalFloor": "150000000", "maintMarginRatio": "0.1", - "cum": "2203800.0" + "cum": "9652550.0" } }, { "tier": 7.0, - "currency": "BUSD", - "minNotional": 100000000.0, - "maxNotional": 200000000.0, + "currency": "USDC", + "minNotional": 300000000.0, + "maxNotional": 450000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "200000000", - "notionalFloor": "100000000", + "notionalCap": "450000000", + "notionalFloor": "300000000", "maintMarginRatio": "0.125", - "cum": "4703800.0" + "cum": "17152550.0" } }, { "tier": 8.0, - "currency": "BUSD", - "minNotional": 200000000.0, - "maxNotional": 400000000.0, + "currency": "USDC", + "minNotional": 450000000.0, + "maxNotional": 600000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "400000000", - "notionalFloor": "200000000", + "notionalCap": "600000000", + "notionalFloor": "450000000", "maintMarginRatio": "0.15", - "cum": "9703800.0" + "cum": "28402550.0" } }, { "tier": 9.0, - "currency": "BUSD", - "minNotional": 400000000.0, - "maxNotional": 600000000.0, + "currency": "USDC", + "minNotional": 600000000.0, + "maxNotional": 800000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "600000000", - "notionalFloor": "400000000", + "notionalCap": "800000000", + "notionalFloor": "600000000", "maintMarginRatio": "0.25", - "cum": "49703800.0" + "cum": "88402550.0" } }, { "tier": 10.0, - "currency": "BUSD", - "minNotional": 600000000.0, - "maxNotional": 600500000.0, + "currency": "USDC", + "minNotional": 800000000.0, + "maxNotional": 1000000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "600500000", - "notionalFloor": "600000000", + "notionalCap": "1000000000", + "notionalFloor": "800000000", "maintMarginRatio": "0.5", - "cum": "199703800.0" + "cum": "288402550.0" } } ], @@ -7250,13 +6888,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 8000000.0, + "maxNotional": 10000000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 50.0, "info": { "bracket": "3", "initialLeverage": "50", - "notionalCap": "8000000", + "notionalCap": "10000000", "notionalFloor": "500000", "maintMarginRatio": "0.01", "cum": "2550.0" @@ -7265,117 +6903,117 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 8000000.0, - "maxNotional": 50000000.0, + "minNotional": 10000000.0, + "maxNotional": 80000000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "50000000", - "notionalFloor": "8000000", + "notionalCap": "80000000", + "notionalFloor": "10000000", "maintMarginRatio": "0.025", - "cum": "122550.0" + "cum": "152550.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 50000000.0, - "maxNotional": 80000000.0, + "minNotional": 80000000.0, + "maxNotional": 150000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "80000000", - "notionalFloor": "50000000", + "notionalCap": "150000000", + "notionalFloor": "80000000", "maintMarginRatio": "0.05", - "cum": "1372550.0" + "cum": "2152550.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 80000000.0, - "maxNotional": 100000000.0, + "minNotional": 150000000.0, + "maxNotional": 300000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "100000000", - "notionalFloor": "80000000", + "notionalCap": "300000000", + "notionalFloor": "150000000", "maintMarginRatio": "0.1", - "cum": "5372550.0" + "cum": "9652550.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 100000000.0, - "maxNotional": 120000000.0, + "minNotional": 300000000.0, + "maxNotional": 450000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "120000000", - "notionalFloor": "100000000", + "notionalCap": "450000000", + "notionalFloor": "300000000", "maintMarginRatio": "0.125", - "cum": "7872550.0" + "cum": "17152550.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 120000000.0, - "maxNotional": 200000000.0, + "minNotional": 450000000.0, + "maxNotional": 600000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "200000000", - "notionalFloor": "120000000", + "notionalCap": "600000000", + "notionalFloor": "450000000", "maintMarginRatio": "0.15", - "cum": "10872550.0" + "cum": "28402550.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 200000000.0, - "maxNotional": 300000000.0, + "minNotional": 600000000.0, + "maxNotional": 800000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "300000000", - "notionalFloor": "200000000", + "notionalCap": "800000000", + "notionalFloor": "600000000", "maintMarginRatio": "0.25", - "cum": "30872550.0" + "cum": "88402550.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 300000000.0, - "maxNotional": 500000000.0, + "minNotional": 800000000.0, + "maxNotional": 1000000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "500000000", - "notionalFloor": "300000000", + "notionalCap": "1000000000", + "notionalFloor": "800000000", "maintMarginRatio": "0.5", - "cum": "105872550.0" + "cum": "288402550.0" } } ], - "BTC/USDT:USDT-231229": [ + "BTC/USDT:USDT-240329": [ { "tier": 1.0, "currency": "USDT", @@ -7505,7 +7143,7 @@ } } ], - "BTC/USDT:USDT-240329": [ + "BTC/USDT:USDT-240628": [ { "tier": 1.0, "currency": "USDT", @@ -9657,104 +9295,6 @@ } } ], - "CVX/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.5", - "cum": "199400.0" - } - } - ], "CVX/USDT:USDT": [ { "tier": 1.0, @@ -10473,88 +10013,6 @@ } } ], - "DODO/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "0", - "maintMarginRatio": "0.025", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "2", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "625.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "3", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5625.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "4", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11875.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 1200000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "5", - "initialLeverage": "1", - "notionalCap": "1200000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386875.0" - } - } - ], "DODOX/USDT:USDT": [ { "tier": 1.0, @@ -10669,101 +10127,165 @@ } } ], - "DOGE/BUSD:BUSD": [ + "DOGE/USDC:USDC": [ { "tier": 1.0, - "currency": "BUSD", + "currency": "USDC", "minNotional": 0.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 10.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "10", - "notionalCap": "100000", + "initialLeverage": "75", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.025", + "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 8.0, + "currency": "USDC", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "8", - "notionalCap": "500000", - "notionalFloor": "100000", - "maintMarginRatio": "0.05", - "cum": "2500.0" + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.006", + "cum": "5.0" } }, { "tier": 3.0, - "currency": "BUSD", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "currency": "USDC", + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.007, + "maxLeverage": 40.0, "info": { "bracket": "3", - "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.1", - "cum": "27500.0" + "initialLeverage": "40", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.007", + "cum": "15.0" } }, { "tier": 4.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, + "currency": "USDC", + "minNotional": 50000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 25.0, "info": { "bracket": "4", - "initialLeverage": "3", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.15", - "cum": "77500.0" + "initialLeverage": "25", + "notionalCap": "750000", + "notionalFloor": "50000", + "maintMarginRatio": "0.01", + "cum": "165.0" } }, { "tier": 5.0, - "currency": "BUSD", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "currency": "USDC", + "minNotional": 750000.0, + "maxNotional": 1100000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.25", - "cum": "277500.0" + "initialLeverage": "20", + "notionalCap": "1100000", + "notionalFloor": "750000", + "maintMarginRatio": "0.025", + "cum": "11415.0" } }, { "tier": 6.0, - "currency": "BUSD", - "minNotional": 5000000.0, - "maxNotional": 5200000.0, + "currency": "USDC", + "minNotional": 1100000.0, + "maxNotional": 2200000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "6", + "initialLeverage": "10", + "notionalCap": "2200000", + "notionalFloor": "1100000", + "maintMarginRatio": "0.05", + "cum": "38915.0" + } + }, + { + "tier": 7.0, + "currency": "USDC", + "minNotional": 2200000.0, + "maxNotional": 5600000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "7", + "initialLeverage": "5", + "notionalCap": "5600000", + "notionalFloor": "2200000", + "maintMarginRatio": "0.1", + "cum": "148915.0" + } + }, + { + "tier": 8.0, + "currency": "USDC", + "minNotional": 5600000.0, + "maxNotional": 7000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "8", + "initialLeverage": "4", + "notionalCap": "7000000", + "notionalFloor": "5600000", + "maintMarginRatio": "0.125", + "cum": "288915.0" + } + }, + { + "tier": 9.0, + "currency": "USDC", + "minNotional": 7000000.0, + "maxNotional": 18000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "18000000", + "notionalFloor": "7000000", + "maintMarginRatio": "0.25", + "cum": "1163915.0" + } + }, + { + "tier": 10.0, + "currency": "USDC", + "minNotional": 18000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "10", "initialLeverage": "1", - "notionalCap": "5200000", - "notionalFloor": "5000000", + "notionalCap": "30000000", + "notionalFloor": "18000000", "maintMarginRatio": "0.5", - "cum": "1527500.0" + "cum": "5663915.0" } } ], @@ -10929,120 +10451,6 @@ } } ], - "DOT/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1500000", - "maintMarginRatio": "0.25", - "cum": "199400.0" - } - }, - { - "tier": 7.0, - "currency": "BUSD", - "minNotional": 3000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "4000000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.5", - "cum": "949400.0" - } - } - ], "DOT/USDT:USDT": [ { "tier": 1.0, @@ -11080,13 +10488,13 @@ "tier": 3.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 250000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", + "notionalCap": "500000", "notionalFloor": "50000", "maintMarginRatio": "0.02", "cum": "535.0" @@ -11095,55 +10503,55 @@ { "tier": 4.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 500000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalCap": "2000000", + "notionalFloor": "500000", "maintMarginRatio": "0.05", - "cum": "8035.0" + "cum": "15535.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "notionalCap": "5000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.1", - "cum": "58035.0" + "cum": "115535.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 7000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "notionalCap": "7000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.125", - "cum": "108035.0" + "cum": "240535.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 5000000.0, + "minNotional": 7000000.0, "maxNotional": 10000000.0, "maintenanceMarginRate": 0.15, "maxLeverage": 3.0, @@ -11151,41 +10559,41 @@ "bracket": "7", "initialLeverage": "3", "notionalCap": "10000000", - "notionalFloor": "5000000", + "notionalFloor": "7000000", "maintMarginRatio": "0.15", - "cum": "233035.0" + "cum": "415535.0" } }, { "tier": 8.0, "currency": "USDT", "minNotional": 10000000.0, - "maxNotional": 50000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "50000000", + "notionalCap": "30000000", "notionalFloor": "10000000", "maintMarginRatio": "0.25", - "cum": "1233035.0" + "cum": "1415535.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 50000000.0, - "maxNotional": 100000000.0, + "minNotional": 30000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "100000000", - "notionalFloor": "50000000", + "notionalCap": "50000000", + "notionalFloor": "30000000", "maintMarginRatio": "0.5", - "cum": "13733035.0" + "cum": "8915535.0" } } ], @@ -12051,104 +11459,6 @@ } } ], - "ETC/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, - "info": { - "bracket": "2", - "initialLeverage": "7", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "ETC/USDT:USDT": [ { "tier": 1.0, @@ -12473,17 +11783,17 @@ } } ], - "ETH/BUSD:BUSD": [ + "ETH/USDC:USDC": [ { "tier": 1.0, - "currency": "BUSD", + "currency": "USDC", "minNotional": 0.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.004, - "maxLeverage": 30.0, + "maxLeverage": 125.0, "info": { "bracket": "1", - "initialLeverage": "30", + "initialLeverage": "125", "notionalCap": "50000", "notionalFloor": "0", "maintMarginRatio": "0.004", @@ -12492,15 +11802,15 @@ }, { "tier": 2.0, - "currency": "BUSD", + "currency": "USDC", "minNotional": 50000.0, - "maxNotional": 100000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.005, - "maxLeverage": 25.0, + "maxLeverage": 100.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "100000", + "initialLeverage": "100", + "notionalCap": "500000", "notionalFloor": "50000", "maintMarginRatio": "0.005", "cum": "50.0" @@ -12508,130 +11818,146 @@ }, { "tier": 3.0, - "currency": "BUSD", - "minNotional": 100000.0, + "currency": "USDC", + "minNotional": 500000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75.0, "info": { "bracket": "3", - "initialLeverage": "20", + "initialLeverage": "75", "notionalCap": "1000000", - "notionalFloor": "100000", - "maintMarginRatio": "0.01", - "cum": "550.0" + "notionalFloor": "500000", + "maintMarginRatio": "0.0065", + "cum": "800.0" } }, { "tier": 4.0, - "currency": "BUSD", + "currency": "USDC", "minNotional": 1000000.0, "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 15.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, "info": { "bracket": "4", - "initialLeverage": "15", + "initialLeverage": "50", "notionalCap": "5000000", "notionalFloor": "1000000", - "maintMarginRatio": "0.025", - "cum": "15550.0" + "maintMarginRatio": "0.01", + "cum": "4300.0" } }, { "tier": 5.0, - "currency": "BUSD", + "currency": "USDC", "minNotional": 5000000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 50000000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, "info": { "bracket": "5", - "initialLeverage": "10", - "notionalCap": "10000000", + "initialLeverage": "20", + "notionalCap": "50000000", "notionalFloor": "5000000", - "maintMarginRatio": "0.05", - "cum": "140550.0" + "maintMarginRatio": "0.02", + "cum": "54300.0" } }, { "tier": 6.0, - "currency": "BUSD", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "currency": "USDC", + "minNotional": 50000000.0, + "maxNotional": 100000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "6", - "initialLeverage": "5", - "notionalCap": "20000000", - "notionalFloor": "10000000", - "maintMarginRatio": "0.1", - "cum": "640550.0" + "initialLeverage": "10", + "notionalCap": "100000000", + "notionalFloor": "50000000", + "maintMarginRatio": "0.05", + "cum": "1554300.0" } }, { "tier": 7.0, - "currency": "BUSD", - "minNotional": 20000000.0, - "maxNotional": 40000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "currency": "USDC", + "minNotional": 100000000.0, + "maxNotional": 150000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "7", - "initialLeverage": "4", - "notionalCap": "40000000", - "notionalFloor": "20000000", - "maintMarginRatio": "0.125", - "cum": "1140550.0" + "initialLeverage": "5", + "notionalCap": "150000000", + "notionalFloor": "100000000", + "maintMarginRatio": "0.1", + "cum": "6554300.0" } }, { "tier": 8.0, - "currency": "BUSD", - "minNotional": 40000000.0, - "maxNotional": 80000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, + "currency": "USDC", + "minNotional": 150000000.0, + "maxNotional": 300000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "8", - "initialLeverage": "3", - "notionalCap": "80000000", - "notionalFloor": "40000000", - "maintMarginRatio": "0.15", - "cum": "2140550.0" + "initialLeverage": "4", + "notionalCap": "300000000", + "notionalFloor": "150000000", + "maintMarginRatio": "0.125", + "cum": "10304300.0" } }, { "tier": 9.0, - "currency": "BUSD", - "minNotional": 80000000.0, - "maxNotional": 150000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "currency": "USDC", + "minNotional": 300000000.0, + "maxNotional": 400000000.0, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3.0, "info": { "bracket": "9", - "initialLeverage": "2", - "notionalCap": "150000000", - "notionalFloor": "80000000", - "maintMarginRatio": "0.25", - "cum": "10140550.0" + "initialLeverage": "3", + "notionalCap": "400000000", + "notionalFloor": "300000000", + "maintMarginRatio": "0.15", + "cum": "17804300.0" } }, { "tier": 10.0, - "currency": "BUSD", - "minNotional": 150000000.0, - "maxNotional": 160000000.0, + "currency": "USDC", + "minNotional": 400000000.0, + "maxNotional": 500000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "10", + "initialLeverage": "2", + "notionalCap": "500000000", + "notionalFloor": "400000000", + "maintMarginRatio": "0.25", + "cum": "57804300.0" + } + }, + { + "tier": 11.0, + "currency": "USDC", + "minNotional": 500000000.0, + "maxNotional": 800000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "10", + "bracket": "11", "initialLeverage": "1", - "notionalCap": "160000000", - "notionalFloor": "150000000", + "notionalCap": "800000000", + "notionalFloor": "500000000", "maintMarginRatio": "0.5", - "cum": "47640550.0" + "cum": "182804300.0" } } ], @@ -12640,164 +11966,180 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.005, - "maxLeverage": 100.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.004, + "maxLeverage": 125.0, "info": { "bracket": "1", - "initialLeverage": "100", - "notionalCap": "200000", + "initialLeverage": "125", + "notionalCap": "50000", "notionalFloor": "0", - "maintMarginRatio": "0.005", + "maintMarginRatio": "0.004", "cum": "0.0" } }, { "tier": 2.0, "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.0065, - "maxLeverage": 75.0, + "minNotional": 50000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 100.0, "info": { "bracket": "2", - "initialLeverage": "75", - "notionalCap": "800000", - "notionalFloor": "200000", - "maintMarginRatio": "0.0065", - "cum": "300.0" + "initialLeverage": "100", + "notionalCap": "500000", + "notionalFloor": "50000", + "maintMarginRatio": "0.005", + "cum": "50.0" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 50.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75.0, "info": { "bracket": "3", - "initialLeverage": "50", - "notionalCap": "5000000", - "notionalFloor": "800000", - "maintMarginRatio": "0.01", - "cum": "3100.0" + "initialLeverage": "75", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.0065", + "cum": "800.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 30000000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 20.0, + "minNotional": 1000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, "info": { "bracket": "4", - "initialLeverage": "20", - "notionalCap": "30000000", - "notionalFloor": "5000000", - "maintMarginRatio": "0.02", - "cum": "53100.0" + "initialLeverage": "50", + "notionalCap": "5000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.01", + "cum": "4300.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 30000000.0, + "minNotional": 5000000.0, "maxNotional": 50000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 20.0, "info": { "bracket": "5", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "50000000", - "notionalFloor": "30000000", - "maintMarginRatio": "0.05", - "cum": "953100.0" + "notionalFloor": "5000000", + "maintMarginRatio": "0.02", + "cum": "54300.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 50000000.0, - "maxNotional": 70000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 100000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "6", - "initialLeverage": "5", - "notionalCap": "70000000", + "initialLeverage": "10", + "notionalCap": "100000000", "notionalFloor": "50000000", - "maintMarginRatio": "0.1", - "cum": "3453100.0" + "maintMarginRatio": "0.05", + "cum": "1554300.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 70000000.0, - "maxNotional": 80000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 100000000.0, + "maxNotional": 150000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "7", - "initialLeverage": "4", - "notionalCap": "80000000", - "notionalFloor": "70000000", - "maintMarginRatio": "0.125", - "cum": "5203100.0" + "initialLeverage": "5", + "notionalCap": "150000000", + "notionalFloor": "100000000", + "maintMarginRatio": "0.1", + "cum": "6554300.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 80000000.0, - "maxNotional": 100000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, + "minNotional": 150000000.0, + "maxNotional": 300000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "8", - "initialLeverage": "3", - "notionalCap": "100000000", - "notionalFloor": "80000000", - "maintMarginRatio": "0.15", - "cum": "7203100.0" + "initialLeverage": "4", + "notionalCap": "300000000", + "notionalFloor": "150000000", + "maintMarginRatio": "0.125", + "cum": "10304300.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 100000000.0, - "maxNotional": 150000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 300000000.0, + "maxNotional": 400000000.0, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3.0, "info": { "bracket": "9", - "initialLeverage": "2", - "notionalCap": "150000000", - "notionalFloor": "100000000", - "maintMarginRatio": "0.25", - "cum": "17203100.0" + "initialLeverage": "3", + "notionalCap": "400000000", + "notionalFloor": "300000000", + "maintMarginRatio": "0.15", + "cum": "17804300.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 150000000.0, - "maxNotional": 300000000.0, + "minNotional": 400000000.0, + "maxNotional": 500000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "10", + "initialLeverage": "2", + "notionalCap": "500000000", + "notionalFloor": "400000000", + "maintMarginRatio": "0.25", + "cum": "57804300.0" + } + }, + { + "tier": 11.0, + "currency": "USDT", + "minNotional": 500000000.0, + "maxNotional": 800000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "10", + "bracket": "11", "initialLeverage": "1", - "notionalCap": "300000000", - "notionalFloor": "150000000", + "notionalCap": "800000000", + "notionalFloor": "500000000", "maintMarginRatio": "0.5", - "cum": "54703100.0" + "cum": "182804300.0" } } ], - "ETH/USDT:USDT-231229": [ + "ETH/USDT:USDT-240329": [ { "tier": 1.0, "currency": "USDT", @@ -12927,7 +12269,7 @@ } } ], - "ETH/USDT:USDT-240329": [ + "ETH/USDT:USDT-240628": [ { "tier": 1.0, "currency": "USDT", @@ -13285,104 +12627,6 @@ } } ], - "FIL/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, - "info": { - "bracket": "2", - "initialLeverage": "7", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "FIL/USDT:USDT": [ { "tier": 1.0, @@ -13937,120 +13181,6 @@ } } ], - "FTM/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1500000", - "maintMarginRatio": "0.25", - "cum": "199400.0" - } - }, - { - "tier": 7.0, - "currency": "BUSD", - "minNotional": 3000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "4000000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.5", - "cum": "949400.0" - } - } - ], "FTM/USDT:USDT": [ { "tier": 1.0, @@ -14181,104 +13311,6 @@ } } ], - "FTT/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 15000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "15000", - "notionalFloor": "0", - "maintMarginRatio": "0.025", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 15000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "2", - "initialLeverage": "6", - "notionalCap": "50000", - "notionalFloor": "15000", - "maintMarginRatio": "0.05", - "cum": "375.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 50000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "3", - "initialLeverage": "5", - "notionalCap": "200000", - "notionalFloor": "50000", - "maintMarginRatio": "0.1", - "cum": "2875.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 200000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, - "info": { - "bracket": "4", - "initialLeverage": "3", - "notionalCap": "500000", - "notionalFloor": "200000", - "maintMarginRatio": "0.15", - "cum": "12875.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 500000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "800000", - "notionalFloor": "500000", - "maintMarginRatio": "0.25", - "cum": "62875.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 800000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "800000", - "maintMarginRatio": "0.5", - "cum": "262875.0" - } - } - ], "FTT/USDT:USDT": [ { "tier": 1.0, @@ -14459,104 +13491,6 @@ } } ], - "GAL/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.5", - "cum": "199400.0" - } - } - ], "GAL/USDT:USDT": [ { "tier": 1.0, @@ -14655,104 +13589,6 @@ } } ], - "GALA/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "GALA/USDT:USDT": [ { "tier": 1.0, @@ -15095,104 +13931,6 @@ } } ], - "GMT/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, - "info": { - "bracket": "2", - "initialLeverage": "7", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "GMT/USDT:USDT": [ { "tier": 1.0, @@ -16224,13 +14962,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 15.0, "info": { "bracket": "2", "initialLeverage": "15", - "notionalCap": "25000", + "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.025", "cum": "25.0" @@ -16239,39 +14977,39 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, + "minNotional": 50000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "notionalCap": "200000", + "notionalFloor": "50000", "maintMarginRatio": "0.05", - "cum": "650.0" + "cum": "1275.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "11275.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 2.0, @@ -16279,9 +15017,9 @@ "bracket": "5", "initialLeverage": "2", "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "23775.0" } }, { @@ -16297,7 +15035,7 @@ "notionalCap": "5000000", "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "398775.0" } } ], @@ -17817,6 +16555,120 @@ } } ], + "JUP/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "675.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "KAS/USDT:USDT": [ { "tier": 1.0, @@ -18469,104 +17321,6 @@ } } ], - "LDO/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "LDO/USDT:USDT": [ { "tier": 1.0, @@ -18697,104 +17451,6 @@ } } ], - "LEVER/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.023, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.023", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, - "info": { - "bracket": "2", - "initialLeverage": "7", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "10.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "635.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5635.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11885.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386885.0" - } - } - ], "LEVER/USDT:USDT": [ { "tier": 1.0, @@ -19023,120 +17679,6 @@ } } ], - "LINK/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1500000", - "maintMarginRatio": "0.25", - "cum": "199400.0" - } - }, - { - "tier": 7.0, - "currency": "BUSD", - "minNotional": 3000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "4000000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.5", - "cum": "949400.0" - } - } - ], "LINK/USDT:USDT": [ { "tier": 1.0, @@ -19789,117 +18331,117 @@ } } ], - "LTC/BUSD:BUSD": [ + "LSK/USDT:USDT": [ { "tier": 1.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": "1", - "initialLeverage": "10", + "initialLeverage": "50", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.015", "cum": "0.0" } }, { "tier": 2.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 5000.0, "maxNotional": 25000.0, "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, + "maxLeverage": 20.0, "info": { "bracket": "2", - "initialLeverage": "8", + "initialLeverage": "20", "notionalCap": "25000", "notionalFloor": "5000", "maintMarginRatio": "0.025", - "cum": "25.0" + "cum": "50.0" } }, { "tier": 3.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 25000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "maxLeverage": 10.0, "info": { "bracket": "3", - "initialLeverage": "6", + "initialLeverage": "10", "notionalCap": "100000", "notionalFloor": "25000", "maintMarginRatio": "0.05", - "cum": "650.0" + "cum": "675.0" } }, { "tier": 4.0, - "currency": "BUSD", + "currency": "USDT", "minNotional": 100000.0, - "maxNotional": 250000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", + "notionalCap": "200000", "notionalFloor": "100000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "5675.0" } }, { "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1500000.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1500000", - "notionalFloor": "250000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "10675.0" } }, { "tier": 6.0, - "currency": "BUSD", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1500000", + "notionalCap": "1000000", + "notionalFloor": "500000", "maintMarginRatio": "0.25", - "cum": "199400.0" + "cum": "73175.0" } }, { "tier": 7.0, - "currency": "BUSD", - "minNotional": 3000000.0, - "maxNotional": 3200000.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "3200000", - "notionalFloor": "3000000", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.5", - "cum": "949400.0" + "cum": "323175.0" } } ], @@ -20407,6 +18949,120 @@ } } ], + "MANTA/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "675.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "MASK/USDT:USDT": [ { "tier": 1.0, @@ -20537,120 +19193,6 @@ } } ], - "MATIC/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1500000", - "maintMarginRatio": "0.25", - "cum": "199400.0" - } - }, - { - "tier": 7.0, - "currency": "BUSD", - "minNotional": 3000000.0, - "maxNotional": 3500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "3500000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.5", - "cum": "949400.0" - } - } - ], "MATIC/USDT:USDT": [ { "tier": 1.0, @@ -21497,6 +20039,120 @@ } } ], + "MOVR/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "675.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "MTL/USDT:USDT": [ { "tier": 1.0, @@ -21611,104 +20267,6 @@ } } ], - "NEAR/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, - "info": { - "bracket": "2", - "initialLeverage": "7", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "NEAR/USDT:USDT": [ { "tier": 1.0, @@ -21730,13 +20288,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 150000.0, + "maxNotional": 250000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", + "notionalCap": "250000", "notionalFloor": "10000", "maintMarginRatio": "0.025", "cum": "100.0" @@ -21745,97 +20303,97 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 150000.0, - "maxNotional": 250000.0, + "minNotional": 250000.0, + "maxNotional": 750000.0, "maintenanceMarginRate": 0.03, "maxLeverage": 15.0, "info": { "bracket": "3", "initialLeverage": "15", - "notionalCap": "250000", - "notionalFloor": "150000", + "notionalCap": "750000", + "notionalFloor": "250000", "maintMarginRatio": "0.03", - "cum": "850.0" + "cum": "1350.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 600000.0, + "minNotional": 750000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "600000", - "notionalFloor": "250000", + "notionalCap": "1500000", + "notionalFloor": "750000", "maintMarginRatio": "0.05", - "cum": "5850.0" + "cum": "16350.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 600000.0, - "maxNotional": 1600000.0, + "minNotional": 1500000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "1600000", - "notionalFloor": "600000", + "notionalCap": "4000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.1", - "cum": "35850.0" + "cum": "91350.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 1600000.0, - "maxNotional": 2000000.0, + "minNotional": 4000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "2000000", - "notionalFloor": "1600000", + "notionalCap": "5000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.125", - "cum": "75850.0" + "cum": "191350.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 6000000.0, + "minNotional": 5000000.0, + "maxNotional": 12000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "6000000", - "notionalFloor": "2000000", + "notionalCap": "12000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.25", - "cum": "325850.0" + "cum": "816350.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 10000000.0, + "minNotional": 12000000.0, + "maxNotional": 20000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "6000000", + "notionalCap": "20000000", + "notionalFloor": "12000000", "maintMarginRatio": "0.5", - "cum": "1825850.0" + "cum": "3816350.0" } } ], @@ -21969,6 +20527,120 @@ } } ], + "NFP/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "675.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "NKN/USDT:USDT": [ { "tier": 1.0, @@ -22605,6 +21277,120 @@ } } ], + "ONDO/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "675.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "ONE/USDT:USDT": [ { "tier": 1.0, @@ -22936,13 +21722,13 @@ "tier": 1.0, "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.006, "maxLeverage": 50.0, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", + "notionalCap": "10000", "notionalFloor": "0", "maintMarginRatio": "0.006", "cum": "0.0" @@ -22951,7 +21737,7 @@ { "tier": 2.0, "currency": "USDT", - "minNotional": 5000.0, + "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 25.0, @@ -22959,9 +21745,9 @@ "bracket": "2", "initialLeverage": "25", "notionalCap": "50000", - "notionalFloor": "5000", + "notionalFloor": "10000", "maintMarginRatio": "0.01", - "cum": "20.0" + "cum": "40.0" } }, { @@ -22977,87 +21763,87 @@ "notionalCap": "600000", "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "770.0" + "cum": "790.0" } }, { "tier": 4.0, "currency": "USDT", "minNotional": 600000.0, - "maxNotional": 1200000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1200000", + "notionalCap": "2000000", "notionalFloor": "600000", "maintMarginRatio": "0.05", - "cum": "15770.0" + "cum": "15790.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 1200000.0, - "maxNotional": 3000000.0, + "minNotional": 2000000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "3000000", - "notionalFloor": "1200000", + "notionalCap": "5000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.1", - "cum": "75770.0" + "cum": "115790.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 5000000.0, + "minNotional": 5000000.0, + "maxNotional": 7000000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "3000000", + "notionalCap": "7000000", + "notionalFloor": "5000000", "maintMarginRatio": "0.125", - "cum": "150770.0" + "cum": "240790.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 12000000.0, + "minNotional": 7000000.0, + "maxNotional": 18000000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "12000000", - "notionalFloor": "5000000", + "notionalCap": "18000000", + "notionalFloor": "7000000", "maintMarginRatio": "0.25", - "cum": "775770.0" + "cum": "1115790.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 12000000.0, - "maxNotional": 20000000.0, + "minNotional": 18000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "20000000", - "notionalFloor": "12000000", + "notionalCap": "30000000", + "notionalFloor": "18000000", "maintMarginRatio": "0.5", - "cum": "3775770.0" + "cum": "5615790.0" } } ], @@ -23181,14 +21967,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "50", + "initialLeverage": "75", "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.015", + "maintMarginRatio": "0.0065", "cum": "0.0" } }, @@ -23196,112 +21982,144 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 75000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "25", - "notionalCap": "50000", + "initialLeverage": "50", + "notionalCap": "75000", "notionalFloor": "5000", - "maintMarginRatio": "0.02", - "cum": "25.0" + "maintMarginRatio": "0.01", + "cum": "17.5" } }, { "tier": 3.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 400000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 75000.0, + "maxNotional": 150000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 40.0, "info": { "bracket": "3", - "initialLeverage": "20", - "notionalCap": "400000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "275.0" + "initialLeverage": "40", + "notionalCap": "150000", + "notionalFloor": "75000", + "maintMarginRatio": "0.015", + "cum": "392.5" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 150000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "4", - "initialLeverage": "10", - "notionalCap": "800000", - "notionalFloor": "400000", - "maintMarginRatio": "0.05", - "cum": "10275.0" + "initialLeverage": "25", + "notionalCap": "300000", + "notionalFloor": "150000", + "maintMarginRatio": "0.02", + "cum": "1142.5" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 800000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 300000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "5", - "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "800000", - "maintMarginRatio": "0.1", - "cum": "50275.0" + "initialLeverage": "20", + "notionalCap": "1000000", + "notionalFloor": "300000", + "maintMarginRatio": "0.025", + "cum": "2642.5" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 1000000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "6", - "initialLeverage": "4", - "notionalCap": "4000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.125", - "cum": "100275.0" + "initialLeverage": "10", + "notionalCap": "3000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.05", + "cum": "27642.5" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 8000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 3000000.0, + "maxNotional": 6000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "7", - "initialLeverage": "2", - "notionalCap": "8000000", - "notionalFloor": "4000000", - "maintMarginRatio": "0.25", - "cum": "600275.0" + "initialLeverage": "5", + "notionalCap": "6000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.1", + "cum": "177642.5" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 8000000.0, - "maxNotional": 15000000.0, + "minNotional": 6000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "8", + "initialLeverage": "4", + "notionalCap": "10000000", + "notionalFloor": "6000000", + "maintMarginRatio": "0.125", + "cum": "327642.5" + } + }, + { + "tier": 9.0, + "currency": "USDT", + "minNotional": 10000000.0, + "maxNotional": 20000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "20000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.25", + "cum": "1577642.5" + } + }, + { + "tier": 10.0, + "currency": "USDT", + "minNotional": 20000000.0, + "maxNotional": 30000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "8", + "bracket": "10", "initialLeverage": "1", - "notionalCap": "15000000", - "notionalFloor": "8000000", + "notionalCap": "30000000", + "notionalFloor": "20000000", "maintMarginRatio": "0.5", - "cum": "2600275.0" + "cum": "6577642.5" } } ], @@ -23761,104 +22579,6 @@ } } ], - "PHB/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, - "info": { - "bracket": "2", - "initialLeverage": "7", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "PHB/USDT:USDT": [ { "tier": 1.0, @@ -25298,13 +24018,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 15.0, "info": { "bracket": "2", "initialLeverage": "15", - "notionalCap": "25000", + "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.025", "cum": "25.0" @@ -25313,65 +24033,81 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 100000.0, + "minNotional": 50000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "notionalCap": "200000", + "notionalFloor": "50000", "maintMarginRatio": "0.05", - "cum": "650.0" + "cum": "1275.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "500000", + "notionalFloor": "200000", "maintMarginRatio": "0.1", - "cum": "5650.0" + "cum": "11275.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 500000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "5", - "initialLeverage": "2", + "initialLeverage": "4", "notionalCap": "1000000", - "notionalFloor": "250000", + "notionalFloor": "500000", "maintMarginRatio": "0.125", - "cum": "11900.0" + "cum": "23775.0" } }, { "tier": 6.0, "currency": "USDT", "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "148775.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 2000000.0, "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "7", "initialLeverage": "1", "notionalCap": "5000000", - "notionalFloor": "1000000", + "notionalFloor": "2000000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "648775.0" } } ], @@ -25815,104 +24551,6 @@ } } ], - "SAND/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, - "info": { - "bracket": "2", - "initialLeverage": "7", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "SAND/USDT:USDT": [ { "tier": 1.0, @@ -26809,117 +25447,181 @@ } } ], - "SOL/BUSD:BUSD": [ + "SOL/USDC:USDC": [ { "tier": 1.0, - "currency": "BUSD", + "currency": "USDC", "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 100.0, "info": { "bracket": "1", - "initialLeverage": "10", - "notionalCap": "50000", + "initialLeverage": "100", + "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.02", + "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2.0, - "currency": "BUSD", - "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, + "currency": "USDC", + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75.0, "info": { "bracket": "2", - "initialLeverage": "8", - "notionalCap": "100000", - "notionalFloor": "50000", - "maintMarginRatio": "0.025", - "cum": "250.0" + "initialLeverage": "75", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.0065", + "cum": "15.0" } }, { "tier": 3.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, + "currency": "USDC", + "minNotional": 50000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, "info": { "bracket": "3", - "initialLeverage": "6", - "notionalCap": "500000", - "notionalFloor": "100000", - "maintMarginRatio": "0.05", - "cum": "2750.0" + "initialLeverage": "50", + "notionalCap": "200000", + "notionalFloor": "50000", + "maintMarginRatio": "0.01", + "cum": "190.0" } }, { "tier": 4.0, - "currency": "BUSD", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "currency": "USDC", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.012, + "maxLeverage": 40.0, "info": { "bracket": "4", - "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.1", - "cum": "27750.0" + "initialLeverage": "40", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.012", + "cum": "590.0" } }, { "tier": 5.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, + "currency": "USDC", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "5", - "initialLeverage": "3", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.15", - "cum": "77750.0" + "initialLeverage": "25", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.02", + "cum": "4590.0" } }, { "tier": 6.0, - "currency": "BUSD", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "currency": "USDC", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "6", - "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.25", - "cum": "277750.0" + "initialLeverage": "20", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.025", + "cum": "9590.0" } }, { "tier": 7.0, - "currency": "BUSD", + "currency": "USDC", + "minNotional": 2000000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "7", + "initialLeverage": "10", + "notionalCap": "5000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.05", + "cum": "59590.0" + } + }, + { + "tier": 8.0, + "currency": "USDC", "minNotional": 5000000.0, - "maxNotional": 5500000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "8", + "initialLeverage": "5", + "notionalCap": "15000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.1", + "cum": "309590.0" + } + }, + { + "tier": 9.0, + "currency": "USDC", + "minNotional": 15000000.0, + "maxNotional": 20000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "9", + "initialLeverage": "4", + "notionalCap": "20000000", + "notionalFloor": "15000000", + "maintMarginRatio": "0.125", + "cum": "684590.0" + } + }, + { + "tier": 10.0, + "currency": "USDC", + "minNotional": 20000000.0, + "maxNotional": 50000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "10", + "initialLeverage": "2", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "3184590.0" + } + }, + { + "tier": 11.0, + "currency": "USDC", + "minNotional": 50000000.0, + "maxNotional": 100000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "11", "initialLeverage": "1", - "notionalCap": "5500000", - "notionalFloor": "5000000", + "notionalCap": "100000000", + "notionalFloor": "50000000", "maintMarginRatio": "0.5", - "cum": "1527750.0" + "cum": "15684590.0" } } ], @@ -26929,14 +25631,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.0065, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 100.0, "info": { "bracket": "1", - "initialLeverage": "75", + "initialLeverage": "100", "notionalCap": "10000", "notionalFloor": "0", - "maintMarginRatio": "0.0065", + "maintMarginRatio": "0.005", "cum": "0.0" } }, @@ -26945,63 +25647,63 @@ "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75.0, "info": { "bracket": "2", - "initialLeverage": "50", + "initialLeverage": "75", "notionalCap": "50000", "notionalFloor": "10000", - "maintMarginRatio": "0.01", - "cum": "35.0" + "maintMarginRatio": "0.0065", + "cum": "15.0" } }, { "tier": 3.0, "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.012, - "maxLeverage": 40.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, "info": { "bracket": "3", - "initialLeverage": "40", - "notionalCap": "100000", + "initialLeverage": "50", + "notionalCap": "200000", "notionalFloor": "50000", - "maintMarginRatio": "0.012", - "cum": "135.0" + "maintMarginRatio": "0.01", + "cum": "190.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.012, + "maxLeverage": 40.0, "info": { "bracket": "4", - "initialLeverage": "25", - "notionalCap": "200000", - "notionalFloor": "100000", - "maintMarginRatio": "0.02", - "cum": "935.0" + "initialLeverage": "40", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.012", + "cum": "590.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 200000.0, + "minNotional": 500000.0, "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "5", - "initialLeverage": "20", + "initialLeverage": "25", "notionalCap": "1000000", - "notionalFloor": "200000", - "maintMarginRatio": "0.025", - "cum": "1935.0" + "notionalFloor": "500000", + "maintMarginRatio": "0.02", + "cum": "4590.0" } }, { @@ -27009,79 +25711,95 @@ "currency": "USDT", "minNotional": 1000000.0, "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": "6", - "initialLeverage": "10", + "initialLeverage": "20", "notionalCap": "2000000", "notionalFloor": "1000000", - "maintMarginRatio": "0.05", - "cum": "26935.0" + "maintMarginRatio": "0.025", + "cum": "9590.0" } }, { "tier": 7.0, "currency": "USDT", "minNotional": 2000000.0, - "maxNotional": 4800000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "7", - "initialLeverage": "5", - "notionalCap": "4800000", + "initialLeverage": "10", + "notionalCap": "5000000", "notionalFloor": "2000000", - "maintMarginRatio": "0.1", - "cum": "126935.0" + "maintMarginRatio": "0.05", + "cum": "59590.0" } }, { "tier": 8.0, "currency": "USDT", - "minNotional": 4800000.0, - "maxNotional": 6000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 5000000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": "8", - "initialLeverage": "4", - "notionalCap": "6000000", - "notionalFloor": "4800000", - "maintMarginRatio": "0.125", - "cum": "246935.0" + "initialLeverage": "5", + "notionalCap": "15000000", + "notionalFloor": "5000000", + "maintMarginRatio": "0.1", + "cum": "309590.0" } }, { "tier": 9.0, "currency": "USDT", - "minNotional": 6000000.0, - "maxNotional": 18000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 15000000.0, + "maxNotional": 20000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": "9", - "initialLeverage": "2", - "notionalCap": "18000000", - "notionalFloor": "6000000", - "maintMarginRatio": "0.25", - "cum": "996935.0" + "initialLeverage": "4", + "notionalCap": "20000000", + "notionalFloor": "15000000", + "maintMarginRatio": "0.125", + "cum": "684590.0" } }, { "tier": 10.0, "currency": "USDT", - "minNotional": 18000000.0, - "maxNotional": 30000000.0, + "minNotional": 20000000.0, + "maxNotional": 50000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "10", + "initialLeverage": "2", + "notionalCap": "50000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "3184590.0" + } + }, + { + "tier": 11.0, + "currency": "USDT", + "minNotional": 50000000.0, + "maxNotional": 100000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "10", + "bracket": "11", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "18000000", + "notionalCap": "100000000", + "notionalFloor": "50000000", "maintMarginRatio": "0.5", - "cum": "5496935.0" + "cum": "15684590.0" } } ], @@ -28959,104 +27677,6 @@ } } ], - "TLM/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, - "info": { - "bracket": "2", - "initialLeverage": "7", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386900.0" - } - } - ], "TLM/USDT:USDT": [ { "tier": 1.0, @@ -29404,13 +28024,13 @@ "tier": 2.0, "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "25000", + "notionalCap": "50000", "notionalFloor": "5000", "maintMarginRatio": "0.02", "cum": "25.0" @@ -29419,81 +28039,97 @@ { "tier": 3.0, "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 50000.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": "3", "initialLeverage": "20", - "notionalCap": "50000", - "notionalFloor": "25000", + "notionalCap": "100000", + "notionalFloor": "50000", "maintMarginRatio": "0.025", - "cum": "150.0" + "cum": "275.0" } }, { "tier": 4.0, "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, + "minNotional": 100000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "50000", + "notionalCap": "400000", + "notionalFloor": "100000", "maintMarginRatio": "0.05", - "cum": "1400.0" + "cum": "2775.0" } }, { "tier": 5.0, "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, + "minNotional": 400000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "notionalCap": "1000000", + "notionalFloor": "400000", "maintMarginRatio": "0.1", - "cum": "6400.0" + "cum": "22775.0" } }, { "tier": 6.0, "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 1000000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "6", - "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "initialLeverage": "4", + "notionalCap": "2000000", + "notionalFloor": "1000000", "maintMarginRatio": "0.125", - "cum": "12650.0" + "cum": "47775.0" } }, { "tier": 7.0, "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 3000000.0, + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "7", + "initialLeverage": "2", + "notionalCap": "4000000", + "notionalFloor": "2000000", + "maintMarginRatio": "0.25", + "cum": "297775.0" + } + }, + { + "tier": 8.0, + "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 8000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "7", + "bracket": "8", "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", + "notionalCap": "8000000", + "notionalFloor": "4000000", "maintMarginRatio": "0.5", - "cum": "387650.0" + "cum": "1297775.0" } } ], @@ -29611,120 +28247,6 @@ } } ], - "TRX/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": "5", - "initialLeverage": "4", - "notionalCap": "1500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 1500000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": "6", - "initialLeverage": "2", - "notionalCap": "3000000", - "notionalFloor": "1500000", - "maintMarginRatio": "0.25", - "cum": "199400.0" - } - }, - { - "tier": 7.0, - "currency": "BUSD", - "minNotional": 3000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "7", - "initialLeverage": "1", - "notionalCap": "4000000", - "notionalFloor": "3000000", - "maintMarginRatio": "0.5", - "cum": "949400.0" - } - } - ], "TRX/USDT:USDT": [ { "tier": 1.0, @@ -30154,10 +28676,10 @@ "minNotional": 250000.0, "maxNotional": 1000000.0, "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, + "maxLeverage": 4.0, "info": { "bracket": "4", - "initialLeverage": "2", + "initialLeverage": "4", "notionalCap": "1000000", "notionalFloor": "250000", "maintMarginRatio": "0.125", @@ -30168,114 +28690,32 @@ "tier": 5.0, "currency": "USDT", "minNotional": 1000000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "5", - "initialLeverage": "1", - "notionalCap": "3000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.5", - "cum": "386875.0" - } - } - ], - "UNI/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 8.0, - "info": { - "bracket": "1", - "initialLeverage": "8", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 7.0, - "info": { - "bracket": "2", - "initialLeverage": "7", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.125, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" + "notionalCap": "1500000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.25", + "cum": "136875.0" } }, { "tier": 6.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 1500000.0, + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "1500000", - "notionalFloor": "1000000", + "notionalCap": "3000000", + "notionalFloor": "1500000", "maintMarginRatio": "0.5", - "cum": "386900.0" + "cum": "511875.0" } } ], @@ -30799,104 +29239,6 @@ } } ], - "WAVES/BUSD:BUSD": [ - { - "tier": 1.0, - "currency": "BUSD", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 10.0, - "info": { - "bracket": "1", - "initialLeverage": "10", - "notionalCap": "5000", - "notionalFloor": "0", - "maintMarginRatio": "0.02", - "cum": "0.0" - } - }, - { - "tier": 2.0, - "currency": "BUSD", - "minNotional": 5000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 8.0, - "info": { - "bracket": "2", - "initialLeverage": "8", - "notionalCap": "25000", - "notionalFloor": "5000", - "maintMarginRatio": "0.025", - "cum": "25.0" - } - }, - { - "tier": 3.0, - "currency": "BUSD", - "minNotional": 25000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 6.0, - "info": { - "bracket": "3", - "initialLeverage": "6", - "notionalCap": "100000", - "notionalFloor": "25000", - "maintMarginRatio": "0.05", - "cum": "650.0" - } - }, - { - "tier": 4.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": "4", - "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", - "maintMarginRatio": "0.1", - "cum": "5650.0" - } - }, - { - "tier": 5.0, - "currency": "BUSD", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 2.0, - "info": { - "bracket": "5", - "initialLeverage": "2", - "notionalCap": "500000", - "notionalFloor": "250000", - "maintMarginRatio": "0.125", - "cum": "11900.0" - } - }, - { - "tier": 6.0, - "currency": "BUSD", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": "6", - "initialLeverage": "1", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.5", - "cum": "199400.0" - } - } - ], "WAVES/USDT:USDT": [ { "tier": 1.0, @@ -31125,6 +29467,120 @@ } } ], + "WIF/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "675.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "WLD/USDT:USDT": [ { "tier": 1.0, @@ -31353,6 +29809,120 @@ } } ], + "XAI/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "675.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "XEM/USDT:USDT": [ { "tier": 1.0, @@ -31727,101 +30297,165 @@ } } ], - "XRP/BUSD:BUSD": [ + "XRP/USDC:USDC": [ { "tier": 1.0, - "currency": "BUSD", + "currency": "USDC", "minNotional": 0.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 11.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 75.0, "info": { "bracket": "1", - "initialLeverage": "11", - "notionalCap": "100000", + "initialLeverage": "75", + "notionalCap": "5000", "notionalFloor": "0", - "maintMarginRatio": "0.025", + "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2.0, - "currency": "BUSD", - "minNotional": 100000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "currency": "USDC", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.006, + "maxLeverage": 50.0, "info": { "bracket": "2", - "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "100000", - "maintMarginRatio": "0.05", - "cum": "2500.0" + "initialLeverage": "50", + "notionalCap": "10000", + "notionalFloor": "5000", + "maintMarginRatio": "0.006", + "cum": "5.0" } }, { "tier": 3.0, - "currency": "BUSD", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "currency": "USDC", + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 40.0, "info": { "bracket": "3", - "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", - "maintMarginRatio": "0.1", - "cum": "27500.0" + "initialLeverage": "40", + "notionalCap": "50000", + "notionalFloor": "10000", + "maintMarginRatio": "0.01", + "cum": "45.0" } }, { "tier": 4.0, - "currency": "BUSD", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, + "currency": "USDC", + "minNotional": 50000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": "4", - "initialLeverage": "3", - "notionalCap": "2000000", - "notionalFloor": "1000000", - "maintMarginRatio": "0.15", - "cum": "77500.0" + "initialLeverage": "25", + "notionalCap": "750000", + "notionalFloor": "50000", + "maintMarginRatio": "0.02", + "cum": "545.0" } }, { "tier": 5.0, - "currency": "BUSD", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "currency": "USDC", + "minNotional": 750000.0, + "maxNotional": 3000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": "5", - "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2000000", - "maintMarginRatio": "0.25", - "cum": "277500.0" + "initialLeverage": "10", + "notionalCap": "3000000", + "notionalFloor": "750000", + "maintMarginRatio": "0.05", + "cum": "23045.0" } }, { "tier": 6.0, - "currency": "BUSD", - "minNotional": 5000000.0, - "maxNotional": 5500000.0, + "currency": "USDC", + "minNotional": 3000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "6", + "initialLeverage": "5", + "notionalCap": "10000000", + "notionalFloor": "3000000", + "maintMarginRatio": "0.1", + "cum": "173045.0" + } + }, + { + "tier": 7.0, + "currency": "USDC", + "minNotional": 10000000.0, + "maxNotional": 12000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "7", + "initialLeverage": "4", + "notionalCap": "12000000", + "notionalFloor": "10000000", + "maintMarginRatio": "0.125", + "cum": "423045.0" + } + }, + { + "tier": 8.0, + "currency": "USDC", + "minNotional": 12000000.0, + "maxNotional": 20000000.0, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3.0, + "info": { + "bracket": "8", + "initialLeverage": "3", + "notionalCap": "20000000", + "notionalFloor": "12000000", + "maintMarginRatio": "0.15", + "cum": "723045.0" + } + }, + { + "tier": 9.0, + "currency": "USDC", + "minNotional": 20000000.0, + "maxNotional": 30000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "9", + "initialLeverage": "2", + "notionalCap": "30000000", + "notionalFloor": "20000000", + "maintMarginRatio": "0.25", + "cum": "2723045.0" + } + }, + { + "tier": 10.0, + "currency": "USDC", + "minNotional": 30000000.0, + "maxNotional": 50000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": "6", + "bracket": "10", "initialLeverage": "1", - "notionalCap": "5500000", - "notionalFloor": "5000000", + "notionalCap": "50000000", + "notionalFloor": "30000000", "maintMarginRatio": "0.5", - "cum": "1527500.0" + "cum": "10223045.0" } } ], @@ -32769,6 +31403,120 @@ } } ], + "ZETA/USDT:USDT": [ + { + "tier": 1.0, + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": "1", + "initialLeverage": "50", + "notionalCap": "5000", + "notionalFloor": "0", + "maintMarginRatio": "0.015", + "cum": "0.0" + } + }, + { + "tier": 2.0, + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": "2", + "initialLeverage": "20", + "notionalCap": "25000", + "notionalFloor": "5000", + "maintMarginRatio": "0.025", + "cum": "50.0" + } + }, + { + "tier": 3.0, + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": "3", + "initialLeverage": "10", + "notionalCap": "100000", + "notionalFloor": "25000", + "maintMarginRatio": "0.05", + "cum": "675.0" + } + }, + { + "tier": 4.0, + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": "4", + "initialLeverage": "5", + "notionalCap": "200000", + "notionalFloor": "100000", + "maintMarginRatio": "0.1", + "cum": "5675.0" + } + }, + { + "tier": 5.0, + "currency": "USDT", + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": "5", + "initialLeverage": "4", + "notionalCap": "500000", + "notionalFloor": "200000", + "maintMarginRatio": "0.125", + "cum": "10675.0" + } + }, + { + "tier": 6.0, + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": "6", + "initialLeverage": "2", + "notionalCap": "1000000", + "notionalFloor": "500000", + "maintMarginRatio": "0.25", + "cum": "73175.0" + } + }, + { + "tier": 7.0, + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": "7", + "initialLeverage": "1", + "notionalCap": "2000000", + "notionalFloor": "1000000", + "maintMarginRatio": "0.5", + "cum": "323175.0" + } + } + ], "ZIL/USDT:USDT": [ { "tier": 1.0, From 0f9e8465873c8da64bc87ba603aab861e8d599e5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Feb 2024 13:31:24 +0100 Subject: [PATCH 122/247] Update Tests data to work with new cached levtiers --- tests/conftest.py | 4 ++-- tests/exchange/test_binance.py | 4 ++-- tests/exchange/test_exchange.py | 8 ++++---- tests/exchange/test_okx.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0cc3a8ea0..9c81c050d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3342,7 +3342,7 @@ def leverage_tiers(): 'maintAmt': 386950.0 }, ], - "ADA/BUSD:BUSD": [ + "ADA/USDT:USDT": [ { "minNotional": 0, "maxNotional": 100000, @@ -3386,7 +3386,7 @@ def leverage_tiers(): "maintAmt": 1527500.0 }, ], - 'BNB/BUSD:BUSD': [ + 'XRP/USDT:USDT': [ { "minNotional": 0, # stake(before leverage) = 0 "maxNotional": 100000, # max stake(before leverage) = 5000 diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index c4e657ad9..625033645 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -596,10 +596,10 @@ async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog, c @pytest.mark.parametrize('pair,nominal_value,mm_ratio,amt', [ - ("BNB/BUSD:BUSD", 0.0, 0.025, 0), + ("XRP/USDT:USDT", 0.0, 0.025, 0), ("BNB/USDT:USDT", 100.0, 0.0065, 0), ("BTC/USDT:USDT", 170.30, 0.004, 0), - ("BNB/BUSD:BUSD", 999999.9, 0.1, 27500.0), + ("XRP/USDT:USDT", 999999.9, 0.1, 27500.0), ("BNB/USDT:USDT", 5000000.0, 0.15, 233035.0), ("BTC/USDT:USDT", 600000000, 0.5, 1.997038E8), ]) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 29e458cdd..fc199a7f5 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -4969,8 +4969,8 @@ def test_get_maintenance_ratio_and_amt_exceptions(mocker, default_conf, leverage @pytest.mark.parametrize('pair,value,mmr,maintAmt', [ - ('ADA/BUSD:BUSD', 500, 0.025, 0.0), - ('ADA/BUSD:BUSD', 20000000, 0.5, 1527500.0), + ('ADA/USDT:USDT', 500, 0.025, 0.0), + ('ADA/USDT:USDT', 20000000, 0.5, 1527500.0), ('ZEC/USDT:USDT', 500, 0.01, 0.0), ('ZEC/USDT:USDT', 20000000, 0.5, 654500.0), ]) @@ -5005,10 +5005,10 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers): exchange._leverage_tiers = leverage_tiers - assert exchange.get_max_leverage("BNB/BUSD:BUSD", 1.0) == 20.0 + assert exchange.get_max_leverage("XRP/USDT:USDT", 1.0) == 20.0 assert exchange.get_max_leverage("BNB/USDT:USDT", 100.0) == 75.0 assert exchange.get_max_leverage("BTC/USDT:USDT", 170.30) == 125.0 - assert pytest.approx(exchange.get_max_leverage("BNB/BUSD:BUSD", 99999.9)) == 5.000005 + assert pytest.approx(exchange.get_max_leverage("XRP/USDT:USDT", 99999.9)) == 5.000005 assert pytest.approx(exchange.get_max_leverage("BNB/USDT:USDT", 1500)) == 33.333333333333333 assert exchange.get_max_leverage("BTC/USDT:USDT", 300000000) == 2.0 assert exchange.get_max_leverage("BTC/USDT:USDT", 600000000) == 1.0 # Last tier diff --git a/tests/exchange/test_okx.py b/tests/exchange/test_okx.py index fe9ab3c18..73f87774e 100644 --- a/tests/exchange/test_okx.py +++ b/tests/exchange/test_okx.py @@ -196,7 +196,7 @@ def test_get_max_pair_stake_amount_okx(default_conf, mocker, leverage_tiers): exchange = get_patched_exchange(mocker, default_conf, id="okx") exchange._leverage_tiers = leverage_tiers - assert exchange.get_max_pair_stake_amount('BNB/BUSD:BUSD', 1.0) == 30000000 + assert exchange.get_max_pair_stake_amount('XRP/USDT:USDT', 1.0) == 30000000 assert exchange.get_max_pair_stake_amount('BNB/USDT:USDT', 1.0) == 50000000 assert exchange.get_max_pair_stake_amount('BTC/USDT:USDT', 1.0) == 1000000000 assert exchange.get_max_pair_stake_amount('BTC/USDT:USDT', 1.0, 10.0) == 100000000 From 35e1421d5989369ae6d700cae7113e6536f7eab0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Feb 2024 13:42:25 +0100 Subject: [PATCH 123/247] Reduce whitespace --- tests/freqtradebot/test_freqtradebot.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py index ca6f29078..aa037fe37 100644 --- a/tests/freqtradebot/test_freqtradebot.py +++ b/tests/freqtradebot/test_freqtradebot.py @@ -2923,9 +2923,6 @@ def test_execute_trade_exit_custom_exit_price( } == last_msg - - - @pytest.mark.parametrize( "is_short,amount,current_rate,limit,profit_amount,profit_ratio,profit_or_loss", [ (False, 30, 2.3, 2.2, 5.685, 0.09451372, 'profit'), From cb2aaa7bbb54c14de0015e6083967817a117a42f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Feb 2024 15:00:48 +0100 Subject: [PATCH 124/247] Fix wording fully. --- freqtrade/freqai/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqai/utils.py b/freqtrade/freqai/utils.py index bc335bf20..22d75bc16 100644 --- a/freqtrade/freqai/utils.py +++ b/freqtrade/freqai/utils.py @@ -123,7 +123,7 @@ def plot_feature_importance(model: Any, pair: str, dk: FreqaiDataKitchen, elif "xgb" in str(mdl.__class__): feature_importance = mdl.feature_importances_ else: - logger.info('Model type does not support for generating feature importances.') + logger.info('Model type does not support generating feature importances.') return # Data preparation From 69611826808e8411a40e14cc1de847fc81170a99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 03:50:09 +0000 Subject: [PATCH 125/247] Bump uvicorn from 0.27.0 to 0.27.0.post1 Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.27.0 to 0.27.0.post1. - [Release notes](https://github.com/encode/uvicorn/releases) - [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md) - [Commits](https://github.com/encode/uvicorn/compare/0.27.0...0.27.0.post1) --- updated-dependencies: - dependency-name: uvicorn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 535d94946..cdd09c200 100644 --- a/requirements.txt +++ b/requirements.txt @@ -38,7 +38,7 @@ sdnotify==0.3.2 # API Server fastapi==0.109.0 pydantic==2.5.3 -uvicorn==0.27.0 +uvicorn==0.27.0.post1 pyjwt==2.8.0 aiofiles==23.2.1 psutil==5.9.8 From ab7364c62d9ef1582d8ad49e4abd41ef199717d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 03:50:36 +0000 Subject: [PATCH 126/247] Bump aiohttp from 3.9.2 to 3.9.3 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.9.2 to 3.9.3. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.2...v3.9.3) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 535d94946..e11834d3f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pandas-ta==0.3.14b ccxt==4.2.25 cryptography==42.0.1 -aiohttp==3.9.2 +aiohttp==3.9.3 SQLAlchemy==2.0.25 python-telegram-bot==20.7 # can't be hard-pinned due to telegram-bot pinning httpx with ~ From 440382ae69cacb2317076533bb942d2a73145a97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 03:51:00 +0000 Subject: [PATCH 127/247] Bump ccxt from 4.2.25 to 4.2.35 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.2.25 to 4.2.35. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.2.25...4.2.35) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 535d94946..5be77a149 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.26.3 pandas==2.1.4 pandas-ta==0.3.14b -ccxt==4.2.25 +ccxt==4.2.35 cryptography==42.0.1 aiohttp==3.9.2 SQLAlchemy==2.0.25 From 5114be42cd5450b08cb531250db7fd6e756a2552 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 03:51:09 +0000 Subject: [PATCH 128/247] Bump urllib3 from 2.1.0 to 2.2.0 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.1.0...2.2.0) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 535d94946..5dee1f9c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ httpx>=0.24.1 arrow==1.3.0 cachetools==5.3.2 requests==2.31.0 -urllib3==2.1.0 +urllib3==2.2.0 jsonschema==4.21.1 TA-Lib==0.4.28 technical==1.4.2 From 87e813a3ddc916fd13c825ec72b808f1631d273a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 03:51:20 +0000 Subject: [PATCH 129/247] Bump mkdocs-material from 9.5.6 to 9.5.7 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.6 to 9.5.7. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.6...9.5.7) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index d6d2c29cc..c7f1e1889 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.5.2 mkdocs==1.5.3 -mkdocs-material==9.5.6 +mkdocs-material==9.5.7 mdx_truly_sane_lists==1.3 pymdown-extensions==10.7 jinja2==3.1.3 From e773276ddea5f30f38c57212f9441d12cca80887 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 03:51:36 +0000 Subject: [PATCH 130/247] Bump ruff from 0.1.15 to 0.2.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.15 to 0.2.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.1.15...v0.2.0) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 04d4a8563..1353d4fd0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==3.3.1 -ruff==0.1.15 +ruff==0.2.0 mypy==1.8.0 pre-commit==3.6.0 pytest==7.4.4 From 667a8cc59fdf390d92ef94451aeafdce7ab6b11f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 03:59:04 +0000 Subject: [PATCH 131/247] Bump peter-evans/create-pull-request from 5 to 6 Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 5 to 6. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/v5...v6) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/pre-commit-update.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml index bd50a9c3c..13188af14 100644 --- a/.github/workflows/pre-commit-update.yml +++ b/.github/workflows/pre-commit-update.yml @@ -30,7 +30,7 @@ jobs: - name: Run pre-commit run: pre-commit run --all-files - - uses: peter-evans/create-pull-request@v5 + - uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.REPO_SCOPED_TOKEN }} add-paths: .pre-commit-config.yaml From a675d2b026808ed6f2a2e39b3af1efe87d3692e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 06:02:59 +0000 Subject: [PATCH 132/247] Bump cryptography from 42.0.1 to 42.0.2 Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.1 to 42.0.2. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.1...42.0.2) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 77aa3e722..ce5068720 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ pandas==2.1.4 pandas-ta==0.3.14b ccxt==4.2.35 -cryptography==42.0.1 +cryptography==42.0.2 aiohttp==3.9.3 SQLAlchemy==2.0.25 python-telegram-bot==20.7 From 646aca7a36eb497711f59eee77c88060b7975d95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 08:20:20 +0000 Subject: [PATCH 133/247] Bump fastapi from 0.109.0 to 0.109.2 Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.109.0 to 0.109.2. - [Release notes](https://github.com/tiangolo/fastapi/releases) - [Commits](https://github.com/tiangolo/fastapi/compare/0.109.0...0.109.2) --- updated-dependencies: - dependency-name: fastapi dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ce5068720..3daeed7a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -36,7 +36,7 @@ orjson==3.9.12 sdnotify==0.3.2 # API Server -fastapi==0.109.0 +fastapi==0.109.2 pydantic==2.5.3 uvicorn==0.27.0.post1 pyjwt==2.8.0 From b9245aef2d865c1b1e60cdaf3f57ae68edb90550 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 08:21:44 +0000 Subject: [PATCH 134/247] Bump orjson from 3.9.12 to 3.9.13 Bumps [orjson](https://github.com/ijl/orjson) from 3.9.12 to 3.9.13. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.9.12...3.9.13) --- updated-dependencies: - dependency-name: orjson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ce5068720..19a2a4cd7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ py_find_1st==1.1.6 # Load ticker files 30% faster python-rapidjson==1.14 # Properly format api responses -orjson==3.9.12 +orjson==3.9.13 # Notify systemd sdnotify==0.3.2 From b4f030fd55037257ab7ee8edc1c9100e48c53738 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:18:28 +0000 Subject: [PATCH 135/247] Bump pydantic from 2.5.3 to 2.6.1 Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.5.3 to 2.6.1. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v2.5.3...v2.6.1) --- updated-dependencies: - dependency-name: pydantic dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3daeed7a0..d2813ff19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ sdnotify==0.3.2 # API Server fastapi==0.109.2 -pydantic==2.5.3 +pydantic==2.6.1 uvicorn==0.27.0.post1 pyjwt==2.8.0 aiofiles==23.2.1 From 73e2e034aeada27b38faf101f03685cbb1fcb0a7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Feb 2024 18:22:49 +0100 Subject: [PATCH 136/247] Remove unused argument --- tests/optimize/test_backtest_detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 82c036e07..71cb8ff34 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -900,7 +900,7 @@ TESTS = [ @pytest.mark.parametrize("data", TESTS) -def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer) -> None: +def test_backtest_results(default_conf, mocker, caplog, data: BTContainer) -> None: """ run functional tests """ From 4aef5676d7db4dd4b8a31b0f1e31079648000dc6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Feb 2024 18:10:11 +0100 Subject: [PATCH 137/247] update tool.ruff configuration to match 2.0 version --- pyproject.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1d8d9420d..753f44262 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -115,6 +115,8 @@ ignore = ["freqtrade/vendor/**"] line-length = 100 extend-exclude = [".env", ".venv"] target-version = "py38" + +[tool.ruff.lint] # Exclude UP036 as it's causing the "exit if < 3.9" to fail. extend-select = [ "C90", # mccabe @@ -132,16 +134,17 @@ extend-select = [ # "TCH", # flake8-type-checking "PTH", # flake8-use-pathlib ] + extend-ignore = [ "E241", # Multiple spaces after comma "E272", # Multiple spaces before keyword "E221", # Multiple spaces before operator ] -[tool.ruff.mccabe] +[tool.ruff.lint.mccabe] max-complexity = 12 -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "tests/*" = ["S"] [tool.flake8] From 8d02504072df87059db3180bc955886fa3b43f1e Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Tue, 6 Feb 2024 03:03:17 +0000 Subject: [PATCH 138/247] chore: update pre-commit hooks --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 979fbe083..5a37634ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.1.15' + rev: 'v0.2.1' hooks: - id: ruff From e50fac34a506867246b2b49a5bc4d1d2adf2cf35 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Feb 2024 06:35:46 +0100 Subject: [PATCH 139/247] Bump technical to 1.4.3 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 67b89e19b..15226b477 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ requests==2.31.0 urllib3==2.2.0 jsonschema==4.21.1 TA-Lib==0.4.28 -technical==1.4.2 +technical==1.4.3 tabulate==0.9.0 pycoingecko==3.1.0 jinja2==3.1.3 From d2e9d36dea3bb0407926ba72d6ca3267c7297046 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Feb 2024 18:15:17 +0100 Subject: [PATCH 140/247] chore: Update ordering of requirements in setup.py --- setup.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 64b30ed94..3b92b9dd7 100644 --- a/setup.py +++ b/setup.py @@ -35,21 +35,21 @@ hdf5 = [ develop = [ 'coveralls', + 'isort', 'mypy', - 'ruff', 'pre-commit', - 'pytest', 'pytest-asyncio', 'pytest-cov', 'pytest-mock', 'pytest-random-order', - 'isort', + 'pytest', + 'ruff', 'time-machine', 'types-cachetools', 'types-filelock', + 'types-python-dateutil' 'types-requests', 'types-tabulate', - 'types-python-dateutil' ] jupyter = [ @@ -76,8 +76,11 @@ setup( 'arrow>=1.0.0', 'cachetools', 'requests', + 'httpx>=0.24.1', 'urllib3', 'jsonschema', + 'numpy', + 'pandas', 'TA-Lib', 'pandas-ta', 'technical', @@ -86,29 +89,26 @@ setup( 'py_find_1st', 'python-rapidjson', 'orjson', - 'sdnotify', 'colorama', 'jinja2', 'questionary', 'prompt-toolkit', - 'numpy', - 'pandas', 'joblib>=1.2.0', 'rich', 'pyarrow; platform_machine != "armv7l"', 'fastapi', 'pydantic>=2.2.0', + 'pyjwt', + 'websockets', 'uvicorn', 'psutil', - 'pyjwt', - 'aiofiles', 'schedule', - 'websockets', 'janus', 'ast-comments', + 'aiofiles', 'aiohttp', 'cryptography', - 'httpx>=0.24.1', + 'sdnotify', 'python-dateutil', 'packaging', ], From 43bab85b85c5db8d0c635df5ffd473128d0939ac Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Wed, 7 Feb 2024 11:21:32 +0900 Subject: [PATCH 141/247] fix strategy-updater docs --- docs/utils.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index b4432833d..4bdb88cbd 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -990,11 +990,7 @@ options: -h, --help show this help message and exit --strategy-list STRATEGY_LIST [STRATEGY_LIST ...] Provide a space-separated list of strategies to - backtest. Please note that timeframe needs to be set - either in config or via command line. When using this - together with `--export trades`, the strategy-name is - injected into the filename (so `backtest-data.json` - becomes `backtest-data-SampleStrategy.json` + be converted. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). From 3e5a572fc61c18b5533aa6f0f5d71e7090d971b2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Feb 2024 07:14:40 +0100 Subject: [PATCH 142/247] Allow int as trade-id parameter closes #9780 --- freqtrade/rpc/api_server/api_schemas.py | 2 +- freqtrade/rpc/api_server/api_v1.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index af3e84873..9919d1a05 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -397,7 +397,7 @@ class ForceEnterPayload(BaseModel): class ForceExitPayload(BaseModel): - tradeid: str + tradeid: Union[str, int] ordertype: Optional[OrderTypeValues] = None amount: Optional[float] = None diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 4f4aac32c..99fc3d451 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -215,7 +215,7 @@ def force_entry(payload: ForceEnterPayload, rpc: RPC = Depends(get_rpc)): @router.post('/forcesell', response_model=ResultMsg, tags=['trading']) def forceexit(payload: ForceExitPayload, rpc: RPC = Depends(get_rpc)): ordertype = payload.ordertype.value if payload.ordertype else None - return rpc._rpc_force_exit(payload.tradeid, ordertype, amount=payload.amount) + return rpc._rpc_force_exit(str(payload.tradeid), ordertype, amount=payload.amount) @router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist']) From bf1f08cd21c0b15d3b8a51d49518e4bef86e72cf Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 8 Feb 2024 18:03:49 +0100 Subject: [PATCH 143/247] chore: add dependency groups for regular updates, reschedule to run at 3am UTC (hope this works now ...) --- .github/dependabot.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index dfbc0cee7..8c9a3f936 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,8 +10,17 @@ updates: directory: "/" schedule: interval: weekly + time: "03:00" + timezone: "Etc/UTC" open-pull-requests-limit: 15 target-branch: develop + groups: + types: + patterns: + - "types-*" + pytest: + patterns: + - "pytest*" - package-ecosystem: "github-actions" directory: "/" From 7223a6c504b5f2141a7ca841fe9a3267d007e764 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:04:19 +0000 Subject: [PATCH 144/247] Bump pre-commit/action from 3.0.0 to 3.0.1 Bumps [pre-commit/action](https://github.com/pre-commit/action) from 3.0.0 to 3.0.1. - [Release notes](https://github.com/pre-commit/action/releases) - [Commits](https://github.com/pre-commit/action/compare/v3.0.0...v3.0.1) --- updated-dependencies: - dependency-name: pre-commit/action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8a261d0e..ba55eed04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -325,7 +325,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.10" - - uses: pre-commit/action@v3.0.0 + - uses: pre-commit/action@v3.0.1 docs-check: runs-on: ubuntu-22.04 From a0c246fa9a14738b129f7f68b2dc09afc47c7736 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:05:39 +0000 Subject: [PATCH 145/247] Bump ccxt from 4.2.35 to 4.2.39 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.2.35 to 4.2.39. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.2.35...4.2.39) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 15226b477..e094081af 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.26.3 pandas==2.1.4 pandas-ta==0.3.14b -ccxt==4.2.35 +ccxt==4.2.39 cryptography==42.0.2 aiohttp==3.9.3 SQLAlchemy==2.0.25 From 68fd7d7ffd2ece3c93d9f7bc94e8349f58be3ef4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:05:44 +0000 Subject: [PATCH 146/247] Bump mkdocs-material from 9.5.7 to 9.5.8 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.7 to 9.5.8. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.7...9.5.8) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index c7f1e1889..ddcc951d0 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.5.2 mkdocs==1.5.3 -mkdocs-material==9.5.7 +mkdocs-material==9.5.8 mdx_truly_sane_lists==1.3 pymdown-extensions==10.7 jinja2==3.1.3 From 9f449dd34729e994f016dd16183f4eebb2cf2c2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:06:05 +0000 Subject: [PATCH 147/247] Bump ruff from 0.2.0 to 0.2.1 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.2.0 to 0.2.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.2.0...v0.2.1) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1353d4fd0..155f04376 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==3.3.1 -ruff==0.2.0 +ruff==0.2.1 mypy==1.8.0 pre-commit==3.6.0 pytest==7.4.4 From 01a00ad048e05297d5479e6280e2918163dee9c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:06:26 +0000 Subject: [PATCH 148/247] Bump nbconvert from 7.14.2 to 7.16.0 Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.14.2 to 7.16.0. - [Release notes](https://github.com/jupyter/nbconvert/releases) - [Changelog](https://github.com/jupyter/nbconvert/blob/main/CHANGELOG.md) - [Commits](https://github.com/jupyter/nbconvert/compare/v7.14.2...v7.16.0) --- updated-dependencies: - dependency-name: nbconvert dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1353d4fd0..7f2d00e7f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -21,7 +21,7 @@ isort==5.13.2 time-machine==2.13.0 # Convert jupyter notebooks to markdown documents -nbconvert==7.14.2 +nbconvert==7.16.0 # mypy types types-cachetools==5.3.0.7 From ce6b62f86571e5f97a52ccb5e6d6b51d6e9e07ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Feb 2024 18:26:56 +0000 Subject: [PATCH 149/247] Bump numpy from 1.26.3 to 1.26.4 Bumps [numpy](https://github.com/numpy/numpy) from 1.26.3 to 1.26.4. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v1.26.3...v1.26.4) --- updated-dependencies: - dependency-name: numpy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e094081af..826f49c56 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.26.3 +numpy==1.26.4 pandas==2.1.4 pandas-ta==0.3.14b From adf63a45cfea3c6f38c112cda60c67efb29edfa7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Feb 2024 19:35:16 +0100 Subject: [PATCH 150/247] Align new-config documentation examples closes #9790 --- docs/configuration.md | 2 +- docs/utils.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 202fa49bf..2fc54668a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -14,7 +14,7 @@ You can specify a different configuration file used by the bot with the `-c/--co If you used the [Quick start](docker_quickstart.md#docker-quick-start) method for installing the bot, the installation script should have already created the default configuration file (`config.json`) for you. -If the default configuration file is not created we recommend to use `freqtrade new-config --config config.json` to generate a basic configuration file. +If the default configuration file is not created we recommend to use `freqtrade new-config --config user_data/config.json` to generate a basic configuration file. The Freqtrade configuration file is to be written in JSON format. diff --git a/docs/utils.md b/docs/utils.md index 4bdb88cbd..202526afe 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -54,7 +54,7 @@ optional arguments: ### Create config examples ``` -$ freqtrade new-config --config config_binance.json +$ freqtrade new-config --config user_data/config_binance.json ? Do you want to enable Dry-run (simulated trades)? Yes ? Please insert your stake currency: BTC From f89147fd44d53a81120d97e3d8878d9972b1fbd9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 10 Feb 2024 08:44:16 +0100 Subject: [PATCH 151/247] Allow limiting kraken pairs to `--pairs` selection --- freqtrade/data/converter/trade_converter_kraken.py | 7 +++++++ tests/data/test_trade_converter_kraken.py | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/freqtrade/data/converter/trade_converter_kraken.py b/freqtrade/data/converter/trade_converter_kraken.py index 5abebd6a2..71a6dd8d3 100644 --- a/freqtrade/data/converter/trade_converter_kraken.py +++ b/freqtrade/data/converter/trade_converter_kraken.py @@ -38,6 +38,13 @@ def import_kraken_trades_from_csv(config: Config, convert_to: str): } logger.info(f"Found csv files for {', '.join(data_symbols)}.") + if pairs := config.get('pairs'): + markets = [m for m in markets if m[0] in pairs] + if not markets: + logger.info(f"No data found for pairs {', '.join(pairs)}.") + return + logger.info(f"Converting pairs: {', '.join(m[0] for m in markets)}.") + for pair, name in markets: dfs = [] # Load and combine all csv files for this pair diff --git a/tests/data/test_trade_converter_kraken.py b/tests/data/test_trade_converter_kraken.py index bb44062bf..91de303fb 100644 --- a/tests/data/test_trade_converter_kraken.py +++ b/tests/data/test_trade_converter_kraken.py @@ -34,6 +34,7 @@ def test_import_kraken_trades_from_csv(testdatadir, tmp_path, caplog, default_co import_kraken_trades_from_csv(default_conf_usdt, 'feather') assert log_has("Found csv files for BCHEUR.", caplog) + assert log_has("Converting pairs: BCH/EUR.", caplog) assert log_has_re(r"BCH/EUR: 340 trades.* 2023-01-01.* 2023-01-02.*", caplog) assert dstfile.is_file() @@ -48,3 +49,10 @@ def test_import_kraken_trades_from_csv(testdatadir, tmp_path, caplog, default_co tzinfo=timezone.utc) # ID is not filled assert len(trades.loc[trades['id'] != '']) == 0 + + caplog.clear() + default_conf_usdt['pairs'] = ['XRP/EUR'] + # Filtered to non-existing pair + import_kraken_trades_from_csv(default_conf_usdt, 'feather') + assert log_has("Found csv files for BCHEUR.", caplog) + assert log_has("No data found for pairs XRP/EUR.", caplog) From a83b5abb51ac5dba8dfb11860a79b33197aaabdd Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 10 Feb 2024 08:48:52 +0100 Subject: [PATCH 152/247] Allow wildcards for pairs filter --- freqtrade/data/converter/trade_converter_kraken.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/converter/trade_converter_kraken.py b/freqtrade/data/converter/trade_converter_kraken.py index 71a6dd8d3..ec5b265f7 100644 --- a/freqtrade/data/converter/trade_converter_kraken.py +++ b/freqtrade/data/converter/trade_converter_kraken.py @@ -8,6 +8,7 @@ from freqtrade.data.converter.trade_converter import (trades_convert_types, trades_df_remove_duplicates) from freqtrade.data.history.idatahandler import get_datahandler from freqtrade.exceptions import OperationalException +from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.resolvers import ExchangeResolver @@ -38,10 +39,11 @@ def import_kraken_trades_from_csv(config: Config, convert_to: str): } logger.info(f"Found csv files for {', '.join(data_symbols)}.") - if pairs := config.get('pairs'): + if pairs_raw := config.get('pairs'): + pairs = expand_pairlist(pairs_raw, [m[0] for m in markets]) markets = [m for m in markets if m[0] in pairs] if not markets: - logger.info(f"No data found for pairs {', '.join(pairs)}.") + logger.info(f"No data found for pairs {', '.join(pairs_raw)}.") return logger.info(f"Converting pairs: {', '.join(m[0] for m in markets)}.") From 3aa2d0c30a0ac641ca7369b7ef1b96370d8176c4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 10 Feb 2024 09:37:59 +0100 Subject: [PATCH 153/247] Slightly improve memory-usage of kraken-convert --- freqtrade/data/converter/trade_converter_kraken.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/freqtrade/data/converter/trade_converter_kraken.py b/freqtrade/data/converter/trade_converter_kraken.py index ec5b265f7..614d97b2a 100644 --- a/freqtrade/data/converter/trade_converter_kraken.py +++ b/freqtrade/data/converter/trade_converter_kraken.py @@ -41,7 +41,7 @@ def import_kraken_trades_from_csv(config: Config, convert_to: str): if pairs_raw := config.get('pairs'): pairs = expand_pairlist(pairs_raw, [m[0] for m in markets]) - markets = [m for m in markets if m[0] in pairs] + markets = {m for m in markets if m[0] in pairs} if not markets: logger.info(f"No data found for pairs {', '.join(pairs_raw)}.") return @@ -61,17 +61,18 @@ def import_kraken_trades_from_csv(config: Config, convert_to: str): continue trades = pd.concat(dfs, ignore_index=True) + del dfs trades.loc[:, 'timestamp'] = trades['timestamp'] * 1e3 trades.loc[:, 'cost'] = trades['price'] * trades['amount'] for col in DEFAULT_TRADES_COLUMNS: if col not in trades.columns: - trades[col] = '' - + trades.loc[:, col] = '' trades = trades[DEFAULT_TRADES_COLUMNS] trades = trades_convert_types(trades) trades_df = trades_df_remove_duplicates(trades) + del trades logger.info(f"{pair}: {len(trades_df)} trades, from " f"{trades_df['date'].min():{DATETIME_PRINT_FORMAT}} to " f"{trades_df['date'].max():{DATETIME_PRINT_FORMAT}}") From f10c8f9c3bff2d8990b7bc88d4bb3254a6c66776 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 03:29:17 +0000 Subject: [PATCH 154/247] Bump uvicorn from 0.27.0.post1 to 0.27.1 Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.27.0.post1 to 0.27.1. - [Release notes](https://github.com/encode/uvicorn/releases) - [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md) - [Commits](https://github.com/encode/uvicorn/compare/0.27.0.post1...0.27.1) --- updated-dependencies: - dependency-name: uvicorn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 826f49c56..1bfd431b6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -38,7 +38,7 @@ sdnotify==0.3.2 # API Server fastapi==0.109.2 pydantic==2.6.1 -uvicorn==0.27.0.post1 +uvicorn==0.27.1 pyjwt==2.8.0 aiofiles==23.2.1 psutil==5.9.8 From d5aec91a41661e1d28239639236d874a1271dd9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 03:29:27 +0000 Subject: [PATCH 155/247] Bump pre-commit from 3.6.0 to 3.6.1 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.6.0 to 3.6.1. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v3.6.0...v3.6.1) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c5cf7e654..0f41482de 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,7 +9,7 @@ coveralls==3.3.1 ruff==0.2.1 mypy==1.8.0 -pre-commit==3.6.0 +pre-commit==3.6.1 pytest==7.4.4 pytest-asyncio==0.23.4 pytest-cov==4.1.0 From 7e8e36c6be67cbccdb8d817c8786bbefdf206a07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 03:29:40 +0000 Subject: [PATCH 156/247] Bump ccxt from 4.2.39 to 4.2.42 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.2.39 to 4.2.42. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.2.39...4.2.42) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 826f49c56..bbcf13e8e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.26.4 pandas==2.1.4 pandas-ta==0.3.14b -ccxt==4.2.39 +ccxt==4.2.42 cryptography==42.0.2 aiohttp==3.9.3 SQLAlchemy==2.0.25 From 7d74a4c1936b83c437471ae4c2401f305c58f96c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 03:29:45 +0000 Subject: [PATCH 157/247] Bump tqdm from 4.66.1 to 4.66.2 Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.1 to 4.66.2. - [Release notes](https://github.com/tqdm/tqdm/releases) - [Commits](https://github.com/tqdm/tqdm/compare/v4.66.1...v4.66.2) --- updated-dependencies: - dependency-name: tqdm dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-freqai-rl.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index fa5e9f014..67fed9190 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -8,4 +8,4 @@ gymnasium==0.29.1; python_version < '3.12' stable_baselines3==2.2.1; python_version < '3.12' sb3_contrib>=2.0.0a9; python_version < '3.12' # Progress bar for stable-baselines3 and sb3-contrib -tqdm==4.66.1 +tqdm==4.66.2 From 903ae336794487be8cf99d1ff43c4462bb172a99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 03:29:52 +0000 Subject: [PATCH 158/247] Bump mkdocs-material from 9.5.8 to 9.5.9 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.8 to 9.5.9. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.8...9.5.9) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index ddcc951d0..aca3da72a 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.5.2 mkdocs==1.5.3 -mkdocs-material==9.5.8 +mkdocs-material==9.5.9 mdx_truly_sane_lists==1.3 pymdown-extensions==10.7 jinja2==3.1.3 From 8ca905b45fd713cf41a0a68f63f29cb28845cb45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 03:30:00 +0000 Subject: [PATCH 159/247] Bump python-telegram-bot from 20.7 to 20.8 Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 20.7 to 20.8. - [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases) - [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst) - [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v20.7...v20.8) --- updated-dependencies: - dependency-name: python-telegram-bot dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 826f49c56..df091982f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ ccxt==4.2.39 cryptography==42.0.2 aiohttp==3.9.3 SQLAlchemy==2.0.25 -python-telegram-bot==20.7 +python-telegram-bot==20.8 # can't be hard-pinned due to telegram-bot pinning httpx with ~ httpx>=0.24.1 arrow==1.3.0 From 2c1ea2d2568492374d237e56c6138bf60c586f7b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 03:30:19 +0000 Subject: [PATCH 160/247] Bump tensorboard from 2.15.1 to 2.15.2 Bumps [tensorboard](https://github.com/tensorflow/tensorboard) from 2.15.1 to 2.15.2. - [Release notes](https://github.com/tensorflow/tensorboard/releases) - [Changelog](https://github.com/tensorflow/tensorboard/blob/2.15.2/RELEASE.md) - [Commits](https://github.com/tensorflow/tensorboard/compare/2.15.1...2.15.2) --- updated-dependencies: - dependency-name: tensorboard dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-freqai.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index 2d33efc3c..848b6d920 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -8,5 +8,5 @@ joblib==1.3.2 catboost==1.2.2; 'arm' not in platform_machine and python_version < '3.12' lightgbm==4.3.0 xgboost==2.0.3 -tensorboard==2.15.1 +tensorboard==2.15.2 datasieve==0.1.7 From 0ba27ddee61cd33895870758aa1434e67321693a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 05:04:43 +0000 Subject: [PATCH 161/247] Bump the pytest group with 2 updates Bumps the pytest group with 2 updates: [pytest](https://github.com/pytest-dev/pytest) and [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio). Updates `pytest` from 7.4.4 to 8.0.0 - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.4.4...8.0.0) Updates `pytest-asyncio` from 0.23.4 to 0.23.5 - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.23.4...v0.23.5) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-major dependency-group: pytest - dependency-name: pytest-asyncio dependency-type: direct:development update-type: version-update:semver-patch dependency-group: pytest ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0f41482de..f0095bffa 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,8 +10,8 @@ coveralls==3.3.1 ruff==0.2.1 mypy==1.8.0 pre-commit==3.6.1 -pytest==7.4.4 -pytest-asyncio==0.23.4 +pytest==8.0.0 +pytest-asyncio==0.23.5 pytest-cov==4.1.0 pytest-mock==3.12.0 pytest-random-order==1.1.1 From 0740a1339358517838aa7b01f9472e251fc93006 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 05:18:09 +0000 Subject: [PATCH 162/247] Bump sqlalchemy from 2.0.25 to 2.0.26 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.25 to 2.0.26. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 57d570be4..c9b26eab6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pandas-ta==0.3.14b ccxt==4.2.42 cryptography==42.0.2 aiohttp==3.9.3 -SQLAlchemy==2.0.25 +SQLAlchemy==2.0.26 python-telegram-bot==20.7 # can't be hard-pinned due to telegram-bot pinning httpx with ~ httpx>=0.24.1 From fd737af69d958f419f6e9217e1c2e369ca5e873d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Feb 2024 06:29:42 +0100 Subject: [PATCH 163/247] use query.edit_message_text to simplify telegram class --- freqtrade/rpc/telegram.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index f42e3ab51..f9a0635f0 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1777,13 +1777,9 @@ class Telegram(RPCHandler): msg += f"\nUpdated: {datetime.now().ctime()}" if not query.message: return - chat_id = query.message.chat_id - message_id = query.message.message_id try: - await self._app.bot.edit_message_text( - chat_id=chat_id, - message_id=message_id, + await query.edit_message_text( text=msg, parse_mode=parse_mode, reply_markup=reply_markup From 72290365db735f01ca6d85f1c35b92d38ba19ca8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Feb 2024 06:31:39 +0100 Subject: [PATCH 164/247] telegram: ensure msg update test uses the correct edit_message_text call --- tests/rpc/test_rpc_telegram.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 2e6852876..3c683d7b3 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -2557,22 +2557,22 @@ async def test_telegram__send_msg(default_conf, mocker, caplog) -> None: # Test update query = MagicMock() + query.edit_message_text = AsyncMock() await telegram._send_msg('test', callback_path="DeadBeef", query=query, reload_able=True) - edit_message_text = telegram._app.bot.edit_message_text - assert edit_message_text.call_count == 1 - assert "Updated: " in edit_message_text.call_args_list[0][1]['text'] + assert query.edit_message_text.call_count == 1 + assert "Updated: " in query.edit_message_text.call_args_list[0][1]['text'] - telegram._app.bot.edit_message_text = AsyncMock(side_effect=BadRequest("not modified")) + query.edit_message_text = AsyncMock(side_effect=BadRequest("not modified")) await telegram._send_msg('test', callback_path="DeadBeef", query=query) - assert telegram._app.bot.edit_message_text.call_count == 1 + assert query.edit_message_text.call_count == 1 assert not log_has_re(r"TelegramError: .*", caplog) - telegram._app.bot.edit_message_text = AsyncMock(side_effect=BadRequest("")) + query.edit_message_text = AsyncMock(side_effect=BadRequest("")) await telegram._send_msg('test2', callback_path="DeadBeef", query=query) - assert telegram._app.bot.edit_message_text.call_count == 1 + assert query.edit_message_text.call_count == 1 assert log_has_re(r"TelegramError: .*", caplog) - telegram._app.bot.edit_message_text = AsyncMock(side_effect=TelegramError("DeadBEEF")) + query.edit_message_text = AsyncMock(side_effect=TelegramError("DeadBEEF")) await telegram._send_msg('test3', callback_path="DeadBeef", query=query) assert log_has_re(r"TelegramError: DeadBEEF! Giving up.*", caplog) From f2a7312010d39bce3fac1cc423c5263984c833a8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Feb 2024 06:34:45 +0100 Subject: [PATCH 165/247] Update sqlalchemy pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a37634ff..a1aa00f07 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - types-requests==2.31.0.20240125 - types-tabulate==0.9.0.20240106 - types-python-dateutil==2.8.19.20240106 - - SQLAlchemy==2.0.25 + - SQLAlchemy==2.0.26 # stages: [push] - repo: https://github.com/pycqa/isort From 3d6079ae19108f09205001d485974b837e4329a3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Feb 2024 17:47:43 +0100 Subject: [PATCH 166/247] Add debug output showing the pair to be converted part of #9811 --- freqtrade/data/converter/trade_converter_kraken.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/data/converter/trade_converter_kraken.py b/freqtrade/data/converter/trade_converter_kraken.py index 614d97b2a..c9848c096 100644 --- a/freqtrade/data/converter/trade_converter_kraken.py +++ b/freqtrade/data/converter/trade_converter_kraken.py @@ -48,6 +48,7 @@ def import_kraken_trades_from_csv(config: Config, convert_to: str): logger.info(f"Converting pairs: {', '.join(m[0] for m in markets)}.") for pair, name in markets: + logger.debug(f"Converting pair {pair}, files */{name}.csv") dfs = [] # Load and combine all csv files for this pair for f in tradesdir.rglob(f"{name}.csv"): From 280737447cdac4806161fe6e0f72a3b808d68b5c Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Feb 2024 07:26:23 +0100 Subject: [PATCH 167/247] Don't load empty dataframes - skip these closes #9811 --- freqtrade/data/converter/trade_converter_kraken.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/converter/trade_converter_kraken.py b/freqtrade/data/converter/trade_converter_kraken.py index c9848c096..b0fa11c25 100644 --- a/freqtrade/data/converter/trade_converter_kraken.py +++ b/freqtrade/data/converter/trade_converter_kraken.py @@ -53,7 +53,8 @@ def import_kraken_trades_from_csv(config: Config, convert_to: str): # Load and combine all csv files for this pair for f in tradesdir.rglob(f"{name}.csv"): df = pd.read_csv(f, names=KRAKEN_CSV_TRADE_COLUMNS) - dfs.append(df) + if not df.empty: + dfs.append(df) # Load existing trades data if not dfs: From 57fd0e379abe1161390668c2dd6cd4a8c3925ce1 Mon Sep 17 00:00:00 2001 From: Robert Davey Date: Thu, 15 Feb 2024 15:57:49 +0000 Subject: [PATCH 168/247] Clarify processing_mode for RemotePairlist No default value is specified in the docs for the processing_mode, making it unclear that the default behaviour is to filter out pairs, rather than append. --- docs/includes/pairlists.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 9781edf10..5a6a2560b 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -201,7 +201,7 @@ The RemotePairList is defined in the pairlists section of the configuration sett The optional `mode` option specifies if the pairlist should be used as a `blacklist` or as a `whitelist`. The default value is "whitelist". -The optional `processing_mode` option in the RemotePairList configuration determines how the retrieved pairlist is processed. It can have two values: "filter" or "append". +The optional `processing_mode` option in the RemotePairList configuration determines how the retrieved pairlist is processed. It can have two values: "filter" or "append". The default value is "filter". In "filter" mode, the retrieved pairlist is used as a filter. Only the pairs present in both the original pairlist and the retrieved pairlist are included in the final pairlist. Other pairs are filtered out. From 86da9cb659d126a3a46a4eec2c3a25ab09761a9f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Feb 2024 19:27:15 +0100 Subject: [PATCH 169/247] Simplify some pairlist conditions --- freqtrade/plugins/pairlist/VolatilityFilter.py | 3 +-- freqtrade/plugins/pairlist/rangestabilityfilter.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index 800bf3664..794df5449 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -125,8 +125,7 @@ class VolatilityFilter(IPairList): :return: True if the pair can stay, false if it should be removed """ # Check symbol in cache - cached_res = self._pair_cache.get(pair, None) - if cached_res is not None: + if (cached_res := self._pair_cache.get(pair, None)) is not None: return cached_res result = False diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index f4625f572..e04772e9c 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -123,8 +123,7 @@ class RangeStabilityFilter(IPairList): :return: True if the pair can stay, false if it should be removed """ # Check symbol in cache - cached_res = self._pair_cache.get(pair, None) - if cached_res is not None: + if (cached_res := self._pair_cache.get(pair, None)) is not None: return cached_res result = True From d01e9cf2990e8bf21199b3aed465387be2da8d3c Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 14 Feb 2024 19:49:47 +0100 Subject: [PATCH 170/247] Improve log message --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ff04037da..2032e437d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -702,7 +702,7 @@ class FreqtradeBot(LoggingMixin): delta = f"Delta: {bids_ask_delta}" logger.info( - f"{bids}, {asks}, {delta}, Direction: {side.value}" + f"{bids}, {asks}, {delta}, Direction: {side.value} " f"Bid Price: {order_book['bids'][0][0]}, Ask Price: {order_book['asks'][0][0]}, " f"Immediate Bid Quantity: {order_book['bids'][0][1]}, " f"Immediate Ask Quantity: {order_book['asks'][0][1]}." From 6c9b9e91e8f47b94559c5ab9862b243c8165437e Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Feb 2024 06:39:07 +0100 Subject: [PATCH 171/247] enhance volumpairlist range test --- tests/plugins/test_pairlist.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 09dcd0af3..55d65d3c7 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -626,8 +626,9 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t # "lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}], # "BTC", "ftx", ['HOT/BTC', 'LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC']), ]) -def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history, - pairlists, base_currency, exchange, volumefilter_result) -> None: +def test_VolumePairList_range( + mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history, + pairlists, base_currency, exchange, volumefilter_result, time_machine) -> None: whitelist_conf['pairlists'] = pairlists whitelist_conf['stake_currency'] = base_currency whitelist_conf['exchange']['name'] = exchange @@ -686,23 +687,35 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, get_tickers=tickers, markets=PropertyMock(return_value=shitcoinmarkets) ) - + start_dt = dt_now() + time_machine.move_to(start_dt) # remove ohlcv when looback_timeframe != 1d # to enforce fallback to ticker data if 'lookback_timeframe' in pairlists[0]: if pairlists[0]['lookback_timeframe'] != '1d': ohlcv_data = [] - mocker.patch.multiple( - EXMS, - refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data), - ) + ohclv_mock = mocker.patch(f"{EXMS}.refresh_latest_ohlcv", return_value=ohlcv_data) freqtrade.pairlists.refresh_pairlist() whitelist = freqtrade.pairlists.whitelist + assert ohclv_mock.call_count == 1 assert isinstance(whitelist, list) assert whitelist == volumefilter_result + # Test caching + ohclv_mock.reset_mock() + freqtrade.pairlists.refresh_pairlist() + assert ohclv_mock.call_count == 0 + whitelist = freqtrade.pairlists.whitelist + assert whitelist == volumefilter_result + + time_machine.move_to(start_dt + timedelta(days=2)) + ohclv_mock.reset_mock() + freqtrade.pairlists.refresh_pairlist() + assert ohclv_mock.call_count == 1 + whitelist = freqtrade.pairlists.whitelist + assert whitelist == volumefilter_result def test_PrecisionFilter_error(mocker, whitelist_conf) -> None: From 7f7e9ec8756b2bd2b555e4b14b4ba0c5af476baa Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Feb 2024 06:42:57 +0100 Subject: [PATCH 172/247] Add additional test case for VolumePairlist in range mode --- tests/plugins/test_pairlist.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 55d65d3c7..32f6abb51 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -621,6 +621,12 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", "lookback_timeframe": "1d", "lookback_period": 6, "refresh_period": 86400}], "BTC", "binance", ['LTC/BTC', 'XRP/BTC', 'ETH/BTC', 'HOT/BTC', 'NEO/BTC']), + # VolumePairlist in range mode as filter. + # TKN/BTC is removed because it doesn't have enough candles + ([{"method": "VolumePairList", "number_assets": 5}, + {"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", + "lookback_timeframe": "1d", "lookback_period": 2, "refresh_period": 86400}], + "BTC", "binance", ['LTC/BTC', 'XRP/BTC', 'ETH/BTC', 'TKN/BTC', 'HOT/BTC']), # ftx data is already in Quote currency, therefore won't require conversion # ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", # "lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}], @@ -693,7 +699,7 @@ def test_VolumePairList_range( # to enforce fallback to ticker data if 'lookback_timeframe' in pairlists[0]: if pairlists[0]['lookback_timeframe'] != '1d': - ohlcv_data = [] + ohlcv_data = {} ohclv_mock = mocker.patch(f"{EXMS}.refresh_latest_ohlcv", return_value=ohlcv_data) @@ -706,7 +712,8 @@ def test_VolumePairList_range( # Test caching ohclv_mock.reset_mock() freqtrade.pairlists.refresh_pairlist() - assert ohclv_mock.call_count == 0 + # in "filter" mode, caching is disabled. + assert ohclv_mock.call_count == (0 if len(pairlists) == 1 else 1) whitelist = freqtrade.pairlists.whitelist assert whitelist == volumefilter_result From a22181d721ea465ce27fca653cff56a00b66eae8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Feb 2024 18:16:55 +0100 Subject: [PATCH 173/247] Enable caching for "filter only" Volumepairlist --- freqtrade/plugins/pairlist/VolumePairList.py | 16 ++++++++++++---- tests/plugins/test_pairlist.py | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/freqtrade/plugins/pairlist/VolumePairList.py b/freqtrade/plugins/pairlist/VolumePairList.py index b5525e950..671ba4db4 100644 --- a/freqtrade/plugins/pairlist/VolumePairList.py +++ b/freqtrade/plugins/pairlist/VolumePairList.py @@ -14,7 +14,7 @@ from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date from freqtrade.exchange.types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter -from freqtrade.util import dt_now, format_ms_time +from freqtrade.util import PeriodicCache, dt_now, format_ms_time logger = logging.getLogger(__name__) @@ -63,6 +63,7 @@ class VolumePairList(IPairList): # get timeframe in minutes and seconds self._tf_in_min = timeframe_to_minutes(self._lookback_timeframe) _tf_in_sec = self._tf_in_min * 60 + self._candle_cache = PeriodicCache(maxsize=1000, ttl=_tf_in_sec) # wether to use range lookback or not self._use_range = (self._tf_in_min > 0) & (self._lookback_period > 0) @@ -230,11 +231,18 @@ class VolumePairList(IPairList): ] # Get all candles - candles = {} - if needed_pairs: + candles = { + c: self._candle_cache.get(c, None) for c in needed_pairs + if c in self._candle_cache + } + pairs_to_download = [p for p in needed_pairs if p not in candles] + if pairs_to_download: candles = self._exchange.refresh_latest_ohlcv( - needed_pairs, since_ms=since_ms, cache=False + pairs_to_download, since_ms=since_ms, cache=False ) + for c, val in candles.items(): + self._candle_cache[c] = val + for i, p in enumerate(filtered_tickers): contract_size = self._exchange.markets[p['symbol']].get('contractSize', 1.0) or 1.0 pair_candles = candles[ diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 32f6abb51..d125f8896 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -713,7 +713,7 @@ def test_VolumePairList_range( ohclv_mock.reset_mock() freqtrade.pairlists.refresh_pairlist() # in "filter" mode, caching is disabled. - assert ohclv_mock.call_count == (0 if len(pairlists) == 1 else 1) + assert ohclv_mock.call_count == 0 whitelist = freqtrade.pairlists.whitelist assert whitelist == volumefilter_result From c1d71848490a8530b4e5ef66f21a60baa5c2e6b5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Feb 2024 19:20:08 +0100 Subject: [PATCH 174/247] Adjust for ccxt exception hierarchy change caused by https://github.com/ccxt/ccxt/pull/21035 --- freqtrade/exchange/exchange.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 85a77fe5e..4c142a517 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1242,7 +1242,7 @@ class Exchange: f'Insufficient funds to create {ordertype} {side} order on market {pair}. ' f'Tried to {side} amount {amount} at rate {limit_rate} with ' f'stop-price {stop_price_norm}. Message: {e}') from e - except (ccxt.InvalidOrder, ccxt.BadRequest) as e: + except (ccxt.InvalidOrder, ccxt.BadRequest, ccxt.OperationRejected) as e: # Errors: # `Order would trigger immediately.` raise InvalidOrderException( @@ -2685,7 +2685,7 @@ class Exchange: self._log_exchange_response('set_leverage', res) except ccxt.DDoSProtection as e: raise DDosProtection(e) from e - except (ccxt.BadRequest, ccxt.InsufficientFunds) as e: + except (ccxt.BadRequest, ccxt.OperationRejected, ccxt.InsufficientFunds) as e: if not accept_fail: raise TemporaryError( f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e @@ -2727,7 +2727,7 @@ class Exchange: self._log_exchange_response('set_margin_mode', res) except ccxt.DDoSProtection as e: raise DDosProtection(e) from e - except ccxt.BadRequest as e: + except (ccxt.BadRequest, ccxt.OperationRejected) as e: if not accept_fail: raise TemporaryError( f'Could not set margin mode due to {e.__class__.__name__}. Message: {e}') from e From c6d1c1a980ed9f587e16fa0b9df38d075b185545 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Feb 2024 19:50:56 +0100 Subject: [PATCH 175/247] Add dt_ts_none helper --- freqtrade/util/__init__.py | 5 +++-- freqtrade/util/datetime_helpers.py | 11 ++++++++++- tests/utils/test_datetime_helpers.py | 11 +++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/freqtrade/util/__init__.py b/freqtrade/util/__init__.py index 513406fd2..f7e63d9d3 100644 --- a/freqtrade/util/__init__.py +++ b/freqtrade/util/__init__.py @@ -1,6 +1,6 @@ from freqtrade.util.datetime_helpers import (dt_floor_day, dt_from_ts, dt_humanize, dt_now, dt_ts, - dt_ts_def, dt_utc, format_date, format_ms_time, - shorten_date) + dt_ts_def, dt_ts_none, dt_utc, format_date, + format_ms_time, shorten_date) from freqtrade.util.formatters import decimals_per_coin, fmt_coin, round_value from freqtrade.util.ft_precise import FtPrecise from freqtrade.util.periodic_cache import PeriodicCache @@ -14,6 +14,7 @@ __all__ = [ 'dt_now', 'dt_ts', 'dt_ts_def', + 'dt_ts_none', 'dt_utc', 'format_date', 'format_ms_time', diff --git a/freqtrade/util/datetime_helpers.py b/freqtrade/util/datetime_helpers.py index 102c83143..973a1c51b 100644 --- a/freqtrade/util/datetime_helpers.py +++ b/freqtrade/util/datetime_helpers.py @@ -31,12 +31,21 @@ def dt_ts(dt: Optional[datetime] = None) -> int: def dt_ts_def(dt: Optional[datetime], default: int = 0) -> int: """ Return dt in ms as a timestamp in UTC. - If dt is None, return the current datetime in UTC. + If dt is None, return the given default. """ if dt: return int(dt.timestamp() * 1000) return default +def dt_ts_none(dt: Optional[datetime]) -> Optional[int]: + """ + Return dt in ms as a timestamp in UTC. + If dt is None, return the given default. + """ + if dt: + return int(dt.timestamp() * 1000) + return None + def dt_floor_day(dt: datetime) -> datetime: """Return the floor of the day for the given datetime.""" diff --git a/tests/utils/test_datetime_helpers.py b/tests/utils/test_datetime_helpers.py index b70065645..6fbe75200 100644 --- a/tests/utils/test_datetime_helpers.py +++ b/tests/utils/test_datetime_helpers.py @@ -3,8 +3,8 @@ from datetime import datetime, timedelta, timezone import pytest import time_machine -from freqtrade.util import (dt_floor_day, dt_from_ts, dt_humanize, dt_now, dt_ts, dt_ts_def, dt_utc, - format_date, format_ms_time, shorten_date) +from freqtrade.util import (dt_floor_day, dt_from_ts, dt_humanize, dt_now, dt_ts, dt_ts_def, + dt_ts_none, dt_utc, format_date, format_ms_time, shorten_date) def test_dt_now(): @@ -29,6 +29,13 @@ def test_dt_ts_def(): assert dt_ts_def(datetime(2023, 5, 5, tzinfo=timezone.utc), 123) == 1683244800000 +def test_dt_ts_none(): + assert dt_ts_none(None) is None + assert dt_ts_none(None) is None + assert dt_ts_none(datetime(2023, 5, 5, tzinfo=timezone.utc)) == 1683244800000 + assert dt_ts_none(datetime(2023, 5, 5, tzinfo=timezone.utc)) == 1683244800000 + + def test_dt_utc(): assert dt_utc(2023, 5, 5) == datetime(2023, 5, 5, tzinfo=timezone.utc) assert dt_utc(2023, 5, 5, 0, 0, 0, 555500) == datetime(2023, 5, 5, 0, 0, 0, 555500, From 0f85ef09973a0f32df6488a3873f591888364d88 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Feb 2024 19:52:50 +0100 Subject: [PATCH 176/247] Simplify trade_model serializations --- freqtrade/persistence/trade_model.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index a90d9ab2d..84c11f02c 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -23,7 +23,7 @@ from freqtrade.exchange import (ROUND_DOWN, ROUND_UP, amount_to_contract_precisi from freqtrade.leverage import interest from freqtrade.misc import safe_value_fallback from freqtrade.persistence.base import ModelBase, SessionType -from freqtrade.util import FtPrecise, dt_from_ts, dt_now, dt_ts +from freqtrade.util import FtPrecise, dt_from_ts, dt_now, dt_ts, dt_ts_none logger = logging.getLogger(__name__) @@ -224,8 +224,7 @@ class Order(ModelBase): 'amount': self.safe_amount, 'safe_price': self.safe_price, 'ft_order_side': self.ft_order_side, - 'order_filled_timestamp': int(self.order_filled_date.replace( - tzinfo=timezone.utc).timestamp() * 1000) if self.order_filled_date else None, + 'order_filled_timestamp': dt_ts_none(self.order_filled_utc), 'ft_is_entry': self.ft_order_side == entry_side, 'ft_order_tag': self.ft_order_tag, } @@ -625,15 +624,14 @@ class LocalTrade: 'fee_close_currency': self.fee_close_currency, 'open_date': self.open_date.strftime(DATETIME_PRINT_FORMAT), - 'open_timestamp': int(self.open_date.replace(tzinfo=timezone.utc).timestamp() * 1000), + 'open_timestamp': dt_ts_none(self.open_date_utc), 'open_rate': self.open_rate, 'open_rate_requested': self.open_rate_requested, 'open_trade_value': round(self.open_trade_value, 8), 'close_date': (self.close_date.strftime(DATETIME_PRINT_FORMAT) if self.close_date else None), - 'close_timestamp': int(self.close_date.replace( - tzinfo=timezone.utc).timestamp() * 1000) if self.close_date else None, + 'close_timestamp': dt_ts_none(self.close_date_utc), 'realized_profit': self.realized_profit or 0.0, # Close-profit corresponds to relative realized_profit ratio 'realized_profit_ratio': self.close_profit or None, @@ -659,8 +657,7 @@ class LocalTrade: 'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None, 'stoploss_last_update': (self.stoploss_last_update_utc.strftime(DATETIME_PRINT_FORMAT) if self.stoploss_last_update_utc else None), - 'stoploss_last_update_timestamp': int(self.stoploss_last_update_utc.timestamp() * 1000 - ) if self.stoploss_last_update_utc else None, + 'stoploss_last_update_timestamp': dt_ts_none(self.stoploss_last_update_utc), 'initial_stop_loss_abs': self.initial_stop_loss, 'initial_stop_loss_ratio': (self.initial_stop_loss_pct if self.initial_stop_loss_pct else None), From fb54c9ffe4a4623e62ff36aa0917bd6782391940 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Feb 2024 20:19:08 +0100 Subject: [PATCH 177/247] Add open_fill_date stuff to json schema --- freqtrade/persistence/trade_model.py | 14 ++++++++++++++ freqtrade/rpc/api_server/api_schemas.py | 2 ++ 2 files changed, 16 insertions(+) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 84c11f02c..121a0bd8a 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -460,6 +460,17 @@ class LocalTrade: return self.open_date_utc return max([self.open_date_utc, dt_last_filled]) + @property + def date_entry_fill_utc(self) -> Optional[datetime]: + """ Date of the first filled order""" + orders = self.select_filled_orders(self.entry_side) + if ( + orders + and len((filled_date := [o.order_filled_utc for o in orders if o.order_filled_utc])) + ): + return min(filled_date) + return None + @property def open_date_utc(self): return self.open_date.replace(tzinfo=timezone.utc) @@ -625,6 +636,9 @@ class LocalTrade: 'open_date': self.open_date.strftime(DATETIME_PRINT_FORMAT), 'open_timestamp': dt_ts_none(self.open_date_utc), + 'open_fill_date': (self.date_entry_fill_utc.strftime(DATETIME_PRINT_FORMAT) + if self.date_entry_fill_utc else None), + 'open_fill_timestamp': dt_ts_none(self.date_entry_fill_utc), 'open_rate': self.open_rate, 'open_rate_requested': self.open_rate_requested, 'open_trade_value': round(self.open_trade_value, 8), diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 9919d1a05..3ea9ed4d0 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -288,6 +288,8 @@ class TradeSchema(BaseModel): open_date: str open_timestamp: int + open_fill_date: Optional[str] + open_fill_timestamp: Optional[int] open_rate: float open_rate_requested: Optional[float] = None open_trade_value: float From 1696aa391504cb2ade22a9124d17d7be27eea696 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Feb 2024 20:39:06 +0100 Subject: [PATCH 178/247] Adjust tests for new fields --- tests/persistence/test_persistence.py | 4 ++++ tests/persistence/test_trade_fromjson.py | 2 +- tests/rpc/test_rpc.py | 2 ++ tests/rpc/test_rpc_apiserver.py | 4 ++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index 95db7bc0f..0e0e70ee8 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -1400,6 +1400,8 @@ def test_to_json(fee): 'is_open': None, 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT), 'open_timestamp': int(trade.open_date.timestamp() * 1000), + 'open_fill_date': None, + 'open_fill_timestamp': None, 'close_date': None, 'close_timestamp': None, 'open_rate': 0.123, @@ -1486,6 +1488,8 @@ def test_to_json(fee): 'quote_currency': 'BTC', 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT), 'open_timestamp': int(trade.open_date.timestamp() * 1000), + 'open_fill_date': None, + 'open_fill_timestamp': None, 'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT), 'close_timestamp': int(trade.close_date.timestamp() * 1000), 'open_rate': 0.123, diff --git a/tests/persistence/test_trade_fromjson.py b/tests/persistence/test_trade_fromjson.py index 302a81c54..988f7ed5b 100644 --- a/tests/persistence/test_trade_fromjson.py +++ b/tests/persistence/test_trade_fromjson.py @@ -223,7 +223,7 @@ def test_trade_serialize_load_back(fee): 'realized_profit_ratio', 'close_profit_pct', 'trade_duration_s', 'trade_duration', 'profit_ratio', 'profit_pct', 'profit_abs', 'stop_loss_abs', - 'initial_stop_loss_abs', + 'initial_stop_loss_abs', 'open_fill_date', 'open_fill_timestamp', 'orders', ] failed = [] diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 1f51b30df..85b105892 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -25,6 +25,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'quote_currency': 'BTC', 'open_date': ANY, 'open_timestamp': ANY, + 'open_fill_date': ANY, + 'open_fill_timestamp': ANY, 'is_open': ANY, 'fee_open': ANY, 'fee_open_cost': ANY, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index bba18bcd3..e441b127b 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1165,6 +1165,8 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, 'current_rate': current_rate, 'open_date': ANY, 'open_timestamp': ANY, + 'open_fill_date': ANY, + 'open_fill_timestamp': ANY, 'open_rate': 0.123, 'pair': 'ETH/BTC', 'base_currency': 'ETH', @@ -1368,6 +1370,8 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): 'close_rate': 0.265441, 'open_date': ANY, 'open_timestamp': ANY, + 'open_fill_date': ANY, + 'open_fill_timestamp': ANY, 'open_rate': 0.245441, 'pair': 'ETH/BTC', 'base_currency': 'ETH', From fd48991fb073d5362a5c458e01f2e53d17847967 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Feb 2024 20:44:16 +0100 Subject: [PATCH 179/247] Fix duplicate parentheses --- freqtrade/persistence/trade_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 121a0bd8a..b1330b83c 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -466,7 +466,7 @@ class LocalTrade: orders = self.select_filled_orders(self.entry_side) if ( orders - and len((filled_date := [o.order_filled_utc for o in orders if o.order_filled_utc])) + and len(filled_date := [o.order_filled_utc for o in orders if o.order_filled_utc]) ): return min(filled_date) return None From 60b12c1d9ecd19e8ed7ae6e559021c810c068c8c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Feb 2024 20:45:16 +0100 Subject: [PATCH 180/247] Double newlines between functions ... --- freqtrade/util/datetime_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/util/datetime_helpers.py b/freqtrade/util/datetime_helpers.py index 973a1c51b..66b738e8d 100644 --- a/freqtrade/util/datetime_helpers.py +++ b/freqtrade/util/datetime_helpers.py @@ -37,6 +37,7 @@ def dt_ts_def(dt: Optional[datetime], default: int = 0) -> int: return int(dt.timestamp() * 1000) return default + def dt_ts_none(dt: Optional[datetime]) -> Optional[int]: """ Return dt in ms as a timestamp in UTC. From a0b7df70d694a978dd7433244569883db8358e62 Mon Sep 17 00:00:00 2001 From: CaffeinatedTech Date: Fri, 16 Feb 2024 13:36:16 +1000 Subject: [PATCH 181/247] Added escaping to enter and exit tags on telegram performance messages. --- freqtrade/rpc/telegram.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index f9a0635f0..f28b26766 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -4,6 +4,7 @@ This module manage Telegram communication """ import asyncio +import html import json import logging import re @@ -1378,7 +1379,7 @@ class Telegram(RPCHandler): output = "Entry Tag Performance:\n" for i, trade in enumerate(trades): stat_line = ( - f"{i + 1}.\t {trade['enter_tag']}\t" + f"{i + 1}.\t {html.escape(trade['enter_tag'])}\t" f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit_ratio']:.2%}) " f"({trade['count']})\n") @@ -1410,7 +1411,7 @@ class Telegram(RPCHandler): output = "Exit Reason Performance:\n" for i, trade in enumerate(trades): stat_line = ( - f"{i + 1}.\t {trade['exit_reason']}\t" + f"{i + 1}.\t {html.escape(trade['exit_reason'])}\t" f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit_ratio']:.2%}) " f"({trade['count']})\n") @@ -1442,7 +1443,7 @@ class Telegram(RPCHandler): output = "Mix Tag Performance:\n" for i, trade in enumerate(trades): stat_line = ( - f"{i + 1}.\t {trade['mix_tag']}\t" + f"{i + 1}.\t {html.escape(trade['mix_tag'])}\t" f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit_ratio']:.2%}) " f"({trade['count']})\n") From c0da1b6922891afc66b3071237ce3220f1421cdd Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 16 Feb 2024 20:04:49 +0100 Subject: [PATCH 182/247] Fix edge-case when calculating cagr edge-case with leveraged trades - yielding a negative final balance. closes #9820 --- freqtrade/data/metrics.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 7b45342bb..b37e0bb19 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -191,6 +191,9 @@ def calculate_cagr(days_passed: int, starting_balance: float, final_balance: flo :param final_balance: Final balance to calculate CAGR against :return: CAGR """ + if final_balance < 0: + # With leveraged trades, final_balance can become negative. + return 0 return (final_balance / starting_balance) ** (1 / (days_passed / 365)) - 1 From 4761bf242750e032ca427d0ad3f688cc77b4e23d Mon Sep 17 00:00:00 2001 From: CaffeinatedTech Date: Sat, 17 Feb 2024 09:12:49 +1000 Subject: [PATCH 183/247] Change enter_tag, exit_reason, mix_tag performance messages from HTML to Markdown to fix some string encoding issues. --- freqtrade/rpc/telegram.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index f28b26766..2983eea38 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -4,7 +4,6 @@ This module manage Telegram communication """ import asyncio -import html import json import logging import re @@ -1376,21 +1375,21 @@ class Telegram(RPCHandler): pair = context.args[0] trades = self._rpc._rpc_enter_tag_performance(pair) - output = "Entry Tag Performance:\n" + output = "*Entry Tag Performance:*\n" for i, trade in enumerate(trades): stat_line = ( - f"{i + 1}.\t {html.escape(trade['enter_tag'])}\t" + f"{i + 1}.\t `{trade['enter_tag']}\t" f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit_ratio']:.2%}) " - f"({trade['count']})\n") + f"({trade['count']})`\n") if len(output + stat_line) >= MAX_MESSAGE_LENGTH: - await self._send_msg(output, parse_mode=ParseMode.HTML) + await self._send_msg(output, parse_mode=ParseMode.MARKDOWN_V2) output = stat_line else: output += stat_line - await self._send_msg(output, parse_mode=ParseMode.HTML, + await self._send_msg(output, parse_mode=ParseMode.MARKDOWN_V2, reload_able=True, callback_path="update_enter_tag_performance", query=update.callback_query) @@ -1408,21 +1407,21 @@ class Telegram(RPCHandler): pair = context.args[0] trades = self._rpc._rpc_exit_reason_performance(pair) - output = "Exit Reason Performance:\n" + output = "*Exit Reason Performance:*\n" for i, trade in enumerate(trades): stat_line = ( - f"{i + 1}.\t {html.escape(trade['exit_reason'])}\t" + f"{i + 1}\.\t `{html.escape(trade['exit_reason'])}\t" f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit_ratio']:.2%}) " - f"({trade['count']})\n") + f"({trade['count']})`\n") if len(output + stat_line) >= MAX_MESSAGE_LENGTH: - await self._send_msg(output, parse_mode=ParseMode.HTML) + await self._send_msg(output, parse_mode=ParseMode.MARKDOWN_V2) output = stat_line else: output += stat_line - await self._send_msg(output, parse_mode=ParseMode.HTML, + await self._send_msg(output, parse_mode=ParseMode.MARKDOWN_V2, reload_able=True, callback_path="update_exit_reason_performance", query=update.callback_query) @@ -1440,21 +1439,21 @@ class Telegram(RPCHandler): pair = context.args[0] trades = self._rpc._rpc_mix_tag_performance(pair) - output = "Mix Tag Performance:\n" + output = "*Mix Tag Performance:*\n" for i, trade in enumerate(trades): stat_line = ( - f"{i + 1}.\t {html.escape(trade['mix_tag'])}\t" + f"{i + 1}\.\t `{trade['mix_tag']}\t" f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit_ratio']:.2%}) " - f"({trade['count']})\n") + f"({trade['count']})`\n") if len(output + stat_line) >= MAX_MESSAGE_LENGTH: - await self._send_msg(output, parse_mode=ParseMode.HTML) + await self._send_msg(output, parse_mode=ParseMode.MARKDOWN_V2) output = stat_line else: output += stat_line - await self._send_msg(output, parse_mode=ParseMode.HTML, + await self._send_msg(output, parse_mode=ParseMode.MARKDOWN_V2, reload_able=True, callback_path="update_mix_tag_performance", query=update.callback_query) From 3f3760c0ae7fc3fee5147737907b35d92375dd6b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Feb 2024 12:02:26 +0100 Subject: [PATCH 184/247] Use Markdown V1 - update tests --- freqtrade/rpc/telegram.py | 16 ++++++++-------- tests/rpc/test_rpc_telegram.py | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 2983eea38..fcc61b5e4 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1384,12 +1384,12 @@ class Telegram(RPCHandler): f"({trade['count']})`\n") if len(output + stat_line) >= MAX_MESSAGE_LENGTH: - await self._send_msg(output, parse_mode=ParseMode.MARKDOWN_V2) + await self._send_msg(output, parse_mode=ParseMode.MARKDOWN) output = stat_line else: output += stat_line - await self._send_msg(output, parse_mode=ParseMode.MARKDOWN_V2, + await self._send_msg(output, parse_mode=ParseMode.MARKDOWN, reload_able=True, callback_path="update_enter_tag_performance", query=update.callback_query) @@ -1410,18 +1410,18 @@ class Telegram(RPCHandler): output = "*Exit Reason Performance:*\n" for i, trade in enumerate(trades): stat_line = ( - f"{i + 1}\.\t `{html.escape(trade['exit_reason'])}\t" + f"{i + 1}.\t `{trade['exit_reason']}\t" f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit_ratio']:.2%}) " f"({trade['count']})`\n") if len(output + stat_line) >= MAX_MESSAGE_LENGTH: - await self._send_msg(output, parse_mode=ParseMode.MARKDOWN_V2) + await self._send_msg(output, parse_mode=ParseMode.MARKDOWN) output = stat_line else: output += stat_line - await self._send_msg(output, parse_mode=ParseMode.MARKDOWN_V2, + await self._send_msg(output, parse_mode=ParseMode.MARKDOWN, reload_able=True, callback_path="update_exit_reason_performance", query=update.callback_query) @@ -1442,18 +1442,18 @@ class Telegram(RPCHandler): output = "*Mix Tag Performance:*\n" for i, trade in enumerate(trades): stat_line = ( - f"{i + 1}\.\t `{trade['mix_tag']}\t" + f"{i + 1}.\t `{trade['mix_tag']}\t" f"{fmt_coin(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit_ratio']:.2%}) " f"({trade['count']})`\n") if len(output + stat_line) >= MAX_MESSAGE_LENGTH: - await self._send_msg(output, parse_mode=ParseMode.MARKDOWN_V2) + await self._send_msg(output, parse_mode=ParseMode.MARKDOWN) output = stat_line else: output += stat_line - await self._send_msg(output, parse_mode=ParseMode.MARKDOWN_V2, + await self._send_msg(output, parse_mode=ParseMode.MARKDOWN, reload_able=True, callback_path="update_mix_tag_performance", query=update.callback_query) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 3c683d7b3..7b1347fd6 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1507,7 +1507,7 @@ async def test_telegram_entry_tag_performance_handle( await telegram._enter_tag_performance(update=update, context=context) assert msg_mock.call_count == 1 assert 'Entry Tag Performance' in msg_mock.call_args_list[0][0][0] - assert 'TEST1\t3.987 USDT (5.00%) (1)' in msg_mock.call_args_list[0][0][0] + assert '`TEST1\t3.987 USDT (5.00%) (1)`' in msg_mock.call_args_list[0][0][0] context.args = ['XRP/USDT'] await telegram._enter_tag_performance(update=update, context=context) @@ -1538,7 +1538,7 @@ async def test_telegram_exit_reason_performance_handle( await telegram._exit_reason_performance(update=update, context=context) assert msg_mock.call_count == 1 assert 'Exit Reason Performance' in msg_mock.call_args_list[0][0][0] - assert 'roi\t2.842 USDT (10.00%) (1)' in msg_mock.call_args_list[0][0][0] + assert '`roi\t2.842 USDT (10.00%) (1)`' in msg_mock.call_args_list[0][0][0] context.args = ['XRP/USDT'] await telegram._exit_reason_performance(update=update, context=context) @@ -1570,7 +1570,7 @@ async def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ti await telegram._mix_tag_performance(update=update, context=context) assert msg_mock.call_count == 1 assert 'Mix Tag Performance' in msg_mock.call_args_list[0][0][0] - assert ('TEST3 roi\t2.842 USDT (10.00%) (1)' + assert ('`TEST3 roi\t2.842 USDT (10.00%) (1)`' in msg_mock.call_args_list[0][0][0]) context.args = ['XRP/USDT'] From 8033faa2f29fc58905fd7f0794d0100ad0928bc9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Feb 2024 15:14:11 +0100 Subject: [PATCH 185/247] Update pairlist cache behavior in VolumePairList --- docs/includes/pairlists.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 5a6a2560b..d1dd2cda7 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -68,7 +68,7 @@ When used in the leading position of the chain of Pairlist Handlers, the `pair_w The `refresh_period` setting allows to define the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes). The pairlist cache (`refresh_period`) on `VolumePairList` is only applicable to generating pairlists. -Filtering instances (not the first position in the list) will not apply any cache and will always use up-to-date data. +Filtering instances (not the first position in the list) will not apply any cache (beyond caching candles for the duration of the candle in advanced mode) and will always use up-to-date data. `VolumePairList` is per default based on the ticker data from exchange, as reported by the ccxt library: From bcfe7ef547871b0f1f7e1984e23935aa511ca3ec Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Feb 2024 16:17:32 +0100 Subject: [PATCH 186/247] Refactor ohlcv caching to exchange class --- freqtrade/exchange/exchange.py | 36 ++++++++++++++++++++ freqtrade/plugins/pairlist/VolumePairList.py | 16 ++------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 85a77fe5e..6dbb38bd8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -6,6 +6,7 @@ import asyncio import inspect import logging import signal +from collections import defaultdict from copy import deepcopy from datetime import datetime, timedelta, timezone from math import floor @@ -43,6 +44,7 @@ from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_ from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.util import dt_from_ts, dt_now from freqtrade.util.datetime_helpers import dt_humanize, dt_ts +from freqtrade.util.periodic_cache import PeriodicCache logger = logging.getLogger(__name__) @@ -131,6 +133,7 @@ class Exchange: # Holds candles self._klines: Dict[PairWithTimeframe, DataFrame] = {} + self._expiring_candle_cache: Dict[str, PeriodicCache] = {} # Holds all open sell orders for dry_run self._dry_run_open_orders: Dict[str, Any] = {} @@ -2124,6 +2127,39 @@ class Exchange: return results_df + def refresh_ohlcv_with_cache( + self, + pairs: List[PairWithTimeframe], + since_ms: int + ) -> Dict[PairWithTimeframe, DataFrame]: + """ + Refresh ohlcv data for all pairs in needed_pairs if necessary. + Caches data with expiring per timeframe. + Should only be used for pairlists which need "on time" expirarion, and no longer cache. + """ + + timeframes = [p[1] for p in pairs] + for timeframe in timeframes: + if timeframe not in self._expiring_candle_cache: + timeframe_in_sec = timeframe_to_seconds(timeframe) + # Initialise cache + self._expiring_candle_cache[timeframe] = PeriodicCache(ttl=timeframe_in_sec, + maxsize=1000) + + # Get candles from cache + candles = { + c: self._expiring_candle_cache[c[1]].get(c, None) for c in pairs + if c in self._expiring_candle_cache[c[1]] + } + pairs_to_download = [p for p in pairs if p not in candles] + if pairs_to_download: + candles = self.refresh_latest_ohlcv( + pairs_to_download, since_ms=since_ms, cache=False + ) + for c, val in candles.items(): + self._expiring_candle_cache[c[1]][c] = val + return candles + def _now_is_time_to_refresh(self, pair: str, timeframe: str, candle_type: CandleType) -> bool: # Timeframe in seconds interval_in_sec = timeframe_to_seconds(timeframe) diff --git a/freqtrade/plugins/pairlist/VolumePairList.py b/freqtrade/plugins/pairlist/VolumePairList.py index 671ba4db4..f4d08e800 100644 --- a/freqtrade/plugins/pairlist/VolumePairList.py +++ b/freqtrade/plugins/pairlist/VolumePairList.py @@ -14,7 +14,7 @@ from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date from freqtrade.exchange.types import Tickers from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter -from freqtrade.util import PeriodicCache, dt_now, format_ms_time +from freqtrade.util import dt_now, format_ms_time logger = logging.getLogger(__name__) @@ -63,7 +63,6 @@ class VolumePairList(IPairList): # get timeframe in minutes and seconds self._tf_in_min = timeframe_to_minutes(self._lookback_timeframe) _tf_in_sec = self._tf_in_min * 60 - self._candle_cache = PeriodicCache(maxsize=1000, ttl=_tf_in_sec) # wether to use range lookback or not self._use_range = (self._tf_in_min > 0) & (self._lookback_period > 0) @@ -230,18 +229,7 @@ class VolumePairList(IPairList): if p not in self._pair_cache ] - # Get all candles - candles = { - c: self._candle_cache.get(c, None) for c in needed_pairs - if c in self._candle_cache - } - pairs_to_download = [p for p in needed_pairs if p not in candles] - if pairs_to_download: - candles = self._exchange.refresh_latest_ohlcv( - pairs_to_download, since_ms=since_ms, cache=False - ) - for c, val in candles.items(): - self._candle_cache[c] = val + candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms) for i, p in enumerate(filtered_tickers): contract_size = self._exchange.markets[p['symbol']].get('contractSize', 1.0) or 1.0 From 7b36a0fc4220ae3d7141228ad9e4a23a29a07e7d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Feb 2024 16:26:53 +0100 Subject: [PATCH 187/247] Add explicit test for ohlcv_with_cache --- tests/exchange/test_exchange.py | 60 +++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index fc199a7f5..ef41a6eb0 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2303,6 +2303,66 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach assert res[pair2].at[0, 'open'] +def test_refresh_ohlcv_with_cache(mocker, default_conf, time_machine) -> None: + start = datetime(2021, 8, 1, 0, 0, 0, 0, tzinfo=timezone.utc) + ohlcv = generate_test_data_raw('1h', 100, start.strftime('%Y-%m-%d')) + time_machine.move_to(start, tick=False) + pairs = [ + ('ETH/BTC', '1d', CandleType.SPOT), + ('TKN/BTC', '1d', CandleType.SPOT), + ('LTC/BTC', '1d', CandleType.SPOT), + ('LTC/BTC', '5m', CandleType.SPOT), + ('LTC/BTC', '1h', CandleType.SPOT), + ] + + ohlcv_data = { + p: ohlcv for p in pairs + } + ohlcv_mock = mocker.patch(f"{EXMS}.refresh_latest_ohlcv", return_value=ohlcv_data) + mocker.patch(f"{EXMS}.ohlcv_candle_limit", return_value=100) + exchange = get_patched_exchange(mocker, default_conf) + + assert len(exchange._expiring_candle_cache) == 0 + + res = exchange.refresh_ohlcv_with_cache(pairs, start.timestamp()) + assert ohlcv_mock.call_count == 1 + assert ohlcv_mock.call_args_list[0][0][0] == pairs + assert len(ohlcv_mock.call_args_list[0][0][0]) == 5 + + assert len(res) == 5 + # length of 3 - as we have 3 different timeframes + assert len(exchange._expiring_candle_cache) == 3 + + ohlcv_mock.reset_mock() + res = exchange.refresh_ohlcv_with_cache(pairs, start.timestamp()) + assert ohlcv_mock.call_count == 0 + + # Expire 5m cache + time_machine.move_to(start + timedelta(minutes=6), tick=False) + + ohlcv_mock.reset_mock() + res = exchange.refresh_ohlcv_with_cache(pairs, start.timestamp()) + assert ohlcv_mock.call_count == 1 + assert len(ohlcv_mock.call_args_list[0][0][0]) == 1 + + # Expire 5m and 1h cache + time_machine.move_to(start + timedelta(hours=2), tick=False) + + ohlcv_mock.reset_mock() + res = exchange.refresh_ohlcv_with_cache(pairs, start.timestamp()) + assert ohlcv_mock.call_count == 1 + assert len(ohlcv_mock.call_args_list[0][0][0]) == 2 + + # Expire all caches + time_machine.move_to(start + timedelta(days=1, hours=2), tick=False) + + ohlcv_mock.reset_mock() + res = exchange.refresh_ohlcv_with_cache(pairs, start.timestamp()) + assert ohlcv_mock.call_count == 1 + assert len(ohlcv_mock.call_args_list[0][0][0]) == 5 + assert ohlcv_mock.call_args_list[0][0][0] == pairs + + @pytest.mark.parametrize("exchange_name", EXCHANGES) async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name): ohlcv = [ From 4bcf2c423a1d478bfa54b85bcdef077086ee23a9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Feb 2024 16:27:43 +0100 Subject: [PATCH 188/247] Don't tick on ttl cache --- tests/utils/test_periodiccache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils/test_periodiccache.py b/tests/utils/test_periodiccache.py index df05de4ef..a8931d6a2 100644 --- a/tests/utils/test_periodiccache.py +++ b/tests/utils/test_periodiccache.py @@ -5,7 +5,7 @@ from freqtrade.util import PeriodicCache def test_ttl_cache(): - with time_machine.travel("2021-09-01 05:00:00 +00:00") as t: + with time_machine.travel("2021-09-01 05:00:00 +00:00", tick=False) as t: cache = PeriodicCache(5, ttl=60) cache1h = PeriodicCache(5, ttl=3600) From 78d8a4df2ea41cab6e2c9932091267f1d9316518 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Feb 2024 16:29:52 +0100 Subject: [PATCH 189/247] Use "ohlcv_with_cache" for further pairlists --- freqtrade/plugins/pairlist/VolatilityFilter.py | 5 +---- freqtrade/plugins/pairlist/rangestabilityfilter.py | 6 +----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index 794df5449..b6ce1b9a2 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -104,10 +104,7 @@ class VolatilityFilter(IPairList): since_ms = dt_ts(dt_floor_day(dt_now()) - timedelta(days=self._days)) # Get all candles - candles = {} - if needed_pairs: - candles = self._exchange.refresh_latest_ohlcv(needed_pairs, since_ms=since_ms, - cache=False) + candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms=since_ms) if self._enabled: for p in deepcopy(pairlist): diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index e04772e9c..f2cf4d486 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -101,11 +101,7 @@ class RangeStabilityFilter(IPairList): (p, '1d', self._def_candletype) for p in pairlist if p not in self._pair_cache] since_ms = dt_ts(dt_floor_day(dt_now()) - timedelta(days=self._days - 1)) - # Get all candles - candles = {} - if needed_pairs: - candles = self._exchange.refresh_latest_ohlcv(needed_pairs, since_ms=since_ms, - cache=False) + candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms=since_ms) if self._enabled: for p in deepcopy(pairlist): From ebd439cdd10ede780da071f0829b8b23fd9cef18 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Feb 2024 16:41:10 +0100 Subject: [PATCH 190/247] Remove unused import --- freqtrade/exchange/exchange.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 6dbb38bd8..ee3ca05cf 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -6,7 +6,6 @@ import asyncio import inspect import logging import signal -from collections import defaultdict from copy import deepcopy from datetime import datetime, timedelta, timezone from math import floor From a5d1ae31915f401e8b0031d99eacac4e181f516d Mon Sep 17 00:00:00 2001 From: William Wong <46506352+tar-xz@users.noreply.github.com> Date: Sun, 18 Feb 2024 03:21:50 +0800 Subject: [PATCH 191/247] docs: Update sql_cheatsheet.md --- docs/sql_cheatsheet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sql_cheatsheet.md b/docs/sql_cheatsheet.md index 67c081d4c..a0c5c8da1 100644 --- a/docs/sql_cheatsheet.md +++ b/docs/sql_cheatsheet.md @@ -109,7 +109,7 @@ Freqtrade does not depend or install any additional database driver. Please refe The following systems have been tested and are known to work with freqtrade: * sqlite (default) -* PostgreSQL) +* PostgreSQL * MariaDB !!! Warning From 3250f42257777f076b72bf7bf8966940f9bfc618 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Feb 2024 11:21:34 +0100 Subject: [PATCH 192/247] Improve validate_exchange returns now both required and optional dependencies --- freqtrade/exchange/common.py | 1 + freqtrade/exchange/exchange_utils.py | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index 72ad774b6..d04241e29 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -86,6 +86,7 @@ EXCHANGE_HAS_OPTIONAL = [ # 'fetchPositions', # Futures trading # 'fetchLeverageTiers', # Futures initialization # 'fetchMarketLeverageTiers', # Futures initialization + # 'fetchOpenOrder', 'fetchClosedOrder', # replacement for fetchOrder # 'fetchOpenOrders', 'fetchClosedOrders', # 'fetchOrders', # Refinding balance... ] diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index 98e05bf7a..f8da47fee 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -40,21 +40,30 @@ def available_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> List[st def validate_exchange(exchange: str) -> Tuple[bool, str]: + """ + returns: can_use, reason + with Reason including both missing and missing_opt + """ ex_mod = getattr(ccxt, exchange.lower())() + result = True + reason = '' if not ex_mod or not ex_mod.has: return False, '' missing = [k for k in EXCHANGE_HAS_REQUIRED if ex_mod.has.get(k) is not True] if missing: - return False, f"missing: {', '.join(missing)}" + result = False + reason += f"missing: {', '.join(missing)}" missing_opt = [k for k in EXCHANGE_HAS_OPTIONAL if not ex_mod.has.get(k)] if exchange.lower() in BAD_EXCHANGES: - return False, BAD_EXCHANGES.get(exchange.lower(), '') - if missing_opt: - return True, f"missing opt: {', '.join(missing_opt)}" + result = False + reason = BAD_EXCHANGES.get(exchange.lower(), '') - return True, '' + if missing_opt: + reason += f"{'. ' if reason else ''}missing opt: {', '.join(missing_opt)}. " + + return result, reason def _build_exchange_list_entry( From e06b70eb0530e81a8e6bf503a94c4bef45884a9d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Feb 2024 11:40:50 +0100 Subject: [PATCH 193/247] Add log message for Bybit accout type --- freqtrade/exchange/bybit.py | 16 +++++++++++++--- tests/exchange/test_bybit.py | 21 ++++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index e7c463140..259858802 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -25,6 +25,7 @@ class Bybit(Exchange): officially supported by the Freqtrade development team. So some features may still not work as expected. """ + unified_account = False _ft_has: Dict = { "ohlcv_candle_limit": 1000, @@ -82,9 +83,18 @@ class Bybit(Exchange): Must be overridden in child methods if required. """ try: - if self.trading_mode == TradingMode.FUTURES and not self._config['dry_run']: - position_mode = self._api.set_position_mode(False) - self._log_exchange_response('set_position_mode', position_mode) + if not self._config['dry_run']: + if self.trading_mode == TradingMode.FUTURES: + position_mode = self._api.set_position_mode(False) + self._log_exchange_response('set_position_mode', position_mode) + is_unified = self._api.is_unified_enabled() + # Returns a tuple of bools, first for margin, second for Account + if is_unified and len(is_unified) > 1 and is_unified[1]: + self.unified_account = True + logger.info("Bybit: Unified account.") + else: + self.unified_account = False + logger.info("Bybit: Standard account.") except ccxt.DDoSProtection as e: raise DDosProtection(e) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: diff --git a/tests/exchange/test_bybit.py b/tests/exchange/test_bybit.py index f7383934b..74a490aa9 100644 --- a/tests/exchange/test_bybit.py +++ b/tests/exchange/test_bybit.py @@ -3,18 +3,33 @@ from unittest.mock import MagicMock from freqtrade.enums.marginmode import MarginMode from freqtrade.enums.tradingmode import TradingMode -from tests.conftest import EXMS, get_mock_coro, get_patched_exchange +from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has from tests.exchange.test_exchange import ccxt_exceptionhandlers -def test_additional_exchange_init_bybit(default_conf, mocker): +def test_additional_exchange_init_bybit(default_conf, mocker, caplog): default_conf['dry_run'] = False default_conf['trading_mode'] = TradingMode.FUTURES default_conf['margin_mode'] = MarginMode.ISOLATED api_mock = MagicMock() api_mock.set_position_mode = MagicMock(return_value={"dualSidePosition": False}) - get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) + api_mock.is_unified_enabled = MagicMock(return_value=[False, False]) + + exchange = get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) assert api_mock.set_position_mode.call_count == 1 + assert api_mock.is_unified_enabled.call_count == 1 + assert exchange.unified_account is False + + assert log_has("Bybit: Standard account.", caplog) + + api_mock.set_position_mode.reset_mock() + api_mock.is_unified_enabled = MagicMock(return_value=[False, True]) + exchange = get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) + assert api_mock.set_position_mode.call_count == 1 + assert api_mock.is_unified_enabled.call_count == 1 + assert exchange.unified_account is True + + assert log_has("Bybit: Unified account.", caplog) ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'bybit', "additional_exchange_init", "set_position_mode") From 583b2fc690f2461126d7b14b6af560b87537e928 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Feb 2024 11:44:54 +0100 Subject: [PATCH 194/247] Fail if unified account is detected. --- freqtrade/exchange/bybit.py | 2 ++ tests/exchange/test_bybit.py | 15 ++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index 259858802..63047066a 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -92,6 +92,8 @@ class Bybit(Exchange): if is_unified and len(is_unified) > 1 and is_unified[1]: self.unified_account = True logger.info("Bybit: Unified account.") + raise OperationalException("Bybit: Unified account is not supported. " + "Please use a standard (sub)account.") else: self.unified_account = False logger.info("Bybit: Standard account.") diff --git a/tests/exchange/test_bybit.py b/tests/exchange/test_bybit.py index 74a490aa9..fb7d7a120 100644 --- a/tests/exchange/test_bybit.py +++ b/tests/exchange/test_bybit.py @@ -1,8 +1,11 @@ from datetime import datetime, timedelta, timezone from unittest.mock import MagicMock +import pytest + from freqtrade.enums.marginmode import MarginMode from freqtrade.enums.tradingmode import TradingMode +from freqtrade.exceptions import OperationalException from tests.conftest import EXMS, get_mock_coro, get_patched_exchange, log_has from tests.exchange.test_exchange import ccxt_exceptionhandlers @@ -24,12 +27,14 @@ def test_additional_exchange_init_bybit(default_conf, mocker, caplog): api_mock.set_position_mode.reset_mock() api_mock.is_unified_enabled = MagicMock(return_value=[False, True]) - exchange = get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) - assert api_mock.set_position_mode.call_count == 1 - assert api_mock.is_unified_enabled.call_count == 1 - assert exchange.unified_account is True - + with pytest.raises(OperationalException, match=r"Bybit: Unified account is not supported.*"): + get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) assert log_has("Bybit: Unified account.", caplog) + # exchange = get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) + # assert api_mock.set_position_mode.call_count == 1 + # assert api_mock.is_unified_enabled.call_count == 1 + # assert exchange.unified_account is True + ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'bybit', "additional_exchange_init", "set_position_mode") From 61e09ac719f9c1bffb2cb082bb45dd75657154fa Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Feb 2024 16:07:03 +0100 Subject: [PATCH 195/247] Update telegram help with new wording --- freqtrade/rpc/telegram.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index fcc61b5e4..904b1fdbc 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1364,7 +1364,7 @@ class Telegram(RPCHandler): @authorized_only async def _enter_tag_performance(self, update: Update, context: CallbackContext) -> None: """ - Handler for /buys PAIR . + Handler for /entries PAIR . Shows a performance statistic from finished trades :param bot: telegram bot :param update: message update @@ -1396,7 +1396,7 @@ class Telegram(RPCHandler): @authorized_only async def _exit_reason_performance(self, update: Update, context: CallbackContext) -> None: """ - Handler for /sells. + Handler for /exits. Shows a performance statistic from finished trades :param bot: telegram bot :param update: message update @@ -1676,8 +1676,8 @@ class Telegram(RPCHandler): " *table :* `will display trades in a table`\n" " `pending buy orders are marked with an asterisk (*)`\n" " `pending sell orders are marked with a double asterisk (**)`\n" - "*/buys :* `Shows the enter_tag performance`\n" - "*/sells :* `Shows the exit reason performance`\n" + "*/entries :* `Shows the enter_tag performance`\n" + "*/exits :* `Shows the exit reason performance`\n" "*/mix_tags :* `Shows combined entry tag + exit reason performance`\n" "*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\n" "*/profit []:* `Lists cumulative profit from all finished trades, " From 69a0f4c465fa4f45ef8d4271de85839897c70f05 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Feb 2024 16:09:59 +0100 Subject: [PATCH 196/247] Fix bybit spot live tests --- tests/exchange_online/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/exchange_online/conftest.py b/tests/exchange_online/conftest.py index a613ae586..f8cd8f413 100644 --- a/tests/exchange_online/conftest.py +++ b/tests/exchange_online/conftest.py @@ -324,7 +324,8 @@ def get_futures_exchange(exchange_name, exchange_conf, class_mocker): @pytest.fixture(params=EXCHANGES, scope="class") -def exchange(request, exchange_conf): +def exchange(request, exchange_conf, class_mocker): + class_mocker.patch('freqtrade.exchange.bybit.Bybit.additional_exchange_init') yield from get_exchange(request.param, exchange_conf) From 4c3879cb57b0aaad66e7b1767fe6c0b58de16fc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 03:11:21 +0000 Subject: [PATCH 197/247] Bump the types group with 1 update Bumps the types group with 1 update: [types-requests](https://github.com/python/typeshed). Updates `types-requests` from 2.31.0.20240125 to 2.31.0.20240218 - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f0095bffa..bee5140f6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -26,6 +26,6 @@ nbconvert==7.16.0 # mypy types types-cachetools==5.3.0.7 types-filelock==3.2.7 -types-requests==2.31.0.20240125 +types-requests==2.31.0.20240218 types-tabulate==0.9.0.20240106 types-python-dateutil==2.8.19.20240106 From 0979d0b6e464b15c67e79d10530b846d1c1e65d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 03:11:53 +0000 Subject: [PATCH 198/247] Bump the pytest group with 1 update Bumps the pytest group with 1 update: [pytest](https://github.com/pytest-dev/pytest). Updates `pytest` from 8.0.0 to 8.0.1 - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.0.0...8.0.1) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch dependency-group: pytest ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f0095bffa..a70af1fb9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,7 +10,7 @@ coveralls==3.3.1 ruff==0.2.1 mypy==1.8.0 pre-commit==3.6.1 -pytest==8.0.0 +pytest==8.0.1 pytest-asyncio==0.23.5 pytest-cov==4.1.0 pytest-mock==3.12.0 From 8675f86d14af7021eb6386ad0d997d07bbc9dfb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 03:11:57 +0000 Subject: [PATCH 199/247] Bump urllib3 from 2.2.0 to 2.2.1 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.0 to 2.2.1. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.2.0...2.2.1) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c30036e5a..72e342523 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ httpx>=0.24.1 arrow==1.3.0 cachetools==5.3.2 requests==2.31.0 -urllib3==2.2.0 +urllib3==2.2.1 jsonschema==4.21.1 TA-Lib==0.4.28 technical==1.4.3 From f361824b15b5e7bd7c83774ac420c5f825a1d7a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 03:12:05 +0000 Subject: [PATCH 200/247] Bump cryptography from 42.0.2 to 42.0.3 Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.2 to 42.0.3. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.2...42.0.3) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c30036e5a..52ae42c60 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ pandas==2.1.4 pandas-ta==0.3.14b ccxt==4.2.42 -cryptography==42.0.2 +cryptography==42.0.3 aiohttp==3.9.3 SQLAlchemy==2.0.26 python-telegram-bot==20.8 From c966f83147da6f4aebd394cfd90fc097b4213b80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 03:12:17 +0000 Subject: [PATCH 201/247] Bump plotly from 5.18.0 to 5.19.0 Bumps [plotly](https://github.com/plotly/plotly.py) from 5.18.0 to 5.19.0. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v5.18.0...v5.19.0) --- updated-dependencies: - dependency-name: plotly dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index 8900bf1f9..af746ef98 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,4 +1,4 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==5.18.0 +plotly==5.19.0 From 4241db2fe25b7fc37282ec8e47acc08aed10b4b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 03:12:37 +0000 Subject: [PATCH 202/247] Bump scikit-learn from 1.4.0 to 1.4.1.post1 Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 1.4.0 to 1.4.1.post1. - [Release notes](https://github.com/scikit-learn/scikit-learn/releases) - [Commits](https://github.com/scikit-learn/scikit-learn/compare/1.4.0...1.4.1.post1) --- updated-dependencies: - dependency-name: scikit-learn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-freqai.txt | 2 +- requirements-hyperopt.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index 848b6d920..3719f7d57 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -3,7 +3,7 @@ -r requirements-plot.txt # Required for freqai -scikit-learn==1.4.0 +scikit-learn==1.4.1.post1 joblib==1.3.2 catboost==1.2.2; 'arm' not in platform_machine and python_version < '3.12' lightgbm==4.3.0 diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index b961b3b04..5347adf9c 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -3,6 +3,6 @@ # Required for hyperopt scipy==1.12.0 -scikit-learn==1.4.0 +scikit-learn==1.4.1.post1 ft-scikit-optimize==0.9.2 filelock==3.13.1 From edb5431a778e367c029d4f163f23288a8ee8cee6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 03:13:07 +0000 Subject: [PATCH 203/247] Bump orjson from 3.9.13 to 3.9.14 Bumps [orjson](https://github.com/ijl/orjson) from 3.9.13 to 3.9.14. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.9.13...3.9.14) --- updated-dependencies: - dependency-name: orjson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c30036e5a..73688c870 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ py_find_1st==1.1.6 # Load ticker files 30% faster python-rapidjson==1.14 # Properly format api responses -orjson==3.9.13 +orjson==3.9.14 # Notify systemd sdnotify==0.3.2 From 549b9f62fdc234c6dc43e7c0875a8d5c8438a905 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 03:13:12 +0000 Subject: [PATCH 204/247] Bump tensorboard from 2.15.2 to 2.16.2 Bumps [tensorboard](https://github.com/tensorflow/tensorboard) from 2.15.2 to 2.16.2. - [Release notes](https://github.com/tensorflow/tensorboard/releases) - [Changelog](https://github.com/tensorflow/tensorboard/blob/master/RELEASE.md) - [Commits](https://github.com/tensorflow/tensorboard/compare/2.15.2...2.16.2) --- updated-dependencies: - dependency-name: tensorboard dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-freqai.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai.txt b/requirements-freqai.txt index 848b6d920..1df9ad416 100644 --- a/requirements-freqai.txt +++ b/requirements-freqai.txt @@ -8,5 +8,5 @@ joblib==1.3.2 catboost==1.2.2; 'arm' not in platform_machine and python_version < '3.12' lightgbm==4.3.0 xgboost==2.0.3 -tensorboard==2.15.2 +tensorboard==2.16.2 datasieve==0.1.7 From 381576b8f148517bcc9fc74023599122b2c3a156 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 05:30:18 +0000 Subject: [PATCH 205/247] Bump pre-commit from 3.6.1 to 3.6.2 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.6.1 to 3.6.2. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v3.6.1...v3.6.2) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a70af1fb9..13d699a16 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,7 +9,7 @@ coveralls==3.3.1 ruff==0.2.1 mypy==1.8.0 -pre-commit==3.6.1 +pre-commit==3.6.2 pytest==8.0.1 pytest-asyncio==0.23.5 pytest-cov==4.1.0 From 6aa1bbf574f417d122475bdf97a8ed057b484e6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 05:31:00 +0000 Subject: [PATCH 206/247] Bump sqlalchemy from 2.0.26 to 2.0.27 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.26 to 2.0.27. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d18950d73..09933bffa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pandas-ta==0.3.14b ccxt==4.2.42 cryptography==42.0.3 aiohttp==3.9.3 -SQLAlchemy==2.0.26 +SQLAlchemy==2.0.27 python-telegram-bot==20.8 # can't be hard-pinned due to telegram-bot pinning httpx with ~ httpx>=0.24.1 From 66f48391014a3005b321635e8b8a801b3f51c991 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Feb 2024 07:02:37 +0100 Subject: [PATCH 207/247] Further increase test coverate of max_drawdown --- tests/data/test_btanalysis.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index c1b007e77..554ee261a 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -455,6 +455,13 @@ def test_calculate_max_drawdown2(): with pytest.raises(ValueError, match='No losing trade, therefore no drawdown.'): calculate_max_drawdown(df, date_col='open_date', value_col='profit') + df1 = DataFrame(zip(values[:5], dates[:5]), columns=['profit', 'open_date']) + df1.loc[:, 'profit'] = df1['profit'] * -1 + # No winning trade ... + drawdown, hdate, ldate, hval, lval, drawdown_rel = calculate_max_drawdown( + df1, date_col='open_date', value_col='profit') + assert drawdown == 0.043965 + @pytest.mark.parametrize('profits,relative,highd,lowd,result,result_rel', [ ([0.0, -500.0, 500.0, 10000.0, -1000.0], False, 3, 4, 1000.0, 0.090909), From 39941a7ac04da6b8aa9fabe947ed2ca266ef1b27 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Feb 2024 07:09:23 +0100 Subject: [PATCH 208/247] Improve formatting in drawdown calc --- freqtrade/data/metrics.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index b37e0bb19..738129939 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -143,8 +143,10 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date' starting_balance=starting_balance ) - idxmin = max_drawdown_df['drawdown_relative'].idxmax() if relative \ - else max_drawdown_df['drawdown'].idxmin() + idxmin = ( + max_drawdown_df['drawdown_relative'].idxmax() + if relative else max_drawdown_df['drawdown'].idxmin() + ) if idxmin == 0: raise ValueError("No losing trade, therefore no drawdown.") high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col] From a200b5524b88ccdb3e9f5c3a52c93c3cff367521 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Feb 2024 07:18:56 +0100 Subject: [PATCH 209/247] Update sqlalchemy in pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1aa00f07..f843b6ebe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - types-requests==2.31.0.20240125 - types-tabulate==0.9.0.20240106 - types-python-dateutil==2.8.19.20240106 - - SQLAlchemy==2.0.26 + - SQLAlchemy==2.0.27 # stages: [push] - repo: https://github.com/pycqa/isort From 82876570a339882987cd441a7a0b459ae0569efd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 07:14:01 +0000 Subject: [PATCH 210/247] Bump ruff from 0.2.1 to 0.2.2 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.2.1 to 0.2.2. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.2.1...v0.2.2) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 13d699a16..c606f219d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==3.3.1 -ruff==0.2.1 +ruff==0.2.2 mypy==1.8.0 pre-commit==3.6.2 pytest==8.0.1 From 434b8a423cda08ada2d580ee58ab70204f25f772 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Feb 2024 09:33:09 +0100 Subject: [PATCH 211/247] bump types-requests pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1aa00f07..b73c2e5cf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: additional_dependencies: - types-cachetools==5.3.0.7 - types-filelock==3.2.7 - - types-requests==2.31.0.20240125 + - types-requests==2.31.0.20240218 - types-tabulate==0.9.0.20240106 - types-python-dateutil==2.8.19.20240106 - SQLAlchemy==2.0.26 From 00bde70f73e9363fdfe3e0f6b305b69cb959a6c0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Feb 2024 19:14:44 +0100 Subject: [PATCH 212/247] Fix / improve styling in test class --- tests/rpc/test_rpc_apiserver.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index e441b127b..1e008d98e 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -180,7 +180,9 @@ def test_api_auth(): def test_api_ws_auth(botclient): ftbot, client = botclient - def url(token): return f"/api/v1/message/ws?token={token}" + + def url(token): + return f"/api/v1/message/ws?token={token}" bad_token = "bad-ws_token" with pytest.raises(WebSocketDisconnect): From f6e2030bf2c22ca4bf775df8efee65ea81aeff93 Mon Sep 17 00:00:00 2001 From: xmatthias <5024695+xmatthias@users.noreply.github.com> Date: Tue, 20 Feb 2024 03:03:46 +0000 Subject: [PATCH 213/247] chore: update pre-commit hooks --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f9865be7b..842c87976 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.2.1' + rev: 'v0.2.2' hooks: - id: ruff From 65af7750e6cbb5ce9c026036d2dee1bf0ef2a20e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Feb 2024 13:00:43 +0100 Subject: [PATCH 214/247] Add fetch_order_emulated to support exchanges without proper fetch_order method --- freqtrade/exchange/exchange.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 1df51ed90..2872e603e 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1260,11 +1260,43 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(e) from e + def fetch_order_emulated(self, order_id: str, pair: str, params: Dict) -> Dict: + """ + Emulated fetch_order if the exchange doesn't support fetch_order, but requires separate + calls for open and closed orders. + """ + try: + order = self._api.fetch_open_order(order_id, pair, params=params) + self._log_exchange_response('fetch_open_order', order) + order = self._order_contracts_to_amount(order) + return order + except ccxt.OrderNotFound: + try: + order = self._api.fetch_closed_order(order_id, pair, params=params) + self._log_exchange_response('fetch_closed_order', order) + order = self._order_contracts_to_amount(order) + return order + except ccxt.OrderNotFound as e: + raise RetryableOrderError( + f'Order not found (pair: {pair} id: {order_id}). Message: {e}') from e + except ccxt.InvalidOrder as e: + raise InvalidOrderException( + f'Tried to get an invalid order (pair: {pair} id: {order_id}). Message: {e}') from e + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e + @retrier(retries=API_FETCH_ORDER_RETRY_COUNT) def fetch_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict: if self._config['dry_run']: return self.fetch_dry_run_order(order_id) try: + if not self.exchange_has('fetchOrder'): + return self.fetch_order_emulated(order_id, pair, params) order = self._api.fetch_order(order_id, pair, params=params) self._log_exchange_response('fetch_order', order) order = self._order_contracts_to_amount(order) From 3497f7946e89608551f3873e2eb5ecad93aa850e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Feb 2024 13:09:40 +0100 Subject: [PATCH 215/247] Add test for fetch_order_emulated --- tests/exchange/test_exchange.py | 66 +++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index ef41a6eb0..077f1f8f0 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3391,6 +3391,72 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog): order_id='_', pair='TKN/BTC') +@pytest.mark.usefixtures("init_persistence") +@pytest.mark.parametrize("exchange_name", EXCHANGES) +def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog): + default_conf['dry_run'] = True + default_conf['exchange']['log_responses'] = True + order = MagicMock() + order.myid = 123 + order.symbol = 'TKN/BTC' + + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + mocker.patch(f'{EXMS}.exchange_has', return_value=False) + exchange._dry_run_open_orders['X'] = order + # Dry run - regular fetch_order behavior + assert exchange.fetch_order('X', 'TKN/BTC').myid == 123 + + with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'): + exchange.fetch_order('Y', 'TKN/BTC') + + default_conf['dry_run'] = False + mocker.patch(f'{EXMS}.exchange_has', return_value=False) + api_mock = MagicMock() + api_mock.fetch_open_order = MagicMock(return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) + api_mock.fetch_closed_order = MagicMock(return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + assert exchange.fetch_order( + 'X', 'TKN/BTC') == {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'} + assert log_has( + ("API fetch_open_order: {\'id\': \'123\', \'amount\': 2, \'symbol\': \'TKN/BTC\'}" + ), + caplog + ) + assert api_mock.fetch_open_order.call_count == 1 + assert api_mock.fetch_closed_order.call_count == 0 + caplog.clear() + + # open_order doesn't find order + api_mock.fetch_open_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found")) + api_mock.fetch_closed_order = MagicMock(return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + assert exchange.fetch_order( + 'X', 'TKN/BTC') == {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'} + assert log_has( + ("API fetch_closed_order: {\'id\': \'123\', \'amount\': 2, \'symbol\': \'TKN/BTC\'}" + ), + caplog + ) + assert api_mock.fetch_open_order.call_count == 1 + assert api_mock.fetch_closed_order.call_count == 1 + caplog.clear() + + with pytest.raises(InvalidOrderException): + api_mock.fetch_open_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) + api_mock.fetch_closed_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + exchange.fetch_order(order_id='_', pair='TKN/BTC') + assert api_mock.fetch_open_order.call_count == 1 + + api_mock.fetch_open_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, + 'fetch_order_emulated', 'fetch_open_order', + retries=1, + order_id='_', pair='TKN/BTC', params={}) + + @pytest.mark.usefixtures("init_persistence") @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_stoploss_order(default_conf, mocker, exchange_name): From f53c019d2afa80f33b96e2fad61290d9c01a46d8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Feb 2024 15:14:07 +0100 Subject: [PATCH 216/247] Update "exchange_has" validation with new fallbacks --- freqtrade/exchange/common.py | 15 ++++++++------- freqtrade/exchange/exchange_utils.py | 6 +++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index d04241e29..06ae21001 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -60,16 +60,17 @@ SUPPORTED_EXCHANGES = [ 'okx', ] -EXCHANGE_HAS_REQUIRED = [ +# either the main, or replacement methods (array) is required +EXCHANGE_HAS_REQUIRED = { # Required / private - 'fetchOrder', - 'cancelOrder', - 'createOrder', - 'fetchBalance', + 'fetchOrder': ['fetchOpenOrder', 'fetchClosedOrder'], + 'cancelOrder': [], + 'createOrder': [], + 'fetchBalance': [], # Public endpoints - 'fetchOHLCV', -] + 'fetchOHLCV': [], +} EXCHANGE_HAS_OPTIONAL = [ # Private diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index f8da47fee..f4dc3a721 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -49,7 +49,11 @@ def validate_exchange(exchange: str) -> Tuple[bool, str]: reason = '' if not ex_mod or not ex_mod.has: return False, '' - missing = [k for k in EXCHANGE_HAS_REQUIRED if ex_mod.has.get(k) is not True] + missing = [ + k for k, v in EXCHANGE_HAS_REQUIRED.items() + if ex_mod.has.get(k) is not True + and not (all(ex_mod.has.get(x) for x in v)) + ] if missing: result = False reason += f"missing: {', '.join(missing)}" From 411f60647649ddb0833180dc5df7554939462005 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Feb 2024 15:39:13 +0100 Subject: [PATCH 217/247] Fix some tests due to new method --- tests/exchange/test_bybit.py | 1 + tests/exchange/test_exchange.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/exchange/test_bybit.py b/tests/exchange/test_bybit.py index fb7d7a120..556547d88 100644 --- a/tests/exchange/test_bybit.py +++ b/tests/exchange/test_bybit.py @@ -131,6 +131,7 @@ def test_bybit_fetch_order_canceled_empty(default_conf_usdt, mocker): 'amount': 20.0, }) + mocker.patch(f"{EXMS}.exchange_has", return_value=True) exchange = get_patched_exchange(mocker, default_conf_usdt, api_mock, id='bybit') res = exchange.fetch_order('123', 'BTC/USDT') diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 077f1f8f0..5c4879a32 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3237,6 +3237,7 @@ def test_is_cancel_order_result_suitable(mocker, default_conf, exchange_name, or def test_cancel_order_with_result(default_conf, mocker, exchange_name, corder, call_corder, call_forder): default_conf['dry_run'] = False + mocker.patch(f"{EXMS}.exchange_has", return_value=True) api_mock = MagicMock() api_mock.cancel_order = MagicMock(return_value=corder) api_mock.fetch_order = MagicMock(return_value={}) @@ -3250,6 +3251,7 @@ def test_cancel_order_with_result(default_conf, mocker, exchange_name, corder, @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_cancel_order_with_result_error(default_conf, mocker, exchange_name, caplog): default_conf['dry_run'] = False + mocker.patch(f"{EXMS}.exchange_has", return_value=True) api_mock = MagicMock() api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order")) api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order")) @@ -3347,6 +3349,7 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog): order.myid = 123 order.symbol = 'TKN/BTC' + mocker.patch(f"{EXMS}.exchange_has", return_value=True) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange._dry_run_open_orders['X'] = order assert exchange.fetch_order('X', 'TKN/BTC').myid == 123 @@ -3412,8 +3415,10 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog): default_conf['dry_run'] = False mocker.patch(f'{EXMS}.exchange_has', return_value=False) api_mock = MagicMock() - api_mock.fetch_open_order = MagicMock(return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) - api_mock.fetch_closed_order = MagicMock(return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) + api_mock.fetch_open_order = MagicMock( + return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) + api_mock.fetch_closed_order = MagicMock( + return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.fetch_order( 'X', 'TKN/BTC') == {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'} @@ -3428,7 +3433,8 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog): # open_order doesn't find order api_mock.fetch_open_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found")) - api_mock.fetch_closed_order = MagicMock(return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) + api_mock.fetch_closed_order = MagicMock( + return_value={'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) assert exchange.fetch_order( 'X', 'TKN/BTC') == {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'} @@ -3461,6 +3467,7 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_fetch_stoploss_order(default_conf, mocker, exchange_name): default_conf['dry_run'] = True + mocker.patch(f"{EXMS}.exchange_has", return_value=True) order = MagicMock() order.myid = 123 exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) From b3ba2cee1744141fd0ef252ae731d27a9e65a8df Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 20 Feb 2024 06:17:13 +0100 Subject: [PATCH 218/247] Bump ccxt to 4.2.47 --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 17ebe8867..1efe0b7da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.26.4 pandas==2.1.4 pandas-ta==0.3.14b -ccxt==4.2.42 +ccxt==4.2.47 cryptography==42.0.3 aiohttp==3.9.3 SQLAlchemy==2.0.27 diff --git a/setup.py b/setup.py index 3b92b9dd7..38f0f9a78 100644 --- a/setup.py +++ b/setup.py @@ -70,7 +70,7 @@ setup( ], install_requires=[ # from requirements.txt - 'ccxt>=4.2.15', + 'ccxt>=4.2.47', 'SQLAlchemy>=2.0.6', 'python-telegram-bot>=20.1', 'arrow>=1.0.0', From 0199e7d3d8dfe0046be307eabe70b58a6ef1e680 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 20 Feb 2024 06:30:10 +0100 Subject: [PATCH 219/247] Add type-hint to exchange_has dict --- freqtrade/exchange/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index 06ae21001..8909ef5ff 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -2,7 +2,7 @@ import asyncio import logging import time from functools import wraps -from typing import Any, Callable, Optional, TypeVar, cast, overload +from typing import Any, Callable, Dict, List, Optional, TypeVar, cast, overload from freqtrade.constants import ExchangeConfig from freqtrade.exceptions import DDosProtection, RetryableOrderError, TemporaryError @@ -61,7 +61,7 @@ SUPPORTED_EXCHANGES = [ ] # either the main, or replacement methods (array) is required -EXCHANGE_HAS_REQUIRED = { +EXCHANGE_HAS_REQUIRED: Dict[str, List[str]] = { # Required / private 'fetchOrder': ['fetchOpenOrder', 'fetchClosedOrder'], 'cancelOrder': [], From c013c76e64caa9e00330394fc597015607e8d69a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:49:18 +0000 Subject: [PATCH 220/247] Bump cryptography from 42.0.3 to 42.0.4 Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.3 to 42.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.3...42.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1efe0b7da..1c5bf8ba5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ pandas==2.1.4 pandas-ta==0.3.14b ccxt==4.2.47 -cryptography==42.0.3 +cryptography==42.0.4 aiohttp==3.9.3 SQLAlchemy==2.0.27 python-telegram-bot==20.8 From 4e87169a0ce074eba51007469234a8cb92502136 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 22 Feb 2024 05:55:05 +0100 Subject: [PATCH 221/247] Use set to reduce iterations --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 2872e603e..81d3973ba 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2169,7 +2169,7 @@ class Exchange: Should only be used for pairlists which need "on time" expirarion, and no longer cache. """ - timeframes = [p[1] for p in pairs] + timeframes = {p[1] for p in pairs} for timeframe in timeframes: if timeframe not in self._expiring_candle_cache: timeframe_in_sec = timeframe_to_seconds(timeframe) From 3680e199ce56672ed36e0b599deb6ba189861953 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 22 Feb 2024 06:15:26 +0100 Subject: [PATCH 222/247] Fix range-stability filter downloading too little data --- freqtrade/plugins/pairlist/rangestabilityfilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index f2cf4d486..49fba59b9 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -100,7 +100,7 @@ class RangeStabilityFilter(IPairList): needed_pairs: ListPairsWithTimeframes = [ (p, '1d', self._def_candletype) for p in pairlist if p not in self._pair_cache] - since_ms = dt_ts(dt_floor_day(dt_now()) - timedelta(days=self._days - 1)) + since_ms = dt_ts(dt_floor_day(dt_now()) - timedelta(days=self._days + 1)) candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms=since_ms) if self._enabled: From 4cfd5d004ea10bb8866c68fa4709cbcbdd149969 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 22 Feb 2024 06:38:28 +0100 Subject: [PATCH 223/247] Improve ohlcv pair cache with since_ms avoids some rare bugs in more complex configurations. --- freqtrade/exchange/exchange.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 81d3973ba..f896c7c51 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2171,16 +2171,16 @@ class Exchange: timeframes = {p[1] for p in pairs} for timeframe in timeframes: - if timeframe not in self._expiring_candle_cache: + if (timeframe, since_ms) not in self._expiring_candle_cache: timeframe_in_sec = timeframe_to_seconds(timeframe) # Initialise cache - self._expiring_candle_cache[timeframe] = PeriodicCache(ttl=timeframe_in_sec, - maxsize=1000) + self._expiring_candle_cache[(timeframe, since_ms)] = PeriodicCache( + ttl=timeframe_in_sec, maxsize=1000) # Get candles from cache candles = { - c: self._expiring_candle_cache[c[1]].get(c, None) for c in pairs - if c in self._expiring_candle_cache[c[1]] + c: self._expiring_candle_cache[(c[1], since_ms)].get(c, None) for c in pairs + if c in self._expiring_candle_cache[(c[1], since_ms)] } pairs_to_download = [p for p in pairs if p not in candles] if pairs_to_download: @@ -2188,7 +2188,7 @@ class Exchange: pairs_to_download, since_ms=since_ms, cache=False ) for c, val in candles.items(): - self._expiring_candle_cache[c[1]][c] = val + self._expiring_candle_cache[(c[1], since_ms)][c] = val return candles def _now_is_time_to_refresh(self, pair: str, timeframe: str, candle_type: CandleType) -> bool: From db83b0cdb0098d0f2d0a4b19443631a42a2d2abc Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 22 Feb 2024 07:09:47 +0100 Subject: [PATCH 224/247] Update typehint for candle_cache --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index f896c7c51..7f7fccca8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -132,7 +132,7 @@ class Exchange: # Holds candles self._klines: Dict[PairWithTimeframe, DataFrame] = {} - self._expiring_candle_cache: Dict[str, PeriodicCache] = {} + self._expiring_candle_cache: Dict[Tuple[str, int], PeriodicCache] = {} # Holds all open sell orders for dry_run self._dry_run_open_orders: Dict[str, Any] = {} From e30fa3a4459c3ed154b0b3f81f4e083721870ca6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 22 Feb 2024 18:17:58 +0100 Subject: [PATCH 225/247] Remove duplicate pairlistmanager init --- freqtrade/freqtradebot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2032e437d..4c6c4c1d4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -82,7 +82,6 @@ class FreqtradeBot(LoggingMixin): PairLocks.timeframe = self.config['timeframe'] - self.pairlists = PairListManager(self.exchange, self.config) self.trading_mode: TradingMode = self.config.get('trading_mode', TradingMode.SPOT) self.last_process: Optional[datetime] = None From f4ad1e802007bc11518ab133ac4773822815891d Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 22 Feb 2024 19:02:04 +0100 Subject: [PATCH 226/247] chore: Remove wrong typehint --- tests/optimize/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/optimize/conftest.py b/tests/optimize/conftest.py index 4d257addc..cb8a6b5f7 100644 --- a/tests/optimize/conftest.py +++ b/tests/optimize/conftest.py @@ -30,7 +30,7 @@ def hyperopt_conf(default_conf): @pytest.fixture(autouse=True) -def backtesting_cleanup() -> None: +def backtesting_cleanup(): yield None Backtesting.cleanup() From 0acde289023a32fe9c3137ff8aaf0192171a9d56 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 22 Feb 2024 19:28:02 +0100 Subject: [PATCH 227/247] Remove pointless comment --- freqtrade/plugins/pairlist/VolatilityFilter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index b6ce1b9a2..ef72486e1 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -103,7 +103,6 @@ class VolatilityFilter(IPairList): (p, '1d', self._def_candletype) for p in pairlist if p not in self._pair_cache] since_ms = dt_ts(dt_floor_day(dt_now()) - timedelta(days=self._days)) - # Get all candles candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms=since_ms) if self._enabled: From 9ac7149c47c9e07fa09b0c2ae73f12b2a1522370 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 22 Feb 2024 22:14:29 +0100 Subject: [PATCH 228/247] Add check to verify that "since" is properly respected. --- tests/exchange_online/test_ccxt_compat.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/exchange_online/test_ccxt_compat.py b/tests/exchange_online/test_ccxt_compat.py index f95f4c000..370bc8184 100644 --- a/tests/exchange_online/test_ccxt_compat.py +++ b/tests/exchange_online/test_ccxt_compat.py @@ -12,6 +12,7 @@ import pytest from freqtrade.enums import CandleType from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date from freqtrade.exchange.exchange import timeframe_to_msecs +from freqtrade.util import dt_floor_day, dt_now, dt_ts from tests.exchange_online.conftest import EXCHANGE_FIXTURE_TYPE, EXCHANGES @@ -187,6 +188,25 @@ class TestCCXTExchange: now = datetime.now(timezone.utc) - timedelta(minutes=(timeframe_to_minutes(timeframe) * 2)) assert exch.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now) + def test_ccxt_fetch_ohlcv_startdate(self, exchange: EXCHANGE_FIXTURE_TYPE): + """ + Test that pair data starts at the provided startdate + """ + exch, exchangename = exchange + pair = EXCHANGES[exchangename]['pair'] + timeframe = '1d' + + pair_tf = (pair, timeframe, CandleType.SPOT) + # last 5 days ... + since_ms = dt_ts(dt_floor_day(dt_now()) - timedelta(days=6)) + ohlcv = exch.refresh_latest_ohlcv([pair_tf], since_ms=since_ms) + assert isinstance(ohlcv, dict) + assert len(ohlcv[pair_tf]) == len(exch.klines(pair_tf)) + # Check if last-timeframe is within the last 2 intervals + now = datetime.now(timezone.utc) - timedelta(minutes=(timeframe_to_minutes(timeframe) * 2)) + assert exch.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now) + assert exch.klines(pair_tf)['date'].astype(int).iloc[0] // 1e6 == since_ms + def ccxt__async_get_candle_history( self, exchange, exchangename, pair, timeframe, candle_type, factor=0.9): From c663016b47ed29641c31344cf5f1a185d38bc18f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 24 Feb 2024 08:06:06 +0100 Subject: [PATCH 229/247] Improve some type safety --- freqtrade/freqtradebot.py | 8 ++++---- freqtrade/persistence/trade_model.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4c6c4c1d4..3f23f43ae 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -986,7 +986,7 @@ class FreqtradeBot(LoggingMixin): return enter_limit_requested, stake_amount, leverage - def _notify_enter(self, trade: Trade, order: Order, order_type: str, + def _notify_enter(self, trade: Trade, order: Order, order_type: Optional[str], fill: bool = False, sub_trade: bool = False) -> None: """ Sends rpc notification when a entry order occurred. @@ -1010,7 +1010,7 @@ class FreqtradeBot(LoggingMixin): 'direction': 'Short' if trade.is_short else 'Long', 'limit': open_rate, # Deprecated (?) 'open_rate': open_rate, - 'order_type': order_type, + 'order_type': order_type or 'unknown', 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], 'base_currency': self.exchange.get_pair_base_currency(trade.pair), @@ -1775,7 +1775,7 @@ class FreqtradeBot(LoggingMixin): return True - def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False, + def _notify_exit(self, trade: Trade, order_type: Optional[str], fill: bool = False, sub_trade: bool = False, order: Optional[Order] = None) -> None: """ Sends rpc notification when a sell occurred. @@ -1807,7 +1807,7 @@ class FreqtradeBot(LoggingMixin): 'gain': gain, 'limit': order_rate, # Deprecated 'order_rate': order_rate, - 'order_type': order_type, + 'order_type': order_type or 'unknown', 'amount': amount, 'open_rate': trade.open_rate, 'close_rate': order_rate, diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index b1330b83c..407affe72 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -73,8 +73,7 @@ class Order(ModelBase): order_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) status: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) symbol: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) - # TODO: type: order_type type is Optional[str] - order_type: Mapped[str] = mapped_column(String(50), nullable=True) + order_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) side: Mapped[str] = mapped_column(String(25), nullable=True) price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) average: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) @@ -815,6 +814,7 @@ class LocalTrade: order.funding_fee = self.funding_fee_running # Reset running funding fees self.funding_fee_running = 0.0 + order_type = order.order_type.upper() if order.order_type else None if order.ft_order_side == self.entry_side: # Update open rate and actual amount @@ -822,20 +822,20 @@ class LocalTrade: self.amount = order.safe_amount_after_fee if self.is_open: payment = "SELL" if self.is_short else "BUY" - logger.info(f'{order.order_type.upper()}_{payment} has been fulfilled for {self}.') + logger.info(f'{order_type}_{payment} has been fulfilled for {self}.') self.recalc_trade_from_orders() elif order.ft_order_side == self.exit_side: if self.is_open: payment = "BUY" if self.is_short else "SELL" # * On margin shorts, you buy a little bit more than the amount (amount + interest) - logger.info(f'{order.order_type.upper()}_{payment} has been fulfilled for {self}.') + logger.info(f'{order_type}_{payment} has been fulfilled for {self}.') elif order.ft_order_side == 'stoploss' and order.status not in ('open', ): self.close_rate_requested = self.stop_loss self.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value if self.is_open and order.safe_filled > 0: - logger.info(f'{order.order_type.upper()} is hit for {self}.') + logger.info(f'{order_type} is hit for {self}.') else: raise ValueError(f'Unknown order type: {order.order_type}') From f164b893519522f762969b647e6e14fc38343cbc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Feb 2024 08:57:05 +0100 Subject: [PATCH 230/247] Ensure pytz is updated regularily by pinning it --- requirements.txt | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 1c5bf8ba5..c227ae732 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,6 +50,7 @@ questionary==2.0.1 prompt-toolkit==3.0.36 # Extensions to datetime library python-dateutil==2.8.2 +pytz==2024.1 #Futures schedule==1.2.1 diff --git a/setup.py b/setup.py index 38f0f9a78..dea1966fa 100644 --- a/setup.py +++ b/setup.py @@ -110,6 +110,7 @@ setup( 'cryptography', 'sdnotify', 'python-dateutil', + 'pytz', 'packaging', ], extras_require={ From 3e0755b8ad480bdbe1f8d1f12ef551422590e12b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Feb 2024 09:02:17 +0100 Subject: [PATCH 231/247] Enhance funding-fee call test to account for different timezones --- tests/freqtradebot/test_freqtradebot.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py index aa037fe37..1891c2332 100644 --- a/tests/freqtradebot/test_freqtradebot.py +++ b/tests/freqtradebot/test_freqtradebot.py @@ -4680,9 +4680,14 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None: ('futures', 17, "2021-08-31 23:59:59", "2021-09-01 08:01:07"), ('futures', 17, "2021-08-31 23:59:58", "2021-09-01 08:01:07"), ]) +@pytest.mark.parametrize('tzoffset', [ + '+00:00', + '+01:00', + '-02:00', +]) def test_update_funding_fees_schedule(mocker, default_conf, trading_mode, calls, time_machine, - t1, t2): - time_machine.move_to(f"{t1} +00:00", tick=False) + t1, t2, tzoffset): + time_machine.move_to(f"{t1} {tzoffset}", tick=False) patch_RPCManager(mocker) patch_exchange(mocker) @@ -4691,7 +4696,7 @@ def test_update_funding_fees_schedule(mocker, default_conf, trading_mode, calls, default_conf['margin_mode'] = 'isolated' freqtrade = get_patched_freqtradebot(mocker, default_conf) - time_machine.move_to(f"{t2} +00:00", tick=False) + time_machine.move_to(f"{t2} {tzoffset}", tick=False) # Check schedule jobs in debugging with freqtrade._schedule.jobs freqtrade._schedule.run_pending() From aad327b1fe4aa6abad07d43a32e1d04b86d0723f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Feb 2024 09:02:58 +0100 Subject: [PATCH 232/247] Update comment around funding fees --- freqtrade/freqtradebot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 3f23f43ae..974f8124e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -128,8 +128,9 @@ class FreqtradeBot(LoggingMixin): self.update_funding_fees() self.wallets.update() - # TODO: This would be more efficient if scheduled in utc time, and performed at each - # TODO: funding interval, specified by funding_fee_times on the exchange classes + # This would be more efficient if scheduled in utc time, and performed at each + # funding interval, specified by funding_fee_times on the exchange classes + # However, this reduces the precision - and might therefore lead to problems. for time_slot in range(0, 24): for minutes in [1, 31]: t = str(time(time_slot, minutes, 2)) From e2d3774b07a12e0ad04ae8f3607486e709372681 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Feb 2024 09:07:53 +0100 Subject: [PATCH 233/247] Clearer wallets variable/parameter wording --- freqtrade/optimize/backtesting.py | 2 +- freqtrade/wallets.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 493c7567f..8d16122ea 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -201,7 +201,7 @@ class Backtesting: self.prepare_backtest(False) - self.wallets = Wallets(self.config, self.exchange, log=False) + self.wallets = Wallets(self.config, self.exchange, is_backtest=True) self.progress = BTProgress() self.abort = False diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 0f41114ed..0d22feb36 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -36,9 +36,9 @@ class PositionWallet(NamedTuple): class Wallets: - def __init__(self, config: Config, exchange: Exchange, log: bool = True) -> None: + def __init__(self, config: Config, exchange: Exchange, is_backtest: bool = False) -> None: self._config = config - self._log = log + self._is_backtest = is_backtest self._exchange = exchange self._wallets: Dict[str, Wallet] = {} self._positions: Dict[str, PositionWallet] = {} @@ -78,11 +78,11 @@ class Wallets: _wallets = {} _positions = {} open_trades = Trade.get_trades_proxy(is_open=True) - # If not backtesting... - # TODO: potentially remove the ._log workaround to determine backtest mode. - if self._log: + if not self._is_backtest: + # Live / Dry-run mode tot_profit = Trade.get_total_closed_profit() else: + # Backtest mode tot_profit = LocalTrade.total_profit tot_profit += sum(trade.realized_profit for trade in open_trades) tot_in_trades = sum(trade.stake_amount for trade in open_trades) @@ -177,7 +177,7 @@ class Wallets: self._update_live() else: self._update_dry() - if self._log: + if not self._is_backtest: logger.info('Wallets synced.') self._last_wallet_refresh = dt_now() @@ -341,19 +341,19 @@ class Wallets: max_allowed_stake = min(max_allowed_stake, max_stake_amount - trade_amount) if min_stake_amount is not None and min_stake_amount > max_allowed_stake: - if self._log: + if not self._is_backtest: logger.warning("Minimum stake amount > available balance. " f"{min_stake_amount} > {max_allowed_stake}") return 0 if min_stake_amount is not None and stake_amount < min_stake_amount: - if self._log: + if not self._is_backtest: logger.info( f"Stake amount for pair {pair} is too small " f"({stake_amount} < {min_stake_amount}), adjusting to {min_stake_amount}." ) if stake_amount * 1.3 < min_stake_amount: # Top-cap stake-amount adjustments to +30%. - if self._log: + if not self._is_backtest: logger.info( f"Adjusted stake amount for pair {pair} is more than 30% bigger than " f"the desired stake amount of ({stake_amount:.8f} * 1.3 = " @@ -363,7 +363,7 @@ class Wallets: stake_amount = min_stake_amount if stake_amount > max_allowed_stake: - if self._log: + if not self._is_backtest: logger.info( f"Stake amount for pair {pair} is too big " f"({stake_amount} > {max_allowed_stake}), adjusting to {max_allowed_stake}." From d80ed7e33c7bacda632d50075a8bf6a852199e0b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Feb 2024 15:17:42 +0100 Subject: [PATCH 234/247] Bump Docker Python version from 3.11.7 to 3.11.8 --- Dockerfile | 2 +- docker/Dockerfile.armhf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index e5a33df87..a1205f219 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11.7-slim-bookworm as base +FROM python:3.11.8-slim-bookworm as base # Setup env ENV LANG C.UTF-8 diff --git a/docker/Dockerfile.armhf b/docker/Dockerfile.armhf index 4cb8f5fea..1165f305c 100644 --- a/docker/Dockerfile.armhf +++ b/docker/Dockerfile.armhf @@ -1,4 +1,4 @@ -FROM python:3.11.7-slim-bookworm as base +FROM python:3.11.8-slim-bookworm as base # Setup env ENV LANG C.UTF-8 From c06e4ee89ce91e8b4aa76e2f09c44549479dfeda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 03:54:50 +0000 Subject: [PATCH 235/247] Bump the pytest group with 1 update Bumps the pytest group with 1 update: [pytest](https://github.com/pytest-dev/pytest). Updates `pytest` from 8.0.1 to 8.0.2 - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.0.1...8.0.2) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch dependency-group: pytest ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e0993988a..43c06212a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,7 +10,7 @@ coveralls==3.3.1 ruff==0.2.2 mypy==1.8.0 pre-commit==3.6.2 -pytest==8.0.1 +pytest==8.0.2 pytest-asyncio==0.23.5 pytest-cov==4.1.0 pytest-mock==3.12.0 From 9805cd768b3cb438867581925b56d0bdcdf4dc34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 03:54:59 +0000 Subject: [PATCH 236/247] Bump orjson from 3.9.14 to 3.9.15 Bumps [orjson](https://github.com/ijl/orjson) from 3.9.14 to 3.9.15. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.9.14...3.9.15) --- updated-dependencies: - dependency-name: orjson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c227ae732..4891e6556 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ py_find_1st==1.1.6 # Load ticker files 30% faster python-rapidjson==1.14 # Properly format api responses -orjson==3.9.14 +orjson==3.9.15 # Notify systemd sdnotify==0.3.2 From c2998f3d7545ae031f590f1d240927c0e218ad8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 03:55:06 +0000 Subject: [PATCH 237/247] Bump pydantic from 2.6.1 to 2.6.2 Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.6.1 to 2.6.2. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v2.6.1...v2.6.2) --- updated-dependencies: - dependency-name: pydantic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c227ae732..ad7a3640e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ sdnotify==0.3.2 # API Server fastapi==0.109.2 -pydantic==2.6.1 +pydantic==2.6.2 uvicorn==0.27.1 pyjwt==2.8.0 aiofiles==23.2.1 From 313de7b8a0041923f07a46b9011b16baf27e5a79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 03:55:12 +0000 Subject: [PATCH 238/247] Bump mkdocs-material from 9.5.9 to 9.5.11 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.9 to 9.5.11. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.9...9.5.11) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index aca3da72a..cbb81b6b2 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.5.2 mkdocs==1.5.3 -mkdocs-material==9.5.9 +mkdocs-material==9.5.11 mdx_truly_sane_lists==1.3 pymdown-extensions==10.7 jinja2==3.1.3 From e2a486f85eba0f7206a22d13235b738bf8d40553 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 03:55:27 +0000 Subject: [PATCH 239/247] Bump nbconvert from 7.16.0 to 7.16.1 Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.16.0 to 7.16.1. - [Release notes](https://github.com/jupyter/nbconvert/releases) - [Changelog](https://github.com/jupyter/nbconvert/blob/main/CHANGELOG.md) - [Commits](https://github.com/jupyter/nbconvert/compare/v7.16.0...v7.16.1) --- updated-dependencies: - dependency-name: nbconvert dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e0993988a..71c590c02 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -21,7 +21,7 @@ isort==5.13.2 time-machine==2.13.0 # Convert jupyter notebooks to markdown documents -nbconvert==7.16.0 +nbconvert==7.16.1 # mypy types types-cachetools==5.3.0.7 From 4ff888488b42c442efb13293537b634288363454 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 03:55:48 +0000 Subject: [PATCH 240/247] Bump cryptography from 42.0.4 to 42.0.5 Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.4 to 42.0.5. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.4...42.0.5) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c227ae732..5ac1e2d1e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ pandas==2.1.4 pandas-ta==0.3.14b ccxt==4.2.47 -cryptography==42.0.4 +cryptography==42.0.5 aiohttp==3.9.3 SQLAlchemy==2.0.27 python-telegram-bot==20.8 From 080d9b55f8abcc400265ccef71dbf5ca6f71c72e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 05:29:59 +0000 Subject: [PATCH 241/247] Bump fastapi from 0.109.2 to 0.110.0 Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.109.2 to 0.110.0. - [Release notes](https://github.com/tiangolo/fastapi/releases) - [Commits](https://github.com/tiangolo/fastapi/compare/0.109.2...0.110.0) --- updated-dependencies: - dependency-name: fastapi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a08fe7b2a..c7f1db813 100644 --- a/requirements.txt +++ b/requirements.txt @@ -36,7 +36,7 @@ orjson==3.9.15 sdnotify==0.3.2 # API Server -fastapi==0.109.2 +fastapi==0.110.0 pydantic==2.6.2 uvicorn==0.27.1 pyjwt==2.8.0 From d9f4c62f15cdce834f218f94eb43191686e65417 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 26 Feb 2024 07:07:48 +0100 Subject: [PATCH 242/247] Add warning about excessive use of position_adjustment --- docs/strategy-callbacks.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 2292b7ed0..0324e0915 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -797,6 +797,11 @@ Returning a value more than the above (so remaining stake_amount would become ne While `/stopentry` command stops the bot from entering new trades, the position adjustment feature will continue buying new orders on existing trades. +!!! Danger "Performance with many position adjustments" + Position adjustments can be a good approach to increase a strategy's output - but it can also have drawbacks if using this feature extensively. + Each of the orders will be attached to the trade object for the duration of the trade - hence increasing memory usage. + Trades with long duration and 10s or even 100ds of position adjustments are therefore not recommended, and should be closed at regular intervals to not affect performance. + !!! Warning "Backtesting" During backtesting this callback is called for each candle in `timeframe` or `timeframe_detail`, so run-time performance will be affected. This can also cause deviating results between live and backtesting, since backtesting can adjust the trade only once per candle, whereas live could adjust the trade multiple times per candle. From d5c01f7480769dfb66ad06ec69babc291002cfa8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 07:47:19 +0000 Subject: [PATCH 243/247] Bump ccxt from 4.2.47 to 4.2.51 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.2.47 to 4.2.51. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.2.47...4.2.51) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f147245ac..94f63d033 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.26.4 pandas==2.1.4 pandas-ta==0.3.14b -ccxt==4.2.47 +ccxt==4.2.51 cryptography==42.0.5 aiohttp==3.9.3 SQLAlchemy==2.0.27 From 5402d276d61efb102d215d6fc1a84b61cd1eca59 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 26 Feb 2024 18:03:34 +0100 Subject: [PATCH 244/247] Add header to warning box, reorder Boxes --- docs/strategy-callbacks.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 0324e0915..2f04e906e 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -791,21 +791,21 @@ Returning a value more than the above (so remaining stake_amount would become ne If you wish to buy additional orders with DCA, then make sure to leave enough funds in the wallet for that. Using 'unlimited' stake amount with DCA orders requires you to also implement the `custom_stake_amount()` callback to avoid allocating all funds to the initial order. -!!! Warning +!!! Warning "Stoploss calculation" Stoploss is still calculated from the initial opening price, not averaged price. Regular stoploss rules still apply (cannot move down). While `/stopentry` command stops the bot from entering new trades, the position adjustment feature will continue buying new orders on existing trades. -!!! Danger "Performance with many position adjustments" - Position adjustments can be a good approach to increase a strategy's output - but it can also have drawbacks if using this feature extensively. - Each of the orders will be attached to the trade object for the duration of the trade - hence increasing memory usage. - Trades with long duration and 10s or even 100ds of position adjustments are therefore not recommended, and should be closed at regular intervals to not affect performance. - !!! Warning "Backtesting" During backtesting this callback is called for each candle in `timeframe` or `timeframe_detail`, so run-time performance will be affected. This can also cause deviating results between live and backtesting, since backtesting can adjust the trade only once per candle, whereas live could adjust the trade multiple times per candle. +!!! Warning "Performance with many position adjustments" + Position adjustments can be a good approach to increase a strategy's output - but it can also have drawbacks if using this feature extensively. + Each of the orders will be attached to the trade object for the duration of the trade - hence increasing memory usage. + Trades with long duration and 10s or even 100ds of position adjustments are therefore not recommended, and should be closed at regular intervals to not affect performance. + ``` python from freqtrade.persistence import Trade From 01266ed7eb90313c6f7db6f5c77405c300e2597c Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 26 Feb 2024 19:02:11 +0100 Subject: [PATCH 245/247] Align test results ... --- tests/optimize/test_backtesting_adjust_position.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/optimize/test_backtesting_adjust_position.py b/tests/optimize/test_backtesting_adjust_position.py index 7f7bbb29f..ce2b73d02 100644 --- a/tests/optimize/test_backtesting_adjust_position.py +++ b/tests/optimize/test_backtesting_adjust_position.py @@ -76,7 +76,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> 'leverage': [1.0, 1.0], 'is_short': [False, False], 'open_timestamp': [1517251200000, 1517283000000], - 'close_timestamp': [1517265300000, 1517285400000], + 'close_timestamp': [1517265200000, 1517285400000], }) pd.testing.assert_frame_equal(results.drop(columns=['orders']), expected) data_pair = processed[pair] From d6b01a6ffe476797084ac82a1366a5147b2f9975 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 26 Feb 2024 19:17:50 +0100 Subject: [PATCH 246/247] Assert for exact equality --- .../optimize/test_backtesting_adjust_position.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/optimize/test_backtesting_adjust_position.py b/tests/optimize/test_backtesting_adjust_position.py index ce2b73d02..2a158acf3 100644 --- a/tests/optimize/test_backtesting_adjust_position.py +++ b/tests/optimize/test_backtesting_adjust_position.py @@ -57,28 +57,30 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> ), 'close_date': pd.to_datetime([dt_utc(2018, 1, 29, 22, 00, 0), dt_utc(2018, 1, 30, 4, 10, 0)], utc=True), - 'open_rate': [0.10401764894444211, 0.10302485], - 'close_rate': [0.10453904066847439, 0.103541], + 'open_rate': [0.10401764891917063, 0.10302485], + 'close_rate': [0.10453904064307624, 0.10354126528822055], 'fee_open': [0.0025, 0.0025], 'fee_close': [0.0025, 0.0025], 'trade_duration': [200, 40], 'profit_ratio': [0.0, 0.0], 'profit_abs': [0.0, 0.0], 'exit_reason': [ExitType.ROI.value, ExitType.ROI.value], - 'initial_stop_loss_abs': [0.0940005, 0.09272236], + 'initial_stop_loss_abs': [0.0940005, 0.092722365], 'initial_stop_loss_ratio': [-0.1, -0.1], - 'stop_loss_abs': [0.0940005, 0.09272236], + 'stop_loss_abs': [0.0940005, 0.092722365], 'stop_loss_ratio': [-0.1, -0.1], 'min_rate': [0.10370188, 0.10300000000000001], - 'max_rate': [0.10481985, 0.1038888], + 'max_rate': [0.10481985, 0.10388887000000001], 'is_open': [False, False], 'enter_tag': ['', ''], 'leverage': [1.0, 1.0], 'is_short': [False, False], 'open_timestamp': [1517251200000, 1517283000000], - 'close_timestamp': [1517265200000, 1517285400000], + 'close_timestamp': [1517263200000, 1517285400000], }) - pd.testing.assert_frame_equal(results.drop(columns=['orders']), expected) + results_no = results.drop(columns=['orders']) + pd.testing.assert_frame_equal(results_no, expected, check_exact=True) + data_pair = processed[pair] assert len(results.iloc[0]['orders']) == 6 assert len(results.iloc[1]['orders']) == 2 From 0a8719c6179c800f66d3c3edef5bed7a5c0cbfa4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 27 Feb 2024 06:54:21 +0100 Subject: [PATCH 247/247] Bump Version to 2024.2 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 8d18c7e60..32033cb45 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2024.1' +__version__ = '2024.2' if 'dev' in __version__: from pathlib import Path