Update buy_timeout and sell_timeout methods
This commit is contained in:
parent
973644de66
commit
6f1b14c013
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -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"`.
|
||||||
|
@ -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'])
|
||||||
|
@ -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
|
||||||
|
@ -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 "
|
||||||
|
@ -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)(
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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])
|
||||||
|
Loading…
Reference in New Issue
Block a user