From 7bef9a9b3ec8593dac0701e7c5f8df6d77b5d4e0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 Jan 2022 15:59:10 +0100 Subject: [PATCH 1/3] Extract timeout handling from freqtradebot class --- freqtrade/freqtradebot.py | 32 ++++++-------------------------- freqtrade/strategy/interface.py | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8980fb91c..e8f24864f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -9,8 +9,6 @@ from math import isclose from threading import Lock from typing import Any, Dict, List, Optional, Tuple -import arrow - from freqtrade import __version__, constants from freqtrade.configuration import validate_config_consistency from freqtrade.data.converter import order_book_to_dataframe @@ -959,20 +957,6 @@ class FreqtradeBot(LoggingMixin): return True return False - def _check_timed_out(self, side: str, order: dict) -> bool: - """ - Check if timeout is active, and if the order is still open and timed out - """ - timeout = self.config.get('unfilledtimeout', {}).get(side) - ordertime = arrow.get(order['datetime']).datetime - if timeout is not None: - timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes') - timeout_kwargs = {timeout_unit: -timeout} - timeout_threshold = arrow.utcnow().shift(**timeout_kwargs).datetime - return (order['status'] == 'open' and order['side'] == side - and ordertime < timeout_threshold) - return False - def check_handle_timedout(self) -> None: """ Check if any orders are timed out and cancel if necessary @@ -993,20 +977,16 @@ class FreqtradeBot(LoggingMixin): if (order['side'] == 'buy' and (order['status'] == 'open' or fully_cancelled) and ( fully_cancelled - or self._check_timed_out('buy', order) - or strategy_safe_wrapper(self.strategy.check_buy_timeout, - default_retval=False)(pair=trade.pair, - trade=trade, - order=order))): + or self.strategy.ft_check_timed_out( + 'buy', trade, order, datetime.now(timezone.utc)) + )): self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and ( fully_cancelled - or self._check_timed_out('sell', order) - or strategy_safe_wrapper(self.strategy.check_sell_timeout, - default_retval=False)(pair=trade.pair, - trade=trade, - order=order))): + or self.strategy.ft_check_timed_out( + 'sell', trade, order, datetime.now(timezone.utc))) + ): self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT']) canceled_count = trade.get_exit_order_count() max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index c8fb24da1..d073e8dfa 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -852,6 +852,28 @@ class IStrategy(ABC, HyperStrategyMixin): else: return current_profit > roi + def ft_check_timed_out(self, side: str, trade: Trade, order: Dict, + current_time: datetime) -> bool: + """ + FT Internal method. + Check if timeout is active, and if the order is still open and timed out + """ + timeout = self.config.get('unfilledtimeout', {}).get(side) + ordertime = arrow.get(order['datetime']).datetime + if timeout is not None: + timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes') + timeout_kwargs = {timeout_unit: -timeout} + timeout_threshold = current_time + timedelta(**timeout_kwargs) + timedout = (order['status'] == 'open' and order['side'] == side + and ordertime < timeout_threshold) + if timedout: + return True + time_method = 'check_sell_timeout' if order['side'] == 'sell' else 'check_buy_timeout' + + return strategy_safe_wrapper(getattr(self, time_method), + default_retval=False)( + pair=trade.pair, trade=trade, order=order) + def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: """ Populates indicators for given candle (OHLCV) data (for multiple pairs) From 821a9d9cdc31ee5d97883eb46dbfa221d56ee83e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 Jan 2022 16:05:13 +0100 Subject: [PATCH 2/3] Add current_time to check_timeout functions for convenience --- docs/strategy-callbacks.md | 20 +++++++++++--------- freqtrade/strategy/interface.py | 11 ++++++++--- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 6edc549a4..5e03776c7 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -413,7 +413,7 @@ It applies a tight timeout for higher priced assets, while allowing more time to The function must return either `True` (cancel order) or `False` (keep order alive). ``` python -from datetime import datetime, timedelta, timezone +from datetime import datetime, timedelta from freqtrade.persistence import Trade class AwesomeStrategy(IStrategy): @@ -426,22 +426,24 @@ class AwesomeStrategy(IStrategy): 'sell': 60 * 25 } - def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: - if trade.open_rate > 100 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(minutes=5): + def check_buy_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 < datetime.now(timezone.utc) - timedelta(minutes=3): + elif trade.open_rate > 10 and trade.open_date_utc < current_time - timedelta(minutes=3): return True - elif trade.open_rate < 1 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(hours=24): + elif trade.open_rate < 1 and trade.open_date_utc < current_time - timedelta(hours=24): return True return False - def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: - if trade.open_rate > 100 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(minutes=5): + def check_sell_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 < datetime.now(timezone.utc) - timedelta(minutes=3): + elif trade.open_rate > 10 and trade.open_date_utc < current_time - timedelta(minutes=3): return True - elif trade.open_rate < 1 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(hours=24): + elif trade.open_rate < 1 and trade.open_date_utc < current_time - timedelta(hours=24): return True return False ``` diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index d073e8dfa..ac1285025 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -188,7 +188,8 @@ class IStrategy(ABC, HyperStrategyMixin): """ return dataframe - def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: + def check_buy_timeout(self, pair: str, trade: Trade, order: dict, + current_time: datetime, **kwargs) -> bool: """ Check buy timeout function callback. This method can be used to override the buy-timeout. @@ -201,12 +202,14 @@ class IStrategy(ABC, HyperStrategyMixin): :param pair: Pair the trade is for :param trade: trade object. :param order: Order dictionary as returned from CCXT. + :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the buy-order is cancelled. """ return False - def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: + def check_sell_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 sell-timeout. @@ -219,6 +222,7 @@ class IStrategy(ABC, HyperStrategyMixin): :param pair: Pair the trade is for :param trade: trade object. :param order: Order dictionary as returned from CCXT. + :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the sell-order is cancelled. """ @@ -872,7 +876,8 @@ class IStrategy(ABC, HyperStrategyMixin): return strategy_safe_wrapper(getattr(self, time_method), default_retval=False)( - pair=trade.pair, trade=trade, order=order) + pair=trade.pair, trade=trade, order=order, + current_time=current_time) def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: """ From 09db4bcaddb2ef85a7bf28105cc1392fda9d087c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Jan 2022 17:00:00 +0100 Subject: [PATCH 3/3] Don't use strings, use methods directly --- freqtrade/strategy/interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index ac1285025..16d8ee818 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -872,9 +872,9 @@ class IStrategy(ABC, HyperStrategyMixin): and ordertime < timeout_threshold) if timedout: return True - time_method = 'check_sell_timeout' if order['side'] == 'sell' else 'check_buy_timeout' + time_method = self.check_sell_timeout if order['side'] == 'sell' else self.check_buy_timeout - return strategy_safe_wrapper(getattr(self, time_method), + return strategy_safe_wrapper(time_method, default_retval=False)( pair=trade.pair, trade=trade, order=order, current_time=current_time)