Update buy_timeout and sell_timeout methods

This commit is contained in:
Matthias 2022-03-25 19:46:56 +01:00
parent 973644de66
commit 6f1b14c013
11 changed files with 175 additions and 106 deletions

View File

@ -32,8 +32,8 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and
* Call `populate_entry_trend()` * Call `populate_entry_trend()`
* Call `populate_exit_trend()` * Call `populate_exit_trend()`
* Check timeouts for open orders. * Check timeouts for open orders.
* Calls `check_buy_timeout()` strategy callback for open entry orders. * Calls `check_entry_timeout()` strategy callback for open entry orders.
* Calls `check_sell_timeout()` strategy callback for open exit orders. * Calls `check_exit_timeout()` strategy callback for open exit orders.
* Verifies existing positions and eventually places exit orders. * Verifies existing positions and eventually places exit orders.
* Considers stoploss, ROI and exit-signal, `custom_exit()` and `custom_stoploss()`. * 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. * 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. * 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. * 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). * 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 * Generate backtest report output
!!! Note !!! Note

View File

@ -12,7 +12,7 @@ Currently available callbacks:
* [`custom_exit()`](#custom-exit-signal) * [`custom_exit()`](#custom-exit-signal)
* [`custom_stoploss()`](#custom-stoploss) * [`custom_stoploss()`](#custom-stoploss)
* [`custom_entry_price()` and `custom_exit_price()`](#custom-order-price-rules) * [`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_entry()`](#trade-entry-buy-order-confirmation)
* [`confirm_trade_exit()`](#trade-exit-sell-order-confirmation) * [`confirm_trade_exit()`](#trade-exit-sell-order-confirmation)
* [`adjust_trade_position()`](#adjust-trade-position) * [`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 ### Custom order timeout example
Called for every open order until that order is either filled or cancelled. 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. 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. It applies a tight timeout for higher priced assets, while allowing more time to fill on cheap coins.
@ -429,7 +429,7 @@ class AwesomeStrategy(IStrategy):
'sell': 60 * 25 'sell': 60 * 25
} }
def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict,
current_time: datetime, **kwargs) -> bool: current_time: datetime, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
return True return True
@ -440,7 +440,7 @@ class AwesomeStrategy(IStrategy):
return False 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: current_time: datetime, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
return True return True
@ -470,7 +470,7 @@ class AwesomeStrategy(IStrategy):
'sell': 60 * 25 'sell': 60 * 25
} }
def check_buy_timeout(self, pair: str, trade: Trade, order: dict, def check_entry_timeout(self, pair: str, trade: Trade, order: dict,
current_time: datetime, **kwargs) -> bool: current_time: datetime, **kwargs) -> bool:
ob = self.dp.orderbook(pair, 1) ob = self.dp.orderbook(pair, 1)
current_price = ob['bids'][0][0] current_price = ob['bids'][0][0]
@ -480,7 +480,7 @@ class AwesomeStrategy(IStrategy):
return False 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: current_time: datetime, **kwargs) -> bool:
ob = self.dp.orderbook(pair, 1) ob = self.dp.orderbook(pair, 1)
current_price = ob['asks'][0][0] current_price = ob['asks'][0][0]

View File

@ -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_buy_trend()` -> `populate_entry_trend()`
* `populate_sell_trend()` -> `populate_exit_trend()` * `populate_sell_trend()` -> `populate_exit_trend()`
* `custom_sell()` -> `custom_exit()` * `custom_sell()` -> `custom_exit()`
* `check_buy_timeout()` -> `check_entry_timeout()`
* `check_sell_timeout()` -> `check_exit_timeout()`
* Dataframe columns: * Dataframe columns:
* `buy` -> `enter_long` * `buy` -> `enter_long`
* `sell` -> `exit_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 ### Custom-stake-amount
New string argument `side` - which can be either `"long"` or `"short"`. New string argument `side` - which can be either `"long"` or `"short"`.

View File

@ -1138,13 +1138,12 @@ class FreqtradeBot(LoggingMixin):
fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order) fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order)
is_entering = order['side'] == trade.enter_side is_entering = order['side'] == trade.enter_side
not_closed = order['status'] == 'open' or fully_cancelled 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) max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0)
order_obj = trade.select_order_by_order_id(trade.open_order_id) 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( 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: if is_entering:
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])

View File

@ -853,7 +853,7 @@ class Backtesting:
""" """
for order in [o for o in trade.orders if o.ft_is_open]: 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 timedout:
if order.side == trade.enter_side: if order.side == trade.enter_side:
self.timedout_entry_orders += 1 self.timedout_entry_orders += 1

View File

