Merge pull request #6275 from freqtrade/extract_timedout_from_ftbot

Extract timedout from ftbot
This commit is contained in:
Matthias 2022-01-23 17:28:10 +01:00 committed by GitHub
commit 4b6f9121ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 37 deletions

View File

@ -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). The function must return either `True` (cancel order) or `False` (keep order alive).
``` python ``` python
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
@ -426,22 +426,24 @@ class AwesomeStrategy(IStrategy):
'sell': 60 * 25 'sell': 60 * 25
} }
def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict,
if trade.open_rate > 100 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(minutes=5): current_time: datetime, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
return True 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 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 True
return False 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,
if trade.open_rate > 100 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(minutes=5): current_time: datetime, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
return True 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 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 True
return False return False
``` ```

View File

@ -9,8 +9,6 @@ from math import isclose
from threading import Lock from threading import Lock
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
import arrow
from freqtrade import __version__, constants from freqtrade import __version__, constants
from freqtrade.configuration import validate_config_consistency from freqtrade.configuration import validate_config_consistency
from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.converter import order_book_to_dataframe
@ -959,20 +957,6 @@ class FreqtradeBot(LoggingMixin):
return True return True
return False 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: def check_handle_timedout(self) -> None:
""" """
Check if any orders are timed out and cancel if necessary 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 ( if (order['side'] == 'buy' and (order['status'] == 'open' or fully_cancelled) and (
fully_cancelled fully_cancelled
or self._check_timed_out('buy', order) or self.strategy.ft_check_timed_out(
or strategy_safe_wrapper(self.strategy.check_buy_timeout, 'buy', trade, order, datetime.now(timezone.utc))
default_retval=False)(pair=trade.pair, )):
trade=trade,
order=order))):
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and ( elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and (
fully_cancelled fully_cancelled
or self._check_timed_out('sell', order) or self.strategy.ft_check_timed_out(
or strategy_safe_wrapper(self.strategy.check_sell_timeout, 'sell', trade, order, datetime.now(timezone.utc)))
default_retval=False)(pair=trade.pair, ):
trade=trade,
order=order))):
self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT']) self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT'])
canceled_count = trade.get_exit_order_count() canceled_count = trade.get_exit_order_count()
max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0) max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0)

View File

@ -188,7 +188,8 @@ class IStrategy(ABC, HyperStrategyMixin):
""" """
return dataframe 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. Check buy timeout function callback.
This method can be used to override the buy-timeout. 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 pair: Pair the trade is for
:param trade: trade object. :param trade: trade object.
:param order: Order dictionary as returned from CCXT. :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. :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 bool: When True is returned, then the buy-order is cancelled.
""" """
return False 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. Check sell timeout function callback.
This method can be used to override the sell-timeout. 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 pair: Pair the trade is for
:param trade: trade object. :param trade: trade object.
:param order: Order dictionary as returned from CCXT. :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. :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. :return bool: When True is returned, then the sell-order is cancelled.
""" """
@ -852,6 +856,29 @@ class IStrategy(ABC, HyperStrategyMixin):
else: else:
return current_profit > roi 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 = self.check_sell_timeout if order['side'] == 'sell' else self.check_buy_timeout
return strategy_safe_wrapper(time_method,
default_retval=False)(
pair=trade.pair, trade=trade, order=order,
current_time=current_time)
def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]:
""" """
Populates indicators for given candle (OHLCV) data (for multiple pairs) Populates indicators for given candle (OHLCV) data (for multiple pairs)