From 06248172426ff4a626ef75dd3ff5ce4f9a2ca41c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 26 Mar 2022 11:55:11 +0100 Subject: [PATCH] update unfilledtimeout settings to entry/exit --- config_examples/config_binance.example.json | 4 +-- config_examples/config_bittrex.example.json | 4 +-- config_examples/config_ftx.example.json | 4 +-- config_examples/config_full.example.json | 4 +-- config_examples/config_kraken.example.json | 4 +-- docs/configuration.md | 4 +-- docs/strategy-callbacks.md | 8 +++--- docs/strategy_migration.md | 25 ++++++++++++++++++ freqtrade/configuration/config_validation.py | 21 +++++++++++++++ freqtrade/constants.py | 4 +-- freqtrade/rpc/api_server/api_schemas.py | 4 +-- freqtrade/strategy/interface.py | 8 +++--- freqtrade/templates/base_config.json.j2 | 4 +-- tests/conftest.py | 4 +-- tests/test_configuration.py | 27 +++++++++++++++++++- tests/test_freqtradebot.py | 6 ++--- 16 files changed, 103 insertions(+), 32 deletions(-) diff --git a/config_examples/config_binance.example.json b/config_examples/config_binance.example.json index c6faf506c..ae84af420 100644 --- a/config_examples/config_binance.example.json +++ b/config_examples/config_binance.example.json @@ -8,8 +8,8 @@ "dry_run": true, "cancel_open_orders_on_exit": false, "unfilledtimeout": { - "buy": 10, - "sell": 10, + "entry": 10, + "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, diff --git a/config_examples/config_bittrex.example.json b/config_examples/config_bittrex.example.json index 9fe99c835..1f55d43ed 100644 --- a/config_examples/config_bittrex.example.json +++ b/config_examples/config_bittrex.example.json @@ -8,8 +8,8 @@ "dry_run": true, "cancel_open_orders_on_exit": false, "unfilledtimeout": { - "buy": 10, - "sell": 10, + "entry": 10, + "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, diff --git a/config_examples/config_ftx.example.json b/config_examples/config_ftx.example.json index 4f7c2af54..fbdff3333 100644 --- a/config_examples/config_ftx.example.json +++ b/config_examples/config_ftx.example.json @@ -8,8 +8,8 @@ "dry_run": true, "cancel_open_orders_on_exit": false, "unfilledtimeout": { - "buy": 10, - "sell": 10, + "entry": 10, + "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index bbdafa805..9e4c342ed 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -30,8 +30,8 @@ }, "stoploss": -0.10, "unfilledtimeout": { - "buy": 10, - "sell": 10, + "entry": 10, + "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, diff --git a/config_examples/config_kraken.example.json b/config_examples/config_kraken.example.json index 5ac3a9255..27a4979d4 100644 --- a/config_examples/config_kraken.example.json +++ b/config_examples/config_kraken.example.json @@ -8,8 +8,8 @@ "dry_run": true, "cancel_open_orders_on_exit": false, "unfilledtimeout": { - "buy": 10, - "sell": 10, + "entry": 10, + "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, diff --git a/docs/configuration.md b/docs/configuration.md index 147f0b672..3f3086833 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -102,8 +102,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `trading_mode` | Specifies if you want to trade regularly, trade with leverage, or trade contracts whose prices are derived from matching cryptocurrency prices. [leverage documentation](leverage.md).
*Defaults to `"spot"`.*
**Datatype:** String | `margin_mode` | When trading with leverage, this determines if the collateral owned by the trader will be shared or isolated to each trading pair [leverage documentation](leverage.md).
**Datatype:** String | `liquidation_buffer` | A ratio specifying how large of a safety net to place between the liquidation price and the stoploss to prevent a position from reaching the liquidation price [leverage documentation](leverage.md).
*Defaults to `0.05`.*
**Datatype:** Float -| `unfilledtimeout.buy` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer -| `unfilledtimeout.sell` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer +| `unfilledtimeout.entry` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled entry order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer +| `unfilledtimeout.exit` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled exit order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Integer | `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy).
*Defaults to `minutes`.*
**Datatype:** String | `unfilledtimeout.exit_timeout_count` | How many times can exit orders time out. Once this number of timeouts is reached, an emergency sell is triggered. 0 to disable and allow unlimited order cancels. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0`.*
**Datatype:** Integer | `bid_strategy.price_side` | Select the side of the spread the bot should look at to get the buy rate. [More information below](#buy-price-side).
*Defaults to `bid`.*
**Datatype:** String (either `ask` or `bid`). diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 31d52e30c..6baf38c41 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -425,8 +425,8 @@ class AwesomeStrategy(IStrategy): # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. unfilledtimeout = { - 'buy': 60 * 25, - 'sell': 60 * 25 + 'entry': 60 * 25, + 'exit': 60 * 25 } def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, @@ -466,8 +466,8 @@ class AwesomeStrategy(IStrategy): # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. unfilledtimeout = { - 'buy': 60 * 25, - 'sell': 60 * 25 + 'entry': 60 * 25, + 'exit': 60 * 25 } def check_entry_timeout(self, pair: str, trade: Trade, order: dict, diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md index 19bd20880..ee6f1a494 100644 --- a/docs/strategy_migration.md +++ b/docs/strategy_migration.md @@ -32,6 +32,7 @@ If you intend on using markets other than spot markets, please migrate your stra * Strategy/Configuration settings. * `order_time_in_force` buy -> entry, sell -> exit. * `order_types` buy -> entry, sell -> exit. + * `unfilledtimeout` buy -> entry, sell -> exit. ## Extensive explanation @@ -287,6 +288,7 @@ This should be given the value of `trade.is_short`. "stoploss": "market", "stoploss_on_exchange": false, "stoploss_on_exchange_interval": 60 + } ``` ``` python hl_lines="2-6" @@ -299,4 +301,27 @@ This should be given the value of `trade.is_short`. "stoploss": "market", "stoploss_on_exchange": false, "stoploss_on_exchange_interval": 60 + } +``` + +#### `unfilledtimeout` + +`unfilledtimeout` have changed all wordings from `buy` to `entry` - and `sell` to `exit`. + +``` python hl_lines="2-3" +unfilledtimeout = { + "buy": 10, + "sell": 10, + "exit_timeout_count": 0, + "unit": "minutes" + } +``` + +``` python hl_lines="2-3" +unfilledtimeout = { + "entry": 10, + "exit": 10, + "exit_timeout_count": 0, + "unit": "minutes" + } ``` diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 267509b43..3ebb18cd6 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -216,6 +216,7 @@ def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None: _validate_time_in_force(conf) _validate_order_types(conf) + _validate_unfilledtimeout(conf) def _validate_time_in_force(conf: Dict[str, Any]) -> None: @@ -258,3 +259,23 @@ def _validate_order_types(conf: Dict[str, Any]) -> None: ]: process_deprecated_setting(conf, 'order_types', o, 'order_types', n) + + +def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None: + unfilledtimeout = conf.get('unfilledtimeout', {}) + if any(x in unfilledtimeout for x in ['buy', 'sell']): + if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT: + raise OperationalException( + "Please migrate your unfilledtimeout settings to use the new wording.") + else: + + logger.warning( + "DEPRECATED: Using 'buy' and 'sell' for unfilledtimeout is deprecated." + "Please migrate your unfilledtimeout settings to use 'entry' and 'exit' wording." + ) + for o, n in [ + ('buy', 'entry'), + ('sell', 'exit'), + ]: + + process_deprecated_setting(conf, 'unfilledtimeout', o, 'unfilledtimeout', n) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index fabac5830..6441559ad 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -165,8 +165,8 @@ CONF_SCHEMA = { 'unfilledtimeout': { 'type': 'object', 'properties': { - 'buy': {'type': 'number', 'minimum': 1}, - 'sell': {'type': 'number', 'minimum': 1}, + 'entry': {'type': 'number', 'minimum': 1}, + 'exit': {'type': 'number', 'minimum': 1}, 'exit_timeout_count': {'type': 'number', 'minimum': 0, 'default': 0}, 'unit': {'type': 'string', 'enum': TIMEOUT_UNITS, 'default': 'minutes'} } diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index b6d175c0f..07772647f 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -131,8 +131,8 @@ class Daily(BaseModel): class UnfilledTimeout(BaseModel): - buy: Optional[int] - sell: Optional[int] + entry: Optional[int] + exit: Optional[int] unit: Optional[str] exit_timeout_count: Optional[int] diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index a61483e1d..c00eb238a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -259,7 +259,7 @@ 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 self.check_exit_timeout( + return self.check_sell_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, @@ -1045,14 +1045,14 @@ class IStrategy(ABC, HyperStrategyMixin): 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' + side = 'entry' if order.ft_order_side == trade.enter_side else 'exit' + timeout = self.config.get('unfilledtimeout', {}).get(side) 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 order.order_date_utc < timeout_threshold) + timedout = (order.status == 'open' and order.order_date_utc < timeout_threshold) if timedout: return True time_method = (self.check_exit_timeout if order.side == trade.exit_side diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 60f4b4fd7..e5f8f5efe 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -16,8 +16,8 @@ "trading_mode": "{{ trading_mode }}", "margin_mode": "{{ margin_mode }}", "unfilledtimeout": { - "buy": 10, - "sell": 10, + "entry": 10, + "exit": 10, "exit_timeout_count": 0, "unit": "minutes" }, diff --git a/tests/conftest.py b/tests/conftest.py index 117aeaaed..898945370 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -416,8 +416,8 @@ def get_default_conf(testdatadir): "dry_run_wallet": 1000, "stoploss": -0.10, "unfilledtimeout": { - "buy": 10, - "sell": 30 + "entry": 10, + "exit": 30 }, "bid_strategy": { "ask_last_balance": 0.0, diff --git a/tests/test_configuration.py b/tests/test_configuration.py index e4a30c958..44187104a 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -963,7 +963,7 @@ def test_validate_time_in_force(default_conf, caplog) -> None: validate_config_consistency(conf) -def test_validate_order_types(default_conf, caplog) -> None: +def test__validate_order_types(default_conf, caplog) -> None: conf = deepcopy(default_conf) conf['order_types'] = { 'buy': 'limit', @@ -998,6 +998,31 @@ def test_validate_order_types(default_conf, caplog) -> None: validate_config_consistency(conf) +def test__validate_unfilledtimeout(default_conf, caplog) -> None: + conf = deepcopy(default_conf) + conf['unfilledtimeout'] = { + 'buy': 30, + 'sell': 35, + } + validate_config_consistency(conf) + assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for unfilledtimeout is.*", caplog) + assert conf['unfilledtimeout']['entry'] == 30 + assert conf['unfilledtimeout']['exit'] == 35 + assert 'buy' not in conf['unfilledtimeout'] + assert 'sell' not in conf['unfilledtimeout'] + + conf = deepcopy(default_conf) + conf['unfilledtimeout'] = { + 'buy': 30, + 'sell': 35, + } + conf['trading_mode'] = 'futures' + with pytest.raises( + OperationalException, + match=r"Please migrate your unfilledtimeout settings to use the new wording\."): + validate_config_consistency(conf) + + def test_load_config_test_comments() -> None: """ Load config with comments diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 9637a45e6..37a43eab4 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2378,8 +2378,8 @@ def test_check_handle_timedout_entry_usercustom( old_order = limit_sell_order_old if is_short else limit_buy_order_old old_order['id'] = open_trade.open_order_id - default_conf_usdt["unfilledtimeout"] = {"buy": 30, - "sell": 1400} if is_short else {"buy": 1400, "sell": 30} + default_conf_usdt["unfilledtimeout"] = {"entry": 30, + "exit": 1400} if is_short else {"entry": 1400, "exit": 30} rpc_mock = patch_RPCManager(mocker) cancel_order_mock = MagicMock(return_value=old_order) @@ -2543,7 +2543,7 @@ def test_check_handle_timedout_exit_usercustom( default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker, is_short, open_trade_usdt, caplog ) -> None: - default_conf_usdt["unfilledtimeout"] = {"buy": 1440, "sell": 1440, "exit_timeout_count": 1} + default_conf_usdt["unfilledtimeout"] = {"entry": 1440, "exit": 1440, "exit_timeout_count": 1} limit_sell_order_old['id'] = open_trade_usdt.open_order_id if is_short: limit_sell_order_old['side'] = 'buy'