@ -169,6 +169,51 @@ class StrategyResolver(IResolver):
" in your strategy. Please note that short signals will be ignored in that case." " 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 @staticmethod
def _load_strategy(strategy_name: str, def _load_strategy(strategy_name: str,
config: dict, extra_dir: Optional[str] = None) -> IStrategy: config: dict, extra_dir: Optional[str] = None) -> IStrategy:
@ -208,42 +253,8 @@ class StrategyResolver(IResolver):
) )
if strategy: 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) return StrategyResolver.validate_strategy(strategy)
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
raise OperationalException( raise OperationalException(
f"Impossible to load Strategy '{strategy_name}'. This class does not exist " f"Impossible to load Strategy '{strategy_name}'. This class does not exist "

View File

@ -209,7 +209,14 @@ class IStrategy(ABC, HyperStrategyMixin):
def check_buy_timeout(self, pair: str, trade: Trade, order: dict, def check_buy_timeout(self, pair: str, trade: Trade, order: dict,
current_time: datetime, **kwargs) -> bool: 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. This method can be used to override the enter-timeout.
It is called whenever a limit entry order has been created, It is called whenever a limit entry order has been created,
and is not yet fully filled. 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. :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 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, def check_sell_timeout(self, pair: str, trade: Trade, order: dict,
current_time: datetime, **kwargs) -> bool: 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. Check sell timeout function callback.
This method can be used to override the exit-timeout. This method can be used to override the exit-timeout.
It is called whenever a (long) limit sell order or (short) limit buy 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. :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 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, 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], time_in_force: str, current_time: datetime, entry_tag: Optional[str],
@ -1023,12 +1039,13 @@ class IStrategy(ABC, HyperStrategyMixin):
else: else:
return current_profit > roi 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: current_time: datetime) -> bool:
""" """
FT Internal method. FT Internal method.
Check if timeout is active, and if the order is still open and timed out 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) timeout = self.config.get('unfilledtimeout', {}).get(side)
if timeout is not None: if timeout is not None:
timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes') timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes')
@ -1038,7 +1055,8 @@ class IStrategy(ABC, HyperStrategyMixin):
and order.order_date_utc < timeout_threshold) and order.order_date_utc < timeout_threshold)
if timedout: if timedout:
return True 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, return strategy_safe_wrapper(time_method,
default_retval=False)( default_retval=False)(

View File

@ -170,11 +170,11 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
""" """
return True 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. Check entry timeout function callback.
This method can be used to override the buy-timeout. This method can be used to override the entry-timeout.
It is called whenever a limit buy order has been created, It is called whenever a limit entry order has been created,
and is not yet fully filled. and is not yet fully filled.
Configuration options in `unfilledtimeout` will be verified before this, Configuration options in `unfilledtimeout` will be verified before this,
so ensure to set these timeouts high enough. 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 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. Check exit timeout function callback.
This method can be used to override the sell-timeout. This method can be used to override the exit-timeout.
It is called whenever a limit sell order has been created, It is called whenever a limit exit order has been created,
and is not yet fully filled. and is not yet fully filled.
Configuration options in `unfilledtimeout` will be verified before this, Configuration options in `unfilledtimeout` will be verified before this,
so ensure to set these timeouts high enough. so ensure to set these timeouts high enough.

View File

@ -29,3 +29,21 @@ class TestStrategyImplementCustomSell(TestStrategyNoImplementSell):
current_rate: float, current_profit: float, current_rate: float, current_profit: float,
**kwargs): **kwargs):
return False 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

View File

@ -418,11 +418,20 @@ def test_missing_implements(default_conf):
StrategyResolver.load_strategy(default_conf) StrategyResolver.load_strategy(default_conf)
default_conf['strategy'] = 'TestStrategyImplementCustomSell' default_conf['strategy'] = 'TestStrategyImplementCustomSell'
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
match=r"Please migrate your implementation of `custom_sell`.*"): match=r"Please migrate your implementation of `custom_sell`.*"):
StrategyResolver.load_strategy(default_conf) 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") @pytest.mark.filterwarnings("ignore:deprecated")
def test_call_deprecated_function(result, default_conf, caplog): def test_call_deprecated_function(result, default_conf, caplog):

View File

