diff --git a/config_binance.json.example b/config_binance.json.example index b13a5725c..938bc9342 100644 --- a/config_binance.json.example +++ b/config_binance.json.example @@ -22,10 +22,7 @@ }, "ask_strategy": { "use_order_book": true, - "order_book_top": 1, - "use_sell_signal": true, - "sell_profit_only": false, - "ignore_roi_if_buy_signal": false + "order_book_top": 1 }, "exchange": { "name": "binance", diff --git a/config_bittrex.json.example b/config_bittrex.json.example index 9a0c62021..4352d8822 100644 --- a/config_bittrex.json.example +++ b/config_bittrex.json.example @@ -22,10 +22,7 @@ }, "ask_strategy":{ "use_order_book": true, - "order_book_top": 1, - "use_sell_signal": true, - "sell_profit_only": false, - "ignore_roi_if_buy_signal": false + "order_book_top": 1 }, "exchange": { "name": "bittrex", diff --git a/config_ftx.json.example b/config_ftx.json.example index f1ad0a4f3..48651f04c 100644 --- a/config_ftx.json.example +++ b/config_ftx.json.example @@ -22,10 +22,7 @@ }, "ask_strategy": { "use_order_book": true, - "order_book_top": 1, - "use_sell_signal": true, - "sell_profit_only": false, - "ignore_roi_if_buy_signal": false + "order_book_top": 1 }, "exchange": { "name": "ftx", diff --git a/config_full.json.example b/config_full.json.example index 5b0448f13..d404391a4 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -14,6 +14,10 @@ "trailing_stop_positive": 0.005, "trailing_stop_positive_offset": 0.0051, "trailing_only_offset_is_reached": false, + "use_sell_signal": true, + "sell_profit_only": false, + "sell_profit_offset": 0.0, + "ignore_roi_if_buy_signal": false, "minimal_roi": { "40": 0.0, "30": 0.01, @@ -39,11 +43,7 @@ "ask_strategy":{ "price_side": "ask", "use_order_book": true, - "order_book_top": 1, - "use_sell_signal": true, - "sell_profit_only": false, - "sell_profit_offset": 0.0, - "ignore_roi_if_buy_signal": false + "order_book_top": 1 }, "order_types": { "buy": "limit", diff --git a/config_kraken.json.example b/config_kraken.json.example index 9fe094120..bf3548568 100644 --- a/config_kraken.json.example +++ b/config_kraken.json.example @@ -22,10 +22,7 @@ }, "ask_strategy":{ "use_order_book": true, - "order_book_top": 1, - "use_sell_signal": true, - "sell_profit_only": false, - "ignore_roi_if_buy_signal": false + "order_book_top": 1 }, "exchange": { "name": "kraken", diff --git a/docs/configuration.md b/docs/configuration.md index 074b9ddf0..5c6236e58 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -81,11 +81,11 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `ask_strategy.bid_last_balance` | Interpolate the selling price. More information [below](#sell-price-without-orderbook-enabled). | `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled).
**Datatype:** Boolean | `ask_strategy.order_book_top` | Bot will use the top N rate in Order Book "price_side" to sell. I.e. a value of 2 will allow the bot to pick the 2nd ask rate in [Order Book Asks](#sell-price-with-orderbook-enabled)
*Defaults to `1`.*
**Datatype:** Positive Integer -| `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.*
**Datatype:** Boolean -| `ask_strategy.sell_profit_only` | Wait until the bot reaches `ask_strategy.sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean -| `ask_strategy.sell_profit_offset` | Sell-signal is only active above this value. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0`.*
**Datatype:** Float (as ratio) -| `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean -| `ask_strategy.ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used.
**Datatype:** Integer +| `use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.*
**Datatype:** Boolean +| `sell_profit_only` | Wait until the bot reaches `sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean +| `sell_profit_offset` | Sell-signal is only active above this value. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0`.*
**Datatype:** Float (as ratio) +| `ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean +| `ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used.
**Datatype:** Integer | `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict | `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict | `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
**Datatype:** String @@ -156,11 +156,11 @@ Values set in the configuration file always overwrite values set in the strategy * `order_time_in_force` * `unfilledtimeout` * `disable_dataframe_checks` -* `use_sell_signal` (ask_strategy) -* `sell_profit_only` (ask_strategy) -* `sell_profit_offset` (ask_strategy) -* `ignore_roi_if_buy_signal` (ask_strategy) -* `ignore_buying_expired_candle_after` (ask_strategy) +* `use_sell_signal` +* `sell_profit_only` +* `sell_profit_offset` +* `ignore_roi_if_buy_signal` +* `ignore_buying_expired_candle_after` ### Configuring amount per trade @@ -291,16 +291,16 @@ See [the telegram documentation](telegram-usage.md) for details on usage. When working with larger timeframes (for example 1h or more) and using a low `max_open_trades` value, the last candle can be processed as soon as a trade slot becomes available. When processing the last candle, this can lead to a situation where it may not be desirable to use the buy signal on that candle. For example, when using a condition in your strategy where you use a cross-over, that point may have passed too long ago for you to start a trade on it. -In these situations, you can enable the functionality to ignore candles that are beyond a specified period by setting `ask_strategy.ignore_buying_expired_candle_after` to a positive number, indicating the number of seconds after which the buy signal becomes expired. +In these situations, you can enable the functionality to ignore candles that are beyond a specified period by setting `ignore_buying_expired_candle_after` to a positive number, indicating the number of seconds after which the buy signal becomes expired. For example, if your strategy is using a 1h timeframe, and you only want to buy within the first 5 minutes when a new candle comes in, you can add the following configuration to your strategy: ``` json - "ask_strategy":{ + { + //... "ignore_buying_expired_candle_after": 300, - "price_side": "bid", // ... - }, + } ``` !!! Note diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 29c078d0d..aad03e983 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -150,7 +150,7 @@ def _validate_edge(conf: Dict[str, Any]) -> None: if not conf.get('edge', {}).get('enabled'): return - if not conf.get('ask_strategy', {}).get('use_sell_signal', True): + if not conf.get('use_sell_signal', True): raise OperationalException( "Edge requires `use_sell_signal` to be True, otherwise no sells will happen." ) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 6b2a20c8c..1b162f7c9 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -3,7 +3,7 @@ Functions to handle deprecated settings """ import logging -from typing import Any, Dict +from typing import Any, Dict, Optional from freqtrade.exceptions import OperationalException @@ -12,23 +12,24 @@ logger = logging.getLogger(__name__) def check_conflicting_settings(config: Dict[str, Any], - section1: str, name1: str, - section2: str, name2: str) -> None: - section1_config = config.get(section1, {}) - section2_config = config.get(section2, {}) - if name1 in section1_config and name2 in section2_config: + section_old: str, name_old: str, + section_new: Optional[str], name_new: str) -> None: + section_new_config = config.get(section_new, {}) if section_new else config + section_old_config = config.get(section_old, {}) + if name_new in section_new_config and name_old in section_old_config: + new_name = f"{section_new}.{name_new}" if section_new else f"{name_new}" raise OperationalException( - f"Conflicting settings `{section1}.{name1}` and `{section2}.{name2}` " + f"Conflicting settings `{new_name}` and `{section_old}.{name_old}` " "(DEPRECATED) detected in the configuration file. " "This deprecated setting will be removed in the next versions of Freqtrade. " - f"Please delete it from your configuration and use the `{section1}.{name1}` " + f"Please delete it from your configuration and use the `{new_name}` " "setting instead." ) def process_removed_setting(config: Dict[str, Any], section1: str, name1: str, - section2: str, name2: str) -> None: + section2: Optional[str], name2: str) -> None: """ :param section1: Removed section :param name1: Removed setting name @@ -37,27 +38,32 @@ def process_removed_setting(config: Dict[str, Any], """ section1_config = config.get(section1, {}) if name1 in section1_config: + section_2 = f"{section2}.{name2}" if section2 else f"{name2}" raise OperationalException( - f"Setting `{section1}.{name1}` has been moved to `{section2}.{name2}. " - f"Please delete it from your configuration and use the `{section2}.{name2}` " + f"Setting `{section1}.{name1}` has been moved to `{section_2}. " + f"Please delete it from your configuration and use the `{section_2}` " "setting instead." ) def process_deprecated_setting(config: Dict[str, Any], - section1: str, name1: str, - section2: str, name2: str) -> None: - section2_config = config.get(section2, {}) + section_old: str, name_old: str, + section_new: Optional[str], name_new: str + ) -> None: + check_conflicting_settings(config, section_old, name_old, section_new, name_new) + section_old_config = config.get(section_old, {}) - if name2 in section2_config: + if name_old in section_old_config: + section_2 = f"{section_new}.{name_new}" if section_new else f"{name_new}" logger.warning( "DEPRECATED: " - f"The `{section2}.{name2}` setting is deprecated and " + f"The `{section_old}.{name_old}` setting is deprecated and " "will be removed in the next versions of Freqtrade. " - f"Please use the `{section1}.{name1}` setting in your configuration instead." + f"Please use the `{section_2}` setting in your configuration instead." ) - section1_config = config.get(section1, {}) - section1_config[name1] = section2_config[name2] + + section_new_config = config.get(section_new, {}) if section_new else config + section_new_config[name_new] = section_old_config[name_old] def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: @@ -65,15 +71,24 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: # Kept for future deprecated / moved settings # check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal', # 'experimental', 'use_sell_signal') - # process_deprecated_setting(config, 'ask_strategy', 'use_sell_signal', - # 'experimental', 'use_sell_signal') + process_deprecated_setting(config, 'ask_strategy', 'use_sell_signal', + None, 'use_sell_signal') + process_deprecated_setting(config, 'ask_strategy', 'sell_profit_only', + None, 'sell_profit_only') + process_deprecated_setting(config, 'ask_strategy', 'sell_profit_offset', + None, 'sell_profit_offset') + process_deprecated_setting(config, 'ask_strategy', 'ignore_roi_if_buy_signal', + None, 'ignore_roi_if_buy_signal') + process_deprecated_setting(config, 'ask_strategy', 'ignore_buying_expired_candle_after', + None, 'ignore_buying_expired_candle_after') + # Legacy way - having them in experimental ... process_removed_setting(config, 'experimental', 'use_sell_signal', - 'ask_strategy', 'use_sell_signal') + None, 'use_sell_signal') process_removed_setting(config, 'experimental', 'sell_profit_only', - 'ask_strategy', 'sell_profit_only') + None, 'sell_profit_only') process_removed_setting(config, 'experimental', 'ignore_roi_if_buy_signal', - 'ask_strategy', 'ignore_roi_if_buy_signal') + None, 'ignore_roi_if_buy_signal') if (config.get('edge', {}).get('enabled', False) and 'capital_available_percentage' in config.get('edge', {})): diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 364eb54bd..63cf3e870 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -134,6 +134,11 @@ CONF_SCHEMA = { 'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'trailing_only_offset_is_reached': {'type': 'boolean'}, + 'use_sell_signal': {'type': 'boolean'}, + 'sell_profit_only': {'type': 'boolean'}, + 'sell_profit_offset': {'type': 'number'}, + 'ignore_roi_if_buy_signal': {'type': 'boolean'}, + 'ignore_buying_expired_candle_after': {'type': 'number'}, 'bot_name': {'type': 'string'}, 'unfilledtimeout': { 'type': 'object', @@ -177,10 +182,6 @@ CONF_SCHEMA = { }, 'use_order_book': {'type': 'boolean'}, 'order_book_top': {'type': 'integer', 'minimum': 1, 'maximum': 50, }, - 'use_sell_signal': {'type': 'boolean'}, - 'sell_profit_only': {'type': 'boolean'}, - 'sell_profit_offset': {'type': 'number'}, - 'ignore_roi_if_buy_signal': {'type': 'boolean'} }, 'required': ['price_side'] }, diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d03604cb0..7a7371357 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -684,10 +684,8 @@ class FreqtradeBot(LoggingMixin): (buy, sell) = (False, False) - config_ask_strategy = self.config.get('ask_strategy', {}) - - if (config_ask_strategy.get('use_sell_signal', True) or - config_ask_strategy.get('ignore_roi_if_buy_signal', False)): + if (self.config.get('use_sell_signal', True) or + self.config.get('ignore_roi_if_buy_signal', False)): analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, self.strategy.timeframe) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 24dbb4961..922f27fad 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -378,10 +378,10 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], 'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached', False), 'use_custom_stoploss': config.get('use_custom_stoploss', False), 'minimal_roi': config['minimal_roi'], - 'use_sell_signal': config['ask_strategy']['use_sell_signal'], - 'sell_profit_only': config['ask_strategy']['sell_profit_only'], - 'sell_profit_offset': config['ask_strategy']['sell_profit_offset'], - 'ignore_roi_if_buy_signal': config['ask_strategy']['ignore_roi_if_buy_signal'], + 'use_sell_signal': config['use_sell_signal'], + 'sell_profit_only': config['sell_profit_only'], + 'sell_profit_offset': config['sell_profit_offset'], + 'ignore_roi_if_buy_signal': config['ignore_roi_if_buy_signal'], **daily_stats, **trade_stats } diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index e76d1e3e5..ccd7cea69 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -45,10 +45,6 @@ class StrategyResolver(IResolver): strategy_name, config=config, extra_dir=config.get('strategy_path')) - # make sure ask_strategy dict is available - if 'ask_strategy' not in config: - config['ask_strategy'] = {} - if hasattr(strategy, 'ticker_interval') and not hasattr(strategy, 'timeframe'): # Assign ticker_interval to timeframe to keep compatibility if 'timeframe' not in config: @@ -60,42 +56,36 @@ class StrategyResolver(IResolver): # Set attributes # Check if we need to override configuration # (Attribute name, default, subkey) - attributes = [("minimal_roi", {"0": 10.0}, None), - ("timeframe", None, None), - ("stoploss", None, None), - ("trailing_stop", None, None), - ("trailing_stop_positive", None, None), - ("trailing_stop_positive_offset", 0.0, None), - ("trailing_only_offset_is_reached", None, None), - ("use_custom_stoploss", None, None), - ("process_only_new_candles", None, None), - ("order_types", None, None), - ("order_time_in_force", None, None), - ("stake_currency", None, None), - ("stake_amount", None, None), - ("protections", None, None), - ("startup_candle_count", None, None), - ("unfilledtimeout", None, None), - ("use_sell_signal", True, 'ask_strategy'), - ("sell_profit_only", False, 'ask_strategy'), - ("ignore_roi_if_buy_signal", False, 'ask_strategy'), - ("sell_profit_offset", 0.0, 'ask_strategy'), - ("disable_dataframe_checks", False, None), - ("ignore_buying_expired_candle_after", 0, 'ask_strategy') + attributes = [("minimal_roi", {"0": 10.0}), + ("timeframe", None), + ("stoploss", None), + ("trailing_stop", None), + ("trailing_stop_positive", None), + ("trailing_stop_positive_offset", 0.0), + ("trailing_only_offset_is_reached", None), + ("use_custom_stoploss", None), + ("process_only_new_candles", None), + ("order_types", None), + ("order_time_in_force", None), + ("stake_currency", None), + ("stake_amount", None), + ("protections", None), + ("startup_candle_count", None), + ("unfilledtimeout", None), + ("use_sell_signal", True), + ("sell_profit_only", False), + ("ignore_roi_if_buy_signal", False), + ("sell_profit_offset", 0.0), + ("disable_dataframe_checks", False), + ("ignore_buying_expired_candle_after", 0) ] - for attribute, default, subkey in attributes: - if subkey: - StrategyResolver._override_attribute_helper(strategy, config.get(subkey, {}), - attribute, default) - else: - StrategyResolver._override_attribute_helper(strategy, config, - attribute, default) + for attribute, default in attributes: + StrategyResolver._override_attribute_helper(strategy, config, + attribute, default) # Loop this list again to have output combined - for attribute, _, subkey in attributes: - if subkey and attribute in config[subkey]: - logger.info("Strategy using %s: %s", attribute, config[subkey][attribute]) - elif attribute in config: + for attribute, _ in attributes: + if attribute in config: logger.info("Strategy using %s: %s", attribute, config[attribute]) StrategyResolver._normalize_attributes(strategy) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index ea7d5079d..7aa7e57d9 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -97,6 +97,11 @@ class IStrategy(ABC, HyperStrategyMixin): # run "populate_indicators" only for new candle process_only_new_candles: bool = False + use_sell_signal: bool + sell_profit_only: bool + sell_profit_offset: float + ignore_roi_if_buy_signal: bool + # Number of seconds after which the candle will no longer result in a buy on expired candles ignore_buying_expired_candle_after: int = 0 @@ -543,10 +548,9 @@ class IStrategy(ABC, HyperStrategyMixin): # Set current rate to high for backtesting sell current_rate = high or rate current_profit = trade.calc_profit_ratio(current_rate) - ask_strategy = self.config.get('ask_strategy', {}) # if buy signal and ignore_roi is set, we don't need to evaluate min_roi. - roi_reached = (not (buy and ask_strategy.get('ignore_roi_if_buy_signal', False)) + roi_reached = (not (buy and self.ignore_roi_if_buy_signal) and self.min_roi_reached(trade=trade, current_profit=current_profit, current_time=date)) @@ -556,11 +560,10 @@ class IStrategy(ABC, HyperStrategyMixin): current_rate = rate current_profit = trade.calc_profit_ratio(current_rate) - if (ask_strategy.get('sell_profit_only', False) - and current_profit <= ask_strategy.get('sell_profit_offset', 0)): + if (self.sell_profit_only and current_profit <= self.sell_profit_offset): # sell_profit_only and profit doesn't reach the offset - ignore sell signal pass - elif ask_strategy.get('use_sell_signal', True) and not buy: + elif self.use_sell_signal and not buy: if sell: sell_signal = SellType.SELL_SIGNAL else: diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 043d95bdd..03a6c4855 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -26,9 +26,6 @@ "price_side": "ask", "use_order_book": true, "order_book_top": 1, - "use_sell_signal": true, - "sell_profit_only": false, - "ignore_roi_if_buy_signal": false }, {{ exchange | indent(4) }}, "pairlists": [ diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 488425323..855e5b97d 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -551,7 +551,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: if data.trailing_stop_positive is not None: default_conf["trailing_stop_positive"] = data.trailing_stop_positive default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset - default_conf["ask_strategy"] = {"use_sell_signal": data.use_sell_signal} + default_conf["use_sell_signal"] = data.use_sell_signal mocker.patch("freqtrade.exchange.Exchange.get_fee", return_value=0.0) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 60bd82d71..ec176bc32 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -465,7 +465,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti def test_backtest__enter_trade(default_conf, fee, mocker) -> None: - default_conf['ask_strategy']['use_sell_signal'] = False + default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) patch_exchange(mocker) @@ -511,7 +511,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: - default_conf['ask_strategy']['use_sell_signal'] = False + default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) patch_exchange(mocker) @@ -574,7 +574,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None: - default_conf['ask_strategy']['use_sell_signal'] = False + default_conf['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) patch_exchange(mocker) @@ -819,7 +819,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): @pytest.mark.filterwarnings("ignore:deprecated") def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): - default_conf['ask_strategy'].update({ + default_conf.update({ "use_sell_signal": True, "sell_profit_only": False, "sell_profit_offset": 0.0, @@ -894,7 +894,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): @pytest.mark.filterwarnings("ignore:deprecated") def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys): - default_conf['ask_strategy'].update({ + default_conf.update({ "use_sell_signal": True, "sell_profit_only": False, "sell_profit_offset": 0.0, diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index bd192ecb5..115a2fbde 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -290,14 +290,12 @@ def test_strategy_override_use_sell_signal(caplog, default_conf): assert strategy.use_sell_signal assert isinstance(strategy.use_sell_signal, bool) # must be inserted to configuration - assert 'use_sell_signal' in default_conf['ask_strategy'] - assert default_conf['ask_strategy']['use_sell_signal'] + assert 'use_sell_signal' in default_conf + assert default_conf['use_sell_signal'] default_conf.update({ 'strategy': 'DefaultStrategy', - 'ask_strategy': { - 'use_sell_signal': False, - }, + 'use_sell_signal': False, }) strategy = StrategyResolver.load_strategy(default_conf) @@ -315,14 +313,12 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf): assert not strategy.sell_profit_only assert isinstance(strategy.sell_profit_only, bool) # must be inserted to configuration - assert 'sell_profit_only' in default_conf['ask_strategy'] - assert not default_conf['ask_strategy']['sell_profit_only'] + assert 'sell_profit_only' in default_conf + assert not default_conf['sell_profit_only'] default_conf.update({ 'strategy': 'DefaultStrategy', - 'ask_strategy': { - 'sell_profit_only': True, - }, + 'sell_profit_only': True, }) strategy = StrategyResolver.load_strategy(default_conf) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 366501d8a..8edd09c5a 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -878,15 +878,15 @@ def test_validate_tsl(default_conf): def test_validate_edge2(edge_conf): - edge_conf.update({"ask_strategy": { + edge_conf.update({ "use_sell_signal": True, - }}) + }) # Passes test validate_config_consistency(edge_conf) - edge_conf.update({"ask_strategy": { + edge_conf.update({ "use_sell_signal": False, - }}) + }) with pytest.raises(OperationalException, match="Edge requires `use_sell_signal` to be True, " "otherwise no sells will happen."): validate_config_consistency(edge_conf) @@ -1128,11 +1128,17 @@ def test_pairlist_resolving_fallback(mocker): assert config['datadir'] == Path.cwd() / "user_data/data/binance" -@pytest.mark.skip(reason='Currently no deprecated / moved sections') -# The below is kept as a sample for the future. @pytest.mark.parametrize("setting", [ ("ask_strategy", "use_sell_signal", True, - "experimental", "use_sell_signal", False), + None, "use_sell_signal", False), + ("ask_strategy", "sell_profit_only", True, + None, "sell_profit_only", False), + ("ask_strategy", "sell_profit_offset", 0.1, + None, "sell_profit_offset", 0.01), + ("ask_strategy", "ignore_roi_if_buy_signal", True, + None, "ignore_roi_if_buy_signal", False), + ("ask_strategy", "ignore_buying_expired_candle_after", 5, + None, "ignore_buying_expired_candle_after", 6), ]) def test_process_temporary_deprecated_settings(mocker, default_conf, setting, caplog): patched_configuration_load_config_file(mocker, default_conf) @@ -1141,10 +1147,14 @@ def test_process_temporary_deprecated_settings(mocker, default_conf, setting, ca # (they may not exist in the config) default_conf[setting[0]] = {} default_conf[setting[3]] = {} - # Assign new setting - default_conf[setting[0]][setting[1]] = setting[2] + # Assign deprecated setting - default_conf[setting[3]][setting[4]] = setting[5] + default_conf[setting[0]][setting[1]] = setting[2] + # Assign new setting + if setting[3]: + default_conf[setting[3]][setting[4]] = setting[5] + else: + default_conf[setting[4]] = setting[5] # New and deprecated settings are conflicting ones with pytest.raises(OperationalException, match=r'DEPRECATED'): @@ -1153,13 +1163,19 @@ def test_process_temporary_deprecated_settings(mocker, default_conf, setting, ca caplog.clear() # Delete new setting - del default_conf[setting[0]][setting[1]] + if setting[3]: + del default_conf[setting[3]][setting[4]] + else: + del default_conf[setting[4]] process_temporary_deprecated_settings(default_conf) assert log_has_re('DEPRECATED', caplog) # The value of the new setting shall have been set to the # value of the deprecated one - assert default_conf[setting[0]][setting[1]] == setting[5] + if setting[3]: + assert default_conf[setting[3]][setting[4]] == setting[2] + else: + assert default_conf[setting[4]] == setting[2] @pytest.mark.parametrize("setting", [ @@ -1209,16 +1225,16 @@ def test_check_conflicting_settings(mocker, default_conf, caplog): # New and deprecated settings are conflicting ones with pytest.raises(OperationalException, match=r'DEPRECATED'): check_conflicting_settings(default_conf, - 'sectionA', 'new_setting', - 'sectionB', 'deprecated_setting') + 'sectionB', 'deprecated_setting', + 'sectionA', 'new_setting') caplog.clear() # Delete new setting (deprecated exists) del default_conf['sectionA']['new_setting'] check_conflicting_settings(default_conf, - 'sectionA', 'new_setting', - 'sectionB', 'deprecated_setting') + 'sectionB', 'deprecated_setting', + 'sectionA', 'new_setting') assert not log_has_re('DEPRECATED', caplog) assert 'new_setting' not in default_conf['sectionA'] @@ -1229,8 +1245,8 @@ def test_check_conflicting_settings(mocker, default_conf, caplog): # Delete deprecated setting del default_conf['sectionB']['deprecated_setting'] check_conflicting_settings(default_conf, - 'sectionA', 'new_setting', - 'sectionB', 'deprecated_setting') + 'sectionB', 'deprecated_setting', + 'sectionA', 'new_setting') assert not log_has_re('DEPRECATED', caplog) assert default_conf['sectionA']['new_setting'] == 'valA' @@ -1242,15 +1258,13 @@ def test_process_deprecated_setting(mocker, default_conf, caplog): # (they may not exist in the config) default_conf['sectionA'] = {} default_conf['sectionB'] = {} - # Assign new setting - default_conf['sectionA']['new_setting'] = 'valA' # Assign deprecated setting default_conf['sectionB']['deprecated_setting'] = 'valB' # Both new and deprecated settings exists process_deprecated_setting(default_conf, - 'sectionA', 'new_setting', - 'sectionB', 'deprecated_setting') + 'sectionB', 'deprecated_setting', + 'sectionA', 'new_setting') assert log_has_re('DEPRECATED', caplog) # The value of the new setting shall have been set to the # value of the deprecated one @@ -1261,8 +1275,8 @@ def test_process_deprecated_setting(mocker, default_conf, caplog): # Delete new setting (deprecated exists) del default_conf['sectionA']['new_setting'] process_deprecated_setting(default_conf, - 'sectionA', 'new_setting', - 'sectionB', 'deprecated_setting') + 'sectionB', 'deprecated_setting', + 'sectionA', 'new_setting') assert log_has_re('DEPRECATED', caplog) # The value of the new setting shall have been set to the # value of the deprecated one @@ -1275,11 +1289,21 @@ def test_process_deprecated_setting(mocker, default_conf, caplog): # Delete deprecated setting del default_conf['sectionB']['deprecated_setting'] process_deprecated_setting(default_conf, - 'sectionA', 'new_setting', - 'sectionB', 'deprecated_setting') + 'sectionB', 'deprecated_setting', + 'sectionA', 'new_setting') assert not log_has_re('DEPRECATED', caplog) assert default_conf['sectionA']['new_setting'] == 'valA' + caplog.clear() + # Test moving to root + default_conf['sectionB']['deprecated_setting2'] = "DeadBeef" + process_deprecated_setting(default_conf, + 'sectionB', 'deprecated_setting2', + None, 'new_setting') + + assert log_has_re('DEPRECATED', caplog) + assert default_conf['new_setting'] + def test_process_removed_setting(mocker, default_conf, caplog): patched_configuration_load_config_file(mocker, default_conf) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 4f8a18827..99e11e893 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2960,11 +2960,11 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy buy=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) - default_conf['ask_strategy'] = { + default_conf.update({ 'use_sell_signal': True, 'sell_profit_only': True, 'sell_profit_offset': 0.1, - } + }) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) @@ -2977,7 +2977,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy patch_get_signal(freqtrade, value=(False, True)) assert freqtrade.handle_trade(trade) is False - freqtrade.config['ask_strategy']['sell_profit_offset'] = 0.0 + freqtrade.strategy.sell_profit_offset = 0.0 assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.SELL_SIGNAL.value @@ -2997,10 +2997,10 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, limit_bu buy=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) - default_conf['ask_strategy'] = { + default_conf.update({ 'use_sell_signal': True, 'sell_profit_only': False, - } + }) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) @@ -3028,10 +3028,10 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_o buy=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) - default_conf['ask_strategy'] = { + default_conf.update({ 'use_sell_signal': True, 'sell_profit_only': True, - } + }) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple( @@ -3058,10 +3058,10 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, limit_buy_ buy=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) - default_conf['ask_strategy'] = { + default_conf.update({ 'use_sell_signal': True, 'sell_profit_only': False, - } + }) freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -3209,9 +3209,8 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_buy_order buy=MagicMock(return_value=limit_buy_order_open), get_fee=fee, ) - default_conf['ask_strategy'] = { - 'ignore_roi_if_buy_signal': True - } + default_conf['ignore_roi_if_buy_signal'] = True + freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)