From 9d82de15d43dcb98e5c6c61912f0709315c874b2 Mon Sep 17 00:00:00 2001 From: Axel-CH Date: Fri, 6 Oct 2023 15:56:08 -0400 Subject: [PATCH] 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 = [