@ -2370,7 +2370,7 @@ def test_bot_loop_start_called_once(mocker, default_conf_usdt, caplog):
@pytest.mark.parametrize("is_short", [False, True]) @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, default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
limit_sell_order_old, fee, mocker, is_short limit_sell_order_old, fee, mocker, is_short
) -> None: ) -> None:
@ -2406,34 +2406,23 @@ def test_check_handle_timedout_buy_usercustom(
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
# Return false - trade remains open # Return false - trade remains open
if is_short: freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
else:
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
freqtrade.check_handle_timedout() freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
nb_trades = len(trades) nb_trades = len(trades)
assert nb_trades == 1 assert nb_trades == 1
if is_short: assert freqtrade.strategy.check_entry_timeout.call_count == 1
assert freqtrade.strategy.check_sell_timeout.call_count == 1 freqtrade.strategy.check_entry_timeout = MagicMock(side_effect=KeyError)
# 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)
freqtrade.check_handle_timedout() freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
nb_trades = len(trades) nb_trades = len(trades)
assert nb_trades == 1 assert nb_trades == 1
if is_short: assert freqtrade.strategy.check_entry_timeout.call_count == 1
assert freqtrade.strategy.check_sell_timeout.call_count == 1 freqtrade.strategy.check_entry_timeout = MagicMock(return_value=True)
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)
# Trade should be closed since the function returns true # Trade should be closed since the function returns true
freqtrade.check_handle_timedout() freqtrade.check_handle_timedout()
assert cancel_order_wr_mock.call_count == 1 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() trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
nb_trades = len(trades) nb_trades = len(trades)
assert nb_trades == 0 assert nb_trades == 0
if is_short: assert freqtrade.strategy.check_entry_timeout.call_count == 1
assert freqtrade.strategy.check_sell_timeout.call_count == 1
else:
assert freqtrade.strategy.check_buy_timeout.call_count == 1
@pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
@ -2472,9 +2458,9 @@ def test_check_handle_timedout_buy(
Trade.query.session.add(open_trade) Trade.query.session.add(open_trade)
if is_short: if is_short:
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False) freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False)
else: 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 # check it does cancel buy orders over the time limit
freqtrade.check_handle_timedout() freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
@ -2484,9 +2470,9 @@ def test_check_handle_timedout_buy(
assert nb_trades == 0 assert nb_trades == 0
# Custom user buy-timeout is never called # Custom user buy-timeout is never called
if is_short: if is_short:
assert freqtrade.strategy.check_sell_timeout.call_count == 0 assert freqtrade.strategy.check_exit_timeout.call_count == 0
else: 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]) @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]) @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, default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker,
is_short, open_trade_usdt, caplog is_short, open_trade_usdt, caplog
) -> None: ) -> None:
@ -2585,35 +2571,35 @@ def test_check_handle_timedout_sell_usercustom(
freqtrade.check_handle_timedout() freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False) freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False)
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False) freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
# Return false - No impact # Return false - No impact
freqtrade.check_handle_timedout() freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
assert rpc_mock.call_count == 0 assert rpc_mock.call_count == 0
assert open_trade_usdt.is_open is False 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_exit_timeout.call_count == 1
assert freqtrade.strategy.check_buy_timeout.call_count == (1 if is_short else 0) assert freqtrade.strategy.check_entry_timeout.call_count == 0
freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError) freqtrade.strategy.check_exit_timeout = MagicMock(side_effect=KeyError)
freqtrade.strategy.check_buy_timeout = MagicMock(side_effect=KeyError) freqtrade.strategy.check_entry_timeout = MagicMock(side_effect=KeyError)
# Return Error - No impact # Return Error - No impact
freqtrade.check_handle_timedout() freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
assert rpc_mock.call_count == 0 assert rpc_mock.call_count == 0
assert open_trade_usdt.is_open is False 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_exit_timeout.call_count == 1
assert freqtrade.strategy.check_buy_timeout.call_count == (1 if is_short else 0) assert freqtrade.strategy.check_entry_timeout.call_count == 0
# Return True - sells! # Return True - sells!
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=True) freqtrade.strategy.check_exit_timeout = MagicMock(return_value=True)
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True) freqtrade.strategy.check_entry_timeout = MagicMock(return_value=True)
freqtrade.check_handle_timedout() freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 1 assert rpc_mock.call_count == 1
assert open_trade_usdt.is_open is True 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_exit_timeout.call_count == 1
assert freqtrade.strategy.check_buy_timeout.call_count == (1 if is_short else 0) assert freqtrade.strategy.check_entry_timeout.call_count == 0
# 2nd canceled trade - Fail execute sell # 2nd canceled trade - Fail execute sell
caplog.clear() caplog.clear()
@ -2665,16 +2651,16 @@ def test_check_handle_timedout_sell(
Trade.query.session.add(open_trade_usdt) Trade.query.session.add(open_trade_usdt)
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False) freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False)
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False) freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
# check it does cancel sell orders over the time limit # check it does cancel sell orders over the time limit
freqtrade.check_handle_timedout() freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 1 assert rpc_mock.call_count == 1
assert open_trade_usdt.is_open is True assert open_trade_usdt.is_open is True
# Custom user sell-timeout is never called # Custom user sell-timeout is never called
assert freqtrade.strategy.check_sell_timeout.call_count == 0 assert freqtrade.strategy.check_exit_timeout.call_count == 0
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]) @pytest.mark.parametrize("is_short", [False, True])