diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 06e556246..fe7ca21c0 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -915,7 +915,7 @@ class FreqtradeBot(LoggingMixin): logger.error(f'Unable to place a stoploss order on exchange. {e}') logger.warning('Exiting the trade forcefully') self.execute_trade_exit(trade, trade.stop_loss, exit_reason=SellCheckTuple( - sell_type=ExitType.EMERGENCY_SELL)) + exit_type=ExitType.EMERGENCY_SELL)) except ExchangeError: trade.stoploss_order_id = None @@ -1040,7 +1040,7 @@ class FreqtradeBot(LoggingMixin): ) if should_exit.sell_flag: - logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.sell_type}' + logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.exit_type}' f'Tag: {exit_tag if exit_tag is not None else "None"}') self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag=exit_tag) return True @@ -1102,7 +1102,7 @@ class FreqtradeBot(LoggingMixin): try: self.execute_trade_exit( trade, order.get('price'), - exit_reason=SellCheckTuple(sell_type=ExitType.EMERGENCY_SELL)) + exit_reason=SellCheckTuple(exit_type=ExitType.EMERGENCY_SELL)) except DependencyException as exception: logger.warning( f'Unable to emergency sell trade {trade.pair}: {exception}') @@ -1284,7 +1284,7 @@ class FreqtradeBot(LoggingMixin): trade.open_date ) exit_type = 'sell' # TODO-lev: Update to exit - if exit_reason.sell_type in (ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS): + if exit_reason.exit_type in (ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS): exit_type = 'stoploss' # if stoploss is on exchange and we are on dry_run mode, @@ -1314,7 +1314,7 @@ class FreqtradeBot(LoggingMixin): logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") order_type = ordertype or self.strategy.order_types[exit_type] - if exit_reason.sell_type == ExitType.EMERGENCY_SELL: + if exit_reason.exit_type == ExitType.EMERGENCY_SELL: # Emergency sells (default to market!) order_type = self.strategy.order_types.get("emergencysell", "market") diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 53fba9a82..577a0de41 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -314,7 +314,7 @@ class Backtesting: Get close rate for backtesting result """ # Special handling if high or low hit STOP_LOSS or ROI - if sell.sell_type in (ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS): + if sell.exit_type in (ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS): if trade.stop_loss > sell_row[HIGH_IDX]: # our stoploss was already higher than candle high, # possibly due to a cancelled trade exit. @@ -324,7 +324,7 @@ class Backtesting: # Special case: trailing triggers within same candle as trade opened. Assume most # pessimistic price movement, which is moving just enough to arm stoploss and # immediately going down to stop price. - if sell.sell_type == ExitType.TRAILING_STOP_LOSS and trade_dur == 0: + if sell.exit_type == ExitType.TRAILING_STOP_LOSS and trade_dur == 0: if ( not self.strategy.use_custom_stoploss and self.strategy.trailing_stop and self.strategy.trailing_only_offset_is_reached @@ -345,7 +345,7 @@ class Backtesting: # Set close_rate to stoploss return trade.stop_loss - elif sell.sell_type == (ExitType.ROI): + elif sell.exit_type == (ExitType.ROI): roi_entry, roi = self.strategy.min_roi_reached_entry(trade_dur) if roi is not None and roi_entry is not None: if roi == -1 and roi_entry % self.timeframe_min == 0: @@ -395,7 +395,7 @@ class Backtesting: closerate = self._get_close_rate(sell_row, trade, sell, trade_dur) # call the custom exit price,with default value as previous closerate current_profit = trade.calc_profit_ratio(closerate) - if sell.sell_type in (ExitType.SELL_SIGNAL, ExitType.CUSTOM_SELL): + if sell.exit_type in (ExitType.SELL_SIGNAL, ExitType.CUSTOM_SELL): # Custom exit pricing only for sell-signals closerate = strategy_safe_wrapper(self.strategy.custom_exit_price, default_retval=closerate)( diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 0d99b19b3..56031df71 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -672,7 +672,7 @@ class RPC: closing_side = "buy" if trade.is_short else "sell" current_rate = self._freqtrade.exchange.get_rate( trade.pair, refresh=False, side=closing_side) - exit_reason = SellCheckTuple(sell_type=ExitType.FORCE_SELL) + exit_reason = SellCheckTuple(exit_type=ExitType.FORCE_SELL) order_type = ordertype or self._freqtrade.strategy.order_types.get( "forcesell", self._freqtrade.strategy.order_types["sell"]) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index ba3e95b77..eff13e953 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -34,16 +34,16 @@ class SellCheckTuple: """ NamedTuple for Sell type + reason """ - sell_type: ExitType + exit_type: ExitType exit_reason: str = '' - def __init__(self, sell_type: ExitType, exit_reason: str = ''): - self.sell_type = sell_type - self.exit_reason = exit_reason or sell_type.value + def __init__(self, exit_type: ExitType, exit_reason: str = ''): + self.exit_type = exit_type + self.exit_reason = exit_reason or exit_type.value @property def sell_flag(self): - return self.sell_type != ExitType.NONE + return self.exit_type != ExitType.NONE class IStrategy(ABC, HyperStrategyMixin): @@ -813,26 +813,26 @@ class IStrategy(ABC, HyperStrategyMixin): custom_reason = None if sell_signal in (ExitType.CUSTOM_SELL, ExitType.SELL_SIGNAL): logger.debug(f"{trade.pair} - Sell signal received. " - f"sell_type=ExitType.{sell_signal.name}" + + f"exit_type=ExitType.{sell_signal.name}" + (f", custom_reason={custom_reason}" if custom_reason else "")) - return SellCheckTuple(sell_type=sell_signal, exit_reason=custom_reason) + return SellCheckTuple(exit_type=sell_signal, exit_reason=custom_reason) # Sequence: # Exit-signal # ROI (if not stoploss) # Stoploss - if roi_reached and stoplossflag.sell_type != ExitType.STOP_LOSS: - logger.debug(f"{trade.pair} - Required profit reached. sell_type=ExitType.ROI") - return SellCheckTuple(sell_type=ExitType.ROI) + if roi_reached and stoplossflag.exit_type != ExitType.STOP_LOSS: + logger.debug(f"{trade.pair} - Required profit reached. exit_type=ExitType.ROI") + return SellCheckTuple(exit_type=ExitType.ROI) if stoplossflag.sell_flag: - logger.debug(f"{trade.pair} - Stoploss hit. sell_type={stoplossflag.sell_type}") + logger.debug(f"{trade.pair} - Stoploss hit. exit_type={stoplossflag.exit_type}") return stoplossflag # This one is noisy, commented out... # logger.debug(f"{trade.pair} - No exit signal.") - return SellCheckTuple(sell_type=ExitType.NONE) + return SellCheckTuple(exit_type=ExitType.NONE) def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, current_profit: float, @@ -896,11 +896,11 @@ class IStrategy(ABC, HyperStrategyMixin): if ((sl_higher_short or sl_lower_long) and (not self.order_types.get('stoploss_on_exchange') or self.config['dry_run'])): - sell_type = ExitType.STOP_LOSS + exit_type = ExitType.STOP_LOSS # If initial stoploss is not the same as current one then it is trailing. if trade.initial_stop_loss != trade.stop_loss: - sell_type = ExitType.TRAILING_STOP_LOSS + exit_type = ExitType.TRAILING_STOP_LOSS logger.debug( f"{trade.pair} - HIT STOP: current price at " f"{((high if trade.is_short else low) or current_rate):.6f}, " @@ -915,9 +915,9 @@ class IStrategy(ABC, HyperStrategyMixin): logger.debug(f"{trade.pair} - Trailing stop saved " f"{new_stoploss:.6f}") - return SellCheckTuple(sell_type=sell_type) + return SellCheckTuple(exit_type=exit_type) - return SellCheckTuple(sell_type=ExitType.NONE) + return SellCheckTuple(exit_type=ExitType.NONE) def min_roi_reached_entry(self, trade_dur: int) -> Tuple[Optional[int], Optional[float]]: """ diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 9e3085e83..bd21a0726 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -440,7 +440,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili current_time=now, current_profit=profit, force_stoploss=0, high=None) assert isinstance(sl_flag, SellCheckTuple) - assert sl_flag.sell_type == expected + assert sl_flag.exit_type == expected if expected == ExitType.NONE: assert sl_flag.sell_flag is False else: @@ -450,7 +450,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili sl_flag = strategy.stop_loss_reached(current_rate=trade.open_rate * (1 + profit2), trade=trade, current_time=now, current_profit=profit2, force_stoploss=0, high=None) - assert sl_flag.sell_type == expected2 + assert sl_flag.exit_type == expected2 if expected2 == ExitType.NONE: assert sl_flag.sell_flag is False else: @@ -480,14 +480,14 @@ def test_custom_sell(default_conf, fee, caplog) -> None: low=None, high=None) assert res.sell_flag is False - assert res.sell_type == ExitType.NONE + assert res.exit_type == ExitType.NONE strategy.custom_sell = MagicMock(return_value=True) res = strategy.should_exit(trade, 1, now, enter=False, exit_=False, low=None, high=None) assert res.sell_flag is True - assert res.sell_type == ExitType.CUSTOM_SELL + assert res.exit_type == ExitType.CUSTOM_SELL assert res.exit_reason == 'custom_sell' strategy.custom_sell = MagicMock(return_value='hello world') @@ -495,7 +495,7 @@ def test_custom_sell(default_conf, fee, caplog) -> None: res = strategy.should_exit(trade, 1, now, enter=False, exit_=False, low=None, high=None) - assert res.sell_type == ExitType.CUSTOM_SELL + assert res.exit_type == ExitType.CUSTOM_SELL assert res.sell_flag is True assert res.exit_reason == 'hello world' @@ -504,7 +504,7 @@ def test_custom_sell(default_conf, fee, caplog) -> None: res = strategy.should_exit(trade, 1, now, enter=False, exit_=False, low=None, high=None) - assert res.sell_type == ExitType.CUSTOM_SELL + assert res.exit_type == ExitType.CUSTOM_SELL assert res.sell_flag is True assert res.exit_reason == 'h' * 64 assert log_has_re('Custom sell reason returned from custom_sell is too long.*', caplog) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 673be3c3c..2cb66c0bd 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2093,7 +2093,7 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee, caplog.clear() patch_get_signal(freqtrade) assert freqtrade.handle_trade(trade) - assert log_has("ETH/USDT - Required profit reached. sell_type=ExitType.ROI", + assert log_has("ETH/USDT - Required profit reached. exit_type=ExitType.ROI", caplog) @@ -2135,7 +2135,7 @@ def test_handle_trade_use_exit_signal( else: patch_get_signal(freqtrade, enter_long=False, exit_long=True) assert freqtrade.handle_trade(trade) - assert log_has("ETH/USDT - Sell signal received. sell_type=ExitType.SELL_SIGNAL", + assert log_has("ETH/USDT - Sell signal received. exit_type=ExitType.SELL_SIGNAL", caplog) @@ -2855,7 +2855,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_ freqtrade.execute_trade_exit( trade=trade, limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']), - exit_reason=SellCheckTuple(sell_type=ExitType.ROI) + exit_reason=SellCheckTuple(exit_type=ExitType.ROI) ) assert rpc_mock.call_count == 0 assert freqtrade.strategy.confirm_trade_exit.call_count == 1 @@ -2867,7 +2867,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_ freqtrade.execute_trade_exit( trade=trade, limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']), - exit_reason=SellCheckTuple(sell_type=ExitType.ROI) + exit_reason=SellCheckTuple(exit_type=ExitType.ROI) ) assert freqtrade.strategy.confirm_trade_exit.call_count == 1 @@ -2928,7 +2928,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd ) freqtrade.execute_trade_exit( trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down)()['bid'], - exit_reason=SellCheckTuple(sell_type=ExitType.STOP_LOSS)) + exit_reason=SellCheckTuple(exit_type=ExitType.STOP_LOSS)) assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0] @@ -3003,7 +3003,7 @@ def test_execute_trade_exit_custom_exit_price( freqtrade.execute_trade_exit( trade=trade, limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'], - exit_reason=SellCheckTuple(sell_type=ExitType.SELL_SIGNAL) + exit_reason=SellCheckTuple(exit_type=ExitType.SELL_SIGNAL) ) # Sell price must be different to default bid price @@ -3074,7 +3074,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99 freqtrade.execute_trade_exit( trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down())['bid'], - exit_reason=SellCheckTuple(sell_type=ExitType.STOP_LOSS)) + exit_reason=SellCheckTuple(exit_type=ExitType.STOP_LOSS)) assert rpc_mock.call_count == 2 last_msg = rpc_mock.call_args_list[-1][0][0] @@ -3134,7 +3134,7 @@ def test_execute_trade_exit_sloe_cancel_exception( trade.stoploss_order_id = "abcd" freqtrade.execute_trade_exit(trade=trade, limit=1234, - exit_reason=SellCheckTuple(sell_type=ExitType.STOP_LOSS)) + exit_reason=SellCheckTuple(exit_type=ExitType.STOP_LOSS)) assert create_order_mock.call_count == 2 assert log_has('Could not cancel stoploss order abcd', caplog) @@ -3189,7 +3189,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange( freqtrade.execute_trade_exit( trade=trade, limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'], - exit_reason=SellCheckTuple(sell_type=ExitType.STOP_LOSS) + exit_reason=SellCheckTuple(exit_type=ExitType.STOP_LOSS) ) trade = Trade.query.first() @@ -3328,7 +3328,7 @@ def test_execute_trade_exit_market_order( freqtrade.execute_trade_exit( trade=trade, limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'], - exit_reason=SellCheckTuple(sell_type=ExitType.ROI) + exit_reason=SellCheckTuple(exit_type=ExitType.ROI) ) assert not trade.is_open @@ -3392,7 +3392,7 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u fetch_ticker=ticker_usdt_sell_up ) - exit_reason = SellCheckTuple(sell_type=ExitType.ROI) + exit_reason = SellCheckTuple(exit_type=ExitType.ROI) assert not freqtrade.execute_trade_exit( trade=trade, limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'], @@ -3401,7 +3401,7 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u assert mock_insuf.call_count == 1 -@pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,sell_type,is_short', [ +@pytest.mark.parametrize('profit_only,bid,ask,handle_first,handle_second,exit_type,is_short', [ # Enable profit (True, 2.18, 2.2, False, True, ExitType.SELL_SIGNAL.value, False), (True, 2.18, 2.2, False, True, ExitType.SELL_SIGNAL.value, True), @@ -3418,7 +3418,7 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u ]) def test_sell_profit_only( default_conf_usdt, limit_order, limit_order_open, is_short, - fee, mocker, profit_only, bid, ask, handle_first, handle_second, sell_type) -> None: + fee, mocker, profit_only, bid, ask, handle_first, handle_second, exit_type) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple( @@ -3441,11 +3441,11 @@ def test_sell_profit_only( }) freqtrade = FreqtradeBot(default_conf_usdt) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) - if sell_type == ExitType.SELL_SIGNAL.value: + if exit_type == ExitType.SELL_SIGNAL.value: freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) else: freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple( - sell_type=ExitType.NONE)) + exit_type=ExitType.NONE)) freqtrade.enter_positions() trade = Trade.query.first() @@ -3561,7 +3561,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, freqtrade.execute_trade_exit( trade=trade, limit=ticker_usdt_sell_down()['ask' if is_short else 'bid'], - exit_reason=SellCheckTuple(sell_type=ExitType.STOP_LOSS) + exit_reason=SellCheckTuple(exit_type=ExitType.STOP_LOSS) ) trade.close(ticker_usdt_sell_down()['bid']) assert freqtrade.strategy.is_pair_locked(trade.pair) @@ -4920,7 +4920,7 @@ def test_update_funding_fees( trade=trade, # The values of the next 2 params are irrelevant for this test limit=ticker_usdt_sell_up()['bid'], - exit_reason=SellCheckTuple(sell_type=ExitType.ROI) + exit_reason=SellCheckTuple(exit_type=ExitType.ROI) ) assert trade.funding_fees == pytest.approx(sum( trade.amount * diff --git a/tests/test_integration.py b/tests/test_integration.py index 1bc5a03e5..b876fa067 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -52,8 +52,8 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open]) # Sell 3rd trade (not called for the first trade) should_sell_mock = MagicMock(side_effect=[ - SellCheckTuple(sell_type=ExitType.NONE), - SellCheckTuple(sell_type=ExitType.SELL_SIGNAL)] + SellCheckTuple(exit_type=ExitType.NONE), + SellCheckTuple(exit_type=ExitType.SELL_SIGNAL)] ) cancel_order_mock = MagicMock() mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss) @@ -157,11 +157,11 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc _notify_exit=MagicMock(), ) should_sell_mock = MagicMock(side_effect=[ - SellCheckTuple(sell_type=ExitType.NONE), - SellCheckTuple(sell_type=ExitType.SELL_SIGNAL), - SellCheckTuple(sell_type=ExitType.NONE), - SellCheckTuple(sell_type=ExitType.NONE), - SellCheckTuple(sell_type=ExitType.NONE)] + SellCheckTuple(exit_type=ExitType.NONE), + SellCheckTuple(exit_type=ExitType.SELL_SIGNAL), + SellCheckTuple(exit_type=ExitType.NONE), + SellCheckTuple(exit_type=ExitType.NONE), + SellCheckTuple(exit_type=ExitType.NONE)] ) mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)