From 6f1b14c01393e387e3f6ad8b903313cd56c0c66d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 25 Mar 2022 19:46:56 +0100 Subject: [PATCH] Update buy_timeout and sell_timeout methods --- docs/bot-basics.md | 6 +- docs/strategy-callbacks.md | 16 ++-- docs/strategy_migration.md | 28 +++++++ freqtrade/freqtradebot.py | 3 +- freqtrade/optimize/backtesting.py | 2 +- freqtrade/resolvers/strategy_resolver.py | 81 +++++++++++-------- freqtrade/strategy/interface.py | 28 +++++-- .../subtemplates/strategy_methods_advanced.j2 | 16 ++-- .../broken_futures_strategies.py | 18 +++++ tests/strategy/test_strategy_loading.py | 11 ++- tests/test_freqtradebot.py | 72 +++++++---------- 11 files changed, 175 insertions(+), 106 deletions(-) diff --git a/docs/bot-basics.md b/docs/bot-basics.md index f8d85a711..a44b611f3 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -32,8 +32,8 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and * Call `populate_entry_trend()` * Call `populate_exit_trend()` * Check timeouts for open orders. - * Calls `check_buy_timeout()` strategy callback for open entry orders. - * Calls `check_sell_timeout()` strategy callback for open exit orders. + * Calls `check_entry_timeout()` strategy callback for open entry orders. + * Calls `check_exit_timeout()` strategy callback for open exit orders. * Verifies existing positions and eventually places exit orders. * Considers stoploss, ROI and exit-signal, `custom_exit()` and `custom_stoploss()`. * Determine exit-price based on `ask_strategy` configuration setting or by using the `custom_exit_price()` callback. @@ -64,7 +64,7 @@ This loop will be repeated again and again until the bot is stopped. * Check position adjustments for open trades if enabled and call `adjust_trade_position()` to determine if an additional order is requested. * Call `custom_stoploss()` and `custom_exit()` to find custom exit points. * For exits based on exit-signal and custom-exit: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle). - * Check for Order timeouts, either via the `unfilledtimeout` configuration, or via `check_buy_timeout()` / `check_sell_timeout()` strategy callbacks. + * Check for Order timeouts, either via the `unfilledtimeout` configuration, or via `check_entry_timeout()` / `check_exit_timeout()` strategy callbacks. * Generate backtest report output !!! Note diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 6474ffcaa..31d52e30c 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -12,7 +12,7 @@ Currently available callbacks: * [`custom_exit()`](#custom-exit-signal) * [`custom_stoploss()`](#custom-stoploss) * [`custom_entry_price()` and `custom_exit_price()`](#custom-order-price-rules) -* [`check_buy_timeout()` and `check_sell_timeout()`](#custom-order-timeout-rules) +* [`check_entry_timeout()` and `check_exit_timeout()`](#custom-order-timeout-rules) * [`confirm_trade_entry()`](#trade-entry-buy-order-confirmation) * [`confirm_trade_exit()`](#trade-exit-sell-order-confirmation) * [`adjust_trade_position()`](#adjust-trade-position) @@ -408,7 +408,7 @@ However, freqtrade also offers a custom callback for both order types, which all ### Custom order timeout example Called for every open order until that order is either filled or cancelled. -`check_buy_timeout()` is called for trade entries, while `check_sell_timeout()` is called for trade exit orders. +`check_entry_timeout()` is called for trade entries, while `check_exit_timeout()` is called for trade exit orders. A simple example, which applies different unfilled-timeouts depending on the price of the asset can be seen below. It applies a tight timeout for higher priced assets, while allowing more time to fill on cheap coins. @@ -429,8 +429,8 @@ class AwesomeStrategy(IStrategy): 'sell': 60 * 25 } - def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, - current_time: datetime, **kwargs) -> bool: + def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, + current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True elif trade.open_rate > 10 and trade.open_date_utc < current_time - timedelta(minutes=3): @@ -440,7 +440,7 @@ class AwesomeStrategy(IStrategy): return False - def check_sell_timeout(self, pair: str, trade: Trade, order: dict, + def check_exit_timeout(self, pair: str, trade: Trade, order: dict, current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True @@ -470,8 +470,8 @@ class AwesomeStrategy(IStrategy): 'sell': 60 * 25 } - def check_buy_timeout(self, pair: str, trade: Trade, order: dict, - current_time: datetime, **kwargs) -> bool: + def check_entry_timeout(self, pair: str, trade: Trade, order: dict, + current_time: datetime, **kwargs) -> bool: ob = self.dp.orderbook(pair, 1) current_price = ob['bids'][0][0] # Cancel buy order if price is more than 2% above the order. @@ -480,7 +480,7 @@ class AwesomeStrategy(IStrategy): return False - def check_sell_timeout(self, pair: str, trade: Trade, order: dict, + def check_exit_timeout(self, pair: str, trade: Trade, order: dict, current_time: datetime, **kwargs) -> bool: ob = self.dp.orderbook(pair, 1) current_price = ob['asks'][0][0] diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md index 3a66ae9b3..19bd20880 100644 --- a/docs/strategy_migration.md +++ b/docs/strategy_migration.md @@ -11,6 +11,8 @@ If you intend on using markets other than spot markets, please migrate your stra * `populate_buy_trend()` -> `populate_entry_trend()` * `populate_sell_trend()` -> `populate_exit_trend()` * `custom_sell()` -> `custom_exit()` + * `check_buy_timeout()` -> `check_entry_timeout()` + * `check_sell_timeout()` -> `check_exit_timeout()` * Dataframe columns: * `buy` -> `enter_long` * `sell` -> `exit_long` @@ -124,6 +126,32 @@ class AwesomeStrategy(IStrategy): # ... ``` +### `custom_entry_timeout` + +`check_buy_timeout()` has been renamed to `check_entry_timeout()`, and `check_sell_timeout()` has been renamed to `check_exit_timeout()`. + +``` python hl_lines="2 6" +class AwesomeStrategy(IStrategy): + def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, + current_time: datetime, **kwargs) -> bool: + return False + + def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict, + current_time: datetime, **kwargs) -> bool: + return False +``` + +``` python hl_lines="2 6" +class AwesomeStrategy(IStrategy): + def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, + current_time: datetime, **kwargs) -> bool: + return False + + def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, + current_time: datetime, **kwargs) -> bool: + return False +``` + ### Custom-stake-amount New string argument `side` - which can be either `"long"` or `"short"`. diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f2cd5e5ad..089a5804a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1138,13 +1138,12 @@ class FreqtradeBot(LoggingMixin): fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order) is_entering = order['side'] == trade.enter_side not_closed = order['status'] == 'open' or fully_cancelled - time_method = 'sell' if order['side'] == 'sell' else 'buy' max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0) order_obj = trade.select_order_by_order_id(trade.open_order_id) if not_closed and (fully_cancelled or (order_obj and self.strategy.ft_check_timed_out( - time_method, trade, order_obj, datetime.now(timezone.utc))) + trade, order_obj, datetime.now(timezone.utc))) ): if is_entering: self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index b50932cab..808e94bad 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -853,7 +853,7 @@ class Backtesting: """ for order in [o for o in trade.orders if o.ft_is_open]: - timedout = self.strategy.ft_check_timed_out(order.side, trade, order, current_time) + timedout = self.strategy.ft_check_timed_out(trade, order, current_time) if timedout: if order.side == trade.enter_side: self.timedout_entry_orders += 1 diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index c630fa967..87a9cc4b3 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -169,6 +169,51 @@ class StrategyResolver(IResolver): " in your strategy. Please note that short signals will be ignored in that case." ) + @staticmethod + def validate_strategy(strategy: IStrategy) -> IStrategy: + if strategy.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT: + # Require new method + if not check_override(strategy, IStrategy, 'populate_entry_trend'): + raise OperationalException("`populate_entry_trend` must be implemented.") + if not check_override(strategy, IStrategy, 'populate_exit_trend'): + raise OperationalException("`populate_exit_trend` must be implemented.") + if check_override(strategy, IStrategy, 'check_buy_timeout'): + raise OperationalException("Please migrate your implementation " + "of `check_buy_timeout` to `check_entry_timeout`.") + if check_override(strategy, IStrategy, 'check_sell_timeout'): + raise OperationalException("Please migrate your implementation " + "of `check_sell_timeout` to `check_exit_timeout`.") + + if check_override(strategy, IStrategy, 'custom_sell'): + raise OperationalException( + "Please migrate your implementation of `custom_sell` to `custom_exit`.") + else: + # TODO: Implementing one of the following methods should show a deprecation warning + # buy_trend and sell_trend, custom_sell + if ( + not check_override(strategy, IStrategy, 'populate_buy_trend') + and not check_override(strategy, IStrategy, 'populate_entry_trend') + ): + raise OperationalException( + "`populate_entry_trend` or `populate_buy_trend` must be implemented.") + if ( + not check_override(strategy, IStrategy, 'populate_sell_trend') + and not check_override(strategy, IStrategy, 'populate_exit_trend') + ): + raise OperationalException( + "`populate_exit_trend` or `populate_sell_trend` must be implemented.") + + strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args) + strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) + strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args) + if any(x == 2 for x in [ + strategy._populate_fun_len, + strategy._buy_fun_len, + strategy._sell_fun_len + ]): + strategy.INTERFACE_VERSION = 1 + return strategy + @staticmethod def _load_strategy(strategy_name: str, config: dict, extra_dir: Optional[str] = None) -> IStrategy: @@ -208,42 +253,8 @@ class StrategyResolver(IResolver): ) if strategy: - if strategy.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT: - # Require new method - if not check_override(strategy, IStrategy, 'populate_entry_trend'): - raise OperationalException("`populate_entry_trend` must be implemented.") - if not check_override(strategy, IStrategy, 'populate_exit_trend'): - raise OperationalException("`populate_exit_trend` must be implemented.") - if check_override(strategy, IStrategy, 'custom_sell'): - raise OperationalException( - "Please migrate your implementation of `custom_sell` to `custom_exit`.") - else: - # TODO: Implementing one of the following methods should show a deprecation warning - # buy_trend and sell_trend, custom_sell - if ( - not check_override(strategy, IStrategy, 'populate_buy_trend') - and not check_override(strategy, IStrategy, 'populate_entry_trend') - ): - raise OperationalException( - "`populate_entry_trend` or `populate_buy_trend` must be implemented.") - if ( - not check_override(strategy, IStrategy, 'populate_sell_trend') - and not check_override(strategy, IStrategy, 'populate_exit_trend') - ): - raise OperationalException( - "`populate_exit_trend` or `populate_sell_trend` must be implemented.") - strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args) - strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) - strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args) - if any(x == 2 for x in [ - strategy._populate_fun_len, - strategy._buy_fun_len, - strategy._sell_fun_len - ]): - strategy.INTERFACE_VERSION = 1 - - return strategy + return StrategyResolver.validate_strategy(strategy) raise OperationalException( f"Impossible to load Strategy '{strategy_name}'. This class does not exist " diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index da7f02912..a61483e1d 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -209,7 +209,14 @@ class IStrategy(ABC, HyperStrategyMixin): def check_buy_timeout(self, pair: str, trade: Trade, order: dict, current_time: datetime, **kwargs) -> bool: """ - Check buy timeout function callback. + DEPRECATED: Please use `check_entry_timeout` instead. + """ + return False + + def check_entry_timeout(self, pair: str, trade: Trade, order: dict, + current_time: datetime, **kwargs) -> bool: + """ + Check entry timeout function callback. This method can be used to override the enter-timeout. It is called whenever a limit entry order has been created, and is not yet fully filled. @@ -224,11 +231,19 @@ class IStrategy(ABC, HyperStrategyMixin): :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the entry order is cancelled. """ - return False + return self.check_buy_timeout( + pair=pair, trade=trade, order=order, current_time=current_time) def check_sell_timeout(self, pair: str, trade: Trade, order: dict, current_time: datetime, **kwargs) -> bool: """ + DEPRECATED: Please use `check_exit_timeout` instead. + """ + return False + + def check_exit_timeout(self, pair: str, trade: Trade, order: dict, + current_time: datetime, **kwargs) -> bool: + """ Check sell timeout function callback. This method can be used to override the exit-timeout. It is called whenever a (long) limit sell order or (short) limit buy @@ -244,7 +259,8 @@ class IStrategy(ABC, HyperStrategyMixin): :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the (long)sell/(short)buy-order is cancelled. """ - return False + return self.check_exit_timeout( + pair=pair, trade=trade, order=order, current_time=current_time) def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, current_time: datetime, entry_tag: Optional[str], @@ -1023,12 +1039,13 @@ class IStrategy(ABC, HyperStrategyMixin): else: return current_profit > roi - def ft_check_timed_out(self, side: str, trade: LocalTrade, order: Order, + def ft_check_timed_out(self, trade: LocalTrade, order: Order, current_time: datetime) -> bool: """ FT Internal method. Check if timeout is active, and if the order is still open and timed out """ + side = 'buy' if order.side == 'buy' else 'sell' timeout = self.config.get('unfilledtimeout', {}).get(side) if timeout is not None: timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes') @@ -1038,7 +1055,8 @@ class IStrategy(ABC, HyperStrategyMixin): and order.order_date_utc < timeout_threshold) if timedout: return True - time_method = self.check_sell_timeout if order.side == 'sell' else self.check_buy_timeout + time_method = (self.check_exit_timeout if order.side == trade.exit_side + else self.check_entry_timeout) return strategy_safe_wrapper(time_method, default_retval=False)( diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 index 0ceeca982..d3178740b 100644 --- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 @@ -170,11 +170,11 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: """ return True -def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: +def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: """ - Check buy timeout function callback. - This method can be used to override the buy-timeout. - It is called whenever a limit buy order has been created, + Check entry timeout function callback. + This method can be used to override the entry-timeout. + It is called whenever a limit entry order has been created, and is not yet fully filled. Configuration options in `unfilledtimeout` will be verified before this, so ensure to set these timeouts high enough. @@ -190,11 +190,11 @@ def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> """ return False -def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: +def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: """ - Check sell timeout function callback. - This method can be used to override the sell-timeout. - It is called whenever a limit sell order has been created, + Check exit timeout function callback. + This method can be used to override the exit-timeout. + It is called whenever a limit exit order has been created, and is not yet fully filled. Configuration options in `unfilledtimeout` will be verified before this, so ensure to set these timeouts high enough. diff --git a/tests/strategy/strats/broken_strats/broken_futures_strategies.py b/tests/strategy/strats/broken_strats/broken_futures_strategies.py index 4a84b7491..7e6955d37 100644 --- a/tests/strategy/strats/broken_strats/broken_futures_strategies.py +++ b/tests/strategy/strats/broken_strats/broken_futures_strategies.py @@ -29,3 +29,21 @@ class TestStrategyImplementCustomSell(TestStrategyNoImplementSell): current_rate: float, current_profit: float, **kwargs): return False + + +class TestStrategyImplementBuyTimeout(TestStrategyNoImplementSell): + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + return super().populate_exit_trend(dataframe, metadata) + + def check_buy_timeout(self, pair: str, trade, order: dict, + current_time: datetime, **kwargs) -> bool: + return False + + +class TestStrategyImplementSellTimeout(TestStrategyNoImplementSell): + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + return super().populate_exit_trend(dataframe, metadata) + + def check_sell_timeout(self, pair: str, trade, order: dict, + current_time: datetime, **kwargs) -> bool: + return False diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index 89803e15d..b1b67dcf0 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -418,11 +418,20 @@ def test_missing_implements(default_conf): StrategyResolver.load_strategy(default_conf) default_conf['strategy'] = 'TestStrategyImplementCustomSell' - with pytest.raises(OperationalException, match=r"Please migrate your implementation of `custom_sell`.*"): StrategyResolver.load_strategy(default_conf) + default_conf['strategy'] = 'TestStrategyImplementBuyTimeout' + with pytest.raises(OperationalException, + match=r"Please migrate your implementation of `check_buy_timeout`.*"): + StrategyResolver.load_strategy(default_conf) + + default_conf['strategy'] = 'TestStrategyImplementSellTimeout' + with pytest.raises(OperationalException, + match=r"Please migrate your implementation of `check_sell_timeout`.*"): + StrategyResolver.load_strategy(default_conf) + @pytest.mark.filterwarnings("ignore:deprecated") def test_call_deprecated_function(result, default_conf, caplog): diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 304f525bd..9637a45e6 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2370,7 +2370,7 @@ def test_bot_loop_start_called_once(mocker, default_conf_usdt, caplog): @pytest.mark.parametrize("is_short", [False, True]) -def test_check_handle_timedout_buy_usercustom( +def test_check_handle_timedout_entry_usercustom( default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade, limit_sell_order_old, fee, mocker, is_short ) -> None: @@ -2406,34 +2406,23 @@ def test_check_handle_timedout_buy_usercustom( assert cancel_order_mock.call_count == 0 # Return false - trade remains open - if is_short: - freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False) - else: - freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False) + freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False) freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 0 trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() nb_trades = len(trades) assert nb_trades == 1 - if is_short: - assert freqtrade.strategy.check_sell_timeout.call_count == 1 - # Raise Keyerror ... (no impact on trade) - freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError) - else: - assert freqtrade.strategy.check_buy_timeout.call_count == 1 - freqtrade.strategy.check_buy_timeout = MagicMock(side_effect=KeyError) + assert freqtrade.strategy.check_entry_timeout.call_count == 1 + freqtrade.strategy.check_entry_timeout = MagicMock(side_effect=KeyError) freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 0 trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() nb_trades = len(trades) assert nb_trades == 1 - if is_short: - assert freqtrade.strategy.check_sell_timeout.call_count == 1 - freqtrade.strategy.check_sell_timeout = MagicMock(return_value=True) - else: - assert freqtrade.strategy.check_buy_timeout.call_count == 1 - freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True) + assert freqtrade.strategy.check_entry_timeout.call_count == 1 + freqtrade.strategy.check_entry_timeout = MagicMock(return_value=True) + # Trade should be closed since the function returns true freqtrade.check_handle_timedout() assert cancel_order_wr_mock.call_count == 1 @@ -2441,10 +2430,7 @@ def test_check_handle_timedout_buy_usercustom( trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() nb_trades = len(trades) assert nb_trades == 0 - if is_short: - assert freqtrade.strategy.check_sell_timeout.call_count == 1 - else: - assert freqtrade.strategy.check_buy_timeout.call_count == 1 + assert freqtrade.strategy.check_entry_timeout.call_count == 1 @pytest.mark.parametrize("is_short", [False, True]) @@ -2472,9 +2458,9 @@ def test_check_handle_timedout_buy( Trade.query.session.add(open_trade) if is_short: - freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False) + freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False) else: - freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False) + freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False) # check it does cancel buy orders over the time limit freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 1 @@ -2484,9 +2470,9 @@ def test_check_handle_timedout_buy( assert nb_trades == 0 # Custom user buy-timeout is never called if is_short: - assert freqtrade.strategy.check_sell_timeout.call_count == 0 + assert freqtrade.strategy.check_exit_timeout.call_count == 0 else: - assert freqtrade.strategy.check_buy_timeout.call_count == 0 + assert freqtrade.strategy.check_entry_timeout.call_count == 0 @pytest.mark.parametrize("is_short", [False, True]) @@ -2553,7 +2539,7 @@ def test_check_handle_timedout_buy_exception( @pytest.mark.parametrize("is_short", [False, True]) -def test_check_handle_timedout_sell_usercustom( +def test_check_handle_timedout_exit_usercustom( default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker, is_short, open_trade_usdt, caplog ) -> None: @@ -2585,35 +2571,35 @@ def test_check_handle_timedout_sell_usercustom( freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 0 - freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False) - freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False) + freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False) + freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False) # Return false - No impact freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 0 assert rpc_mock.call_count == 0 assert open_trade_usdt.is_open is False - assert freqtrade.strategy.check_sell_timeout.call_count == (0 if is_short else 1) - assert freqtrade.strategy.check_buy_timeout.call_count == (1 if is_short else 0) + assert freqtrade.strategy.check_exit_timeout.call_count == 1 + assert freqtrade.strategy.check_entry_timeout.call_count == 0 - freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError) - freqtrade.strategy.check_buy_timeout = MagicMock(side_effect=KeyError) + freqtrade.strategy.check_exit_timeout = MagicMock(side_effect=KeyError) + freqtrade.strategy.check_entry_timeout = MagicMock(side_effect=KeyError) # Return Error - No impact freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 0 assert rpc_mock.call_count == 0 assert open_trade_usdt.is_open is False - assert freqtrade.strategy.check_sell_timeout.call_count == (0 if is_short else 1) - assert freqtrade.strategy.check_buy_timeout.call_count == (1 if is_short else 0) + assert freqtrade.strategy.check_exit_timeout.call_count == 1 + assert freqtrade.strategy.check_entry_timeout.call_count == 0 # Return True - sells! - freqtrade.strategy.check_sell_timeout = MagicMock(return_value=True) - freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True) + freqtrade.strategy.check_exit_timeout = MagicMock(return_value=True) + freqtrade.strategy.check_entry_timeout = MagicMock(return_value=True) freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 1 assert rpc_mock.call_count == 1 assert open_trade_usdt.is_open is True - assert freqtrade.strategy.check_sell_timeout.call_count == (0 if is_short else 1) - assert freqtrade.strategy.check_buy_timeout.call_count == (1 if is_short else 0) + assert freqtrade.strategy.check_exit_timeout.call_count == 1 + assert freqtrade.strategy.check_entry_timeout.call_count == 0 # 2nd canceled trade - Fail execute sell caplog.clear() @@ -2665,16 +2651,16 @@ def test_check_handle_timedout_sell( Trade.query.session.add(open_trade_usdt) - freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False) - freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False) + freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False) + freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False) # check it does cancel sell orders over the time limit freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 1 assert rpc_mock.call_count == 1 assert open_trade_usdt.is_open is True # Custom user sell-timeout is never called - assert freqtrade.strategy.check_sell_timeout.call_count == 0 - assert freqtrade.strategy.check_buy_timeout.call_count == 0 + assert freqtrade.strategy.check_exit_timeout.call_count == 0 + assert freqtrade.strategy.check_entry_timeout.call_count == 0 @pytest.mark.parametrize("is_short", [False, True])