From aa67abad941afb8637bfb87737f1c54b137c838c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2024 08:38:07 +0200 Subject: [PATCH 1/9] docs: simplify protections intro this had way too many consecutive "boxes" --- docs/includes/protections.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/includes/protections.md b/docs/includes/protections.md index a4cb9d3cc..6fe766c1f 100644 --- a/docs/includes/protections.md +++ b/docs/includes/protections.md @@ -1,15 +1,11 @@ ## Protections -!!! Warning "Beta feature" - This feature is still in it's testing phase. Should you notice something you think is wrong please let us know via Discord or via Github Issue. - Protections will protect your strategy from unexpected events and market conditions by temporarily stop trading for either one pair, or for all pairs. All protection end times are rounded up to the next candle to avoid sudden, unexpected intra-candle buys. -!!! Note +!!! Tip "Usage tips" Not all Protections will work for all strategies, and parameters will need to be tuned for your strategy to improve performance. -!!! Tip Each Protection can be configured multiple times with different parameters, to allow different levels of protection (short-term / long-term). !!! Note "Backtesting" From 428d451e55f0cc55c1c42ae0dc0b108fb6d079cd Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2024 09:06:57 +0200 Subject: [PATCH 2/9] chore: remove long-deprecated setting --- docs/deprecated.md | 5 ++++- docs/includes/protections.md | 4 ---- freqtrade/configuration/deprecated_settings.py | 4 +++- tests/test_configuration.py | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/deprecated.md b/docs/deprecated.md index 6719ce56d..5357acc62 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -75,7 +75,10 @@ Webhook terminology changed from "sell" to "exit", and from "buy" to "entry", re * `webhooksellfill`, `webhookexitfill` -> `exit_fill` * `webhooksellcancel`, `webhookexitcancel` -> `exit_cancel` - ## Removal of `populate_any_indicators` version 2023.3 saw the removal of `populate_any_indicators` in favor of split methods for feature engineering and targets. Please read the [migration document](strategy_migration.md#freqai-strategy) for full details. + +## Removal of `protections` from configuration + + Setting protections from the configuration via `"protections": [],` has been removed in 2024.10, after having raised deprecation warnings for over 3 years. diff --git a/docs/includes/protections.md b/docs/includes/protections.md index 6fe766c1f..c32846165 100644 --- a/docs/includes/protections.md +++ b/docs/includes/protections.md @@ -11,10 +11,6 @@ All protection end times are rounded up to the next candle to avoid sudden, unex !!! Note "Backtesting" Protections are supported by backtesting and hyperopt, but must be explicitly enabled by using the `--enable-protections` flag. -!!! Warning "Setting protections from the configuration" - Setting protections from the configuration via `"protections": [],` key should be considered deprecated and will be removed in a future version. - It is also no longer guaranteed that your protections apply to the strategy in cases where the strategy defines [protections as property](hyperopt.md#optimizing-protections). - ### Available Protections * [`StoplossGuard`](#stoploss-guard) Stop trading if a certain amount of stoploss occurred within a certain time window. diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 6a0901ed7..c4d78e588 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -177,4 +177,6 @@ def process_temporary_deprecated_settings(config: Config) -> None: ) if "protections" in config: - logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.") + raise ConfigurationError( + "DEPRECATED: Setting 'protections' in the configuration is deprecated." + ) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index d77fae6a8..9c76272db 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1533,8 +1533,8 @@ def test_process_deprecated_protections(default_conf, caplog): assert not log_has(message, caplog) config["protections"] = [] - process_temporary_deprecated_settings(config) - assert log_has(message, caplog) + with pytest.raises(ConfigurationError, match=message): + process_temporary_deprecated_settings(config) def test_flat_vars_to_nested_dict(caplog): From d6cc88fa9961086ea0c0aed7643655715b7492ed Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2024 09:27:40 +0200 Subject: [PATCH 3/9] chore: remove schema syntax highlighting for protections --- build_helpers/schema.json | 51 ---------------------- docs/configuration.md | 1 - freqtrade/configuration/config_schema.py | 54 ------------------------ 3 files changed, 106 deletions(-) diff --git a/build_helpers/schema.json b/build_helpers/schema.json index 8438dc3a0..6a73e75b0 100644 --- a/build_helpers/schema.json +++ b/build_helpers/schema.json @@ -579,57 +579,6 @@ ] } }, - "protections": { - "description": "Configuration for various protections.", - "type": "array", - "items": { - "type": "object", - "properties": { - "method": { - "description": "Method used for the protection.", - "type": "string", - "enum": [ - "CooldownPeriod", - "LowProfitPairs", - "MaxDrawdown", - "StoplossGuard" - ] - }, - "stop_duration": { - "description": "Duration to lock the pair after a protection is triggered, in minutes.", - "type": "number", - "minimum": 0.0 - }, - "stop_duration_candles": { - "description": "Duration to lock the pair after a protection is triggered, in number of candles.", - "type": "number", - "minimum": 0 - }, - "unlock_at": { - "description": "Time when trading will be unlocked regularly. Format: HH:MM", - "type": "string" - }, - "trade_limit": { - "description": "Minimum number of trades required during lookback period.", - "type": "number", - "minimum": 1 - }, - "lookback_period": { - "description": "Period to look back for protection checks, in minutes.", - "type": "number", - "minimum": 1 - }, - "lookback_period_candles": { - "description": "Period to look back for protection checks, in number of candles.", - "type": "number", - "minimum": 1 - } - }, - "required": [ - "method" - ] - } - }, "telegram": { "description": "Telegram settings.", "type": "object", diff --git a/docs/configuration.md b/docs/configuration.md index b05b1dcaa..074c9a577 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -229,7 +229,6 @@ Mandatory parameters are marked as **Required**, which means that they are requi | | **Plugins** | `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation of all possible configuration options. | `pairlists` | Define one or more pairlists to be used. [More information](plugins.md#pairlists-and-pairlist-handlers).
*Defaults to `StaticPairList`.*
**Datatype:** List of Dicts -| `protections` | Define one or more protections to be used. [More information](plugins.md#protections).
**Datatype:** List of Dicts | | **Telegram** | `telegram.enabled` | Enable the usage of Telegram.
**Datatype:** Boolean | `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index cd349daed..5aa9ef5e3 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -449,60 +449,6 @@ CONF_SCHEMA = { "required": ["method"], }, }, - "protections": { - "description": "Configuration for various protections.", - "type": "array", - "items": { - "type": "object", - "properties": { - "method": { - "description": "Method used for the protection.", - "type": "string", - "enum": AVAILABLE_PROTECTIONS, - }, - "stop_duration": { - "description": ( - "Duration to lock the pair after a protection is triggered, " - "in minutes." - ), - "type": "number", - "minimum": 0.0, - }, - "stop_duration_candles": { - "description": ( - "Duration to lock the pair after a protection is triggered, in " - "number of candles." - ), - "type": "number", - "minimum": 0, - }, - "unlock_at": { - "description": ( - "Time when trading will be unlocked regularly. Format: HH:MM" - ), - "type": "string", - }, - "trade_limit": { - "description": "Minimum number of trades required during lookback period.", - "type": "number", - "minimum": 1, - }, - "lookback_period": { - "description": "Period to look back for protection checks, in minutes.", - "type": "number", - "minimum": 1, - }, - "lookback_period_candles": { - "description": ( - "Period to look back for protection checks, in number " "of candles." - ), - "type": "number", - "minimum": 1, - }, - }, - "required": ["method"], - }, - }, # RPC section "telegram": { "description": "Telegram settings.", From e3a6c71087c1fe1f8e4dd47669221c6e4bba8de3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2024 09:41:02 +0200 Subject: [PATCH 4/9] chore: Remove protections from config logic --- freqtrade/optimize/backtesting.py | 4 ---- freqtrade/resolvers/strategy_resolver.py | 1 - freqtrade/rpc/api_server/api_backtest.py | 1 - 3 files changed, 6 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 20116f670..75c0ac075 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -273,10 +273,6 @@ class Backtesting: def _load_protections(self, strategy: IStrategy): if self.config.get("enable_protections", False): - conf = self.config - if hasattr(strategy, "protections"): - conf = deepcopy(conf) - conf["protections"] = strategy.protections self.protections = ProtectionManager(self.config, strategy.protections) def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]: diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index d234a680f..6cd0cef23 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -69,7 +69,6 @@ class StrategyResolver(IResolver): ("order_time_in_force", None), ("stake_currency", None), ("stake_amount", None), - ("protections", None), ("startup_candle_count", None), ("unfilledtimeout", None), ("use_exit_signal", True), diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index e4b598807..4f6484538 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -77,7 +77,6 @@ def __run_backtest_bg(btconfig: Config): lastconfig["timerange"] = btconfig["timerange"] lastconfig["timeframe"] = strat.timeframe - lastconfig["protections"] = btconfig.get("protections", []) lastconfig["enable_protections"] = btconfig.get("enable_protections") lastconfig["dry_run_wallet"] = btconfig.get("dry_run_wallet") From b8feefc5412e82cd11ade99e994b645377149d36 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2024 09:43:50 +0200 Subject: [PATCH 5/9] tests: update protection tests --- docs/hyperopt.md | 1 - tests/freqtradebot/test_freqtradebot.py | 2 +- tests/optimize/test_backtesting.py | 4 ++-- tests/plugins/test_protections.py | 16 ++++++++-------- tests/rpc/test_rpc_manager.py | 2 +- tests/strategy/strats/strategy_test_v3.py | 16 +++++++--------- 6 files changed, 19 insertions(+), 22 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index f88928344..43085029c 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -445,7 +445,6 @@ While this strategy is most likely too simple to provide consistent profit, it s Whether you are using `.range` functionality or the alternatives above, you should try to use space ranges as small as possible since this will improve CPU/RAM usage. - ## Optimizing protections Freqtrade can also optimize protections. How you optimize protections is up to you, and the following should be considered as example only. diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py index 8587e7f9d..08fb3db00 100644 --- a/tests/freqtradebot/test_freqtradebot.py +++ b/tests/freqtradebot/test_freqtradebot.py @@ -553,7 +553,7 @@ def test_enter_positions_global_pairlock( @pytest.mark.parametrize("is_short", [False, True]) def test_handle_protections(mocker, default_conf_usdt, fee, is_short): - default_conf_usdt["protections"] = [ + default_conf_usdt["_strategy_protections"] = [ {"method": "CooldownPeriod", "stop_duration": 60}, { "method": "StoplossGuard", diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index b25230791..f064247c6 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1299,7 +1299,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad # While this test IS a copy of test_backtest_pricecontours, it's needed to ensure # results do not carry-over to the next run, which is not given by using parametrize. patch_exchange(mocker) - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ { "method": "CooldownPeriod", "stop_duration": 3, @@ -1358,7 +1358,7 @@ def test_backtest_pricecontours( default_conf, mocker, testdatadir, protections, contour, expected ) -> None: if protections: - default_conf["protections"] = protections + default_conf["_strategy_protections"] = protections default_conf["enable_protections"] = True patch_exchange(mocker) diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 3fb27ce3d..45a523c92 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -88,7 +88,7 @@ def generate_mock_trade( def test_protectionmanager(mocker, default_conf): - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ {"method": protection} for protection in constants.AVAILABLE_PROTECTIONS ] freqtrade = get_patched_freqtradebot(mocker, default_conf) @@ -196,7 +196,7 @@ def test_protections_init(default_conf, timeframe, expected_lookback, expected_s @pytest.mark.usefixtures("init_persistence") def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short): # Active for both sides (long and short) - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ {"method": "StoplossGuard", "lookback_period": 60, "stop_duration": 40, "trade_limit": 3} ] freqtrade = get_patched_freqtradebot(mocker, default_conf) @@ -268,7 +268,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short): @pytest.mark.parametrize("only_per_side", [False, True]) @pytest.mark.usefixtures("init_persistence") def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair, only_per_side): - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ { "method": "StoplossGuard", "lookback_period": 60, @@ -379,7 +379,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair @pytest.mark.usefixtures("init_persistence") def test_CooldownPeriod(mocker, default_conf, fee, caplog): - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ { "method": "CooldownPeriod", "stop_duration": 60, @@ -425,7 +425,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog): @pytest.mark.usefixtures("init_persistence") def test_CooldownPeriod_unlock_at(mocker, default_conf, fee, caplog, time_machine): - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ { "method": "CooldownPeriod", "unlock_at": "05:00", @@ -509,7 +509,7 @@ def test_CooldownPeriod_unlock_at(mocker, default_conf, fee, caplog, time_machin @pytest.mark.parametrize("only_per_side", [False, True]) @pytest.mark.usefixtures("init_persistence") def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side): - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ { "method": "LowProfitPairs", "lookback_period": 400, @@ -599,7 +599,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side): @pytest.mark.usefixtures("init_persistence") def test_MaxDrawdown(mocker, default_conf, fee, caplog): - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ { "method": "MaxDrawdown", "lookback_period": 1000, @@ -812,7 +812,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): def test_protection_manager_desc( mocker, default_conf, protectionconf, desc_expected, exception_expected ): - default_conf["protections"] = [protectionconf] + default_conf["_strategy_protections"] = [protectionconf] freqtrade = get_patched_freqtradebot(mocker, default_conf) short_desc = str(freqtrade.protections.short_desc()) diff --git a/tests/rpc/test_rpc_manager.py b/tests/rpc/test_rpc_manager.py index 2792fd082..67755255f 100644 --- a/tests/rpc/test_rpc_manager.py +++ b/tests/rpc/test_rpc_manager.py @@ -173,7 +173,7 @@ def test_startupmessages_telegram_enabled(mocker, default_conf) -> None: telegram_mock.reset_mock() default_conf["dry_run"] = True default_conf["whitelist"] = {"method": "VolumePairList", "config": {"number_assets": 20}} - default_conf["protections"] = [ + default_conf["_strategy_protections"] = [ {"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2, "stop_duration": 60} ] freqtradebot = get_patched_freqtradebot(mocker, default_conf) diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index 71404242a..007c7655e 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -75,15 +75,13 @@ class StrategyTestV3(IStrategy): protection_cooldown_lookback = IntParameter([0, 50], default=30) # TODO: Can this work with protection tests? (replace HyperoptableStrategy implicitly ... ) - # @property - # def protections(self): - # prot = [] - # if self.protection_enabled.value: - # prot.append({ - # "method": "CooldownPeriod", - # "stop_duration_candles": self.protection_cooldown_lookback.value - # }) - # return prot + @property + def protections(self): + prot = [] + if self.protection_enabled.value: + # Workaround to simplify tests. This will not work in real scenarios. + prot = self.config.get("_strategy_protections", {}) + return prot bot_started = False From 23cf9f47b090e4debd4b397b6a0cabc432046c91 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2024 15:06:15 +0200 Subject: [PATCH 6/9] chore: move protection validation to protectionManager --- freqtrade/configuration/config_validation.py | 37 ------------------ freqtrade/plugins/protectionmanager.py | 41 +++++++++++++++++++- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 6a14841ff..536feb535 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -1,7 +1,6 @@ import logging from collections import Counter from copy import deepcopy -from datetime import datetime from typing import Any, Dict from jsonschema import Draft4Validator, validators @@ -84,7 +83,6 @@ def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = Fal _validate_price_config(conf) _validate_edge(conf) _validate_whitelist(conf) - _validate_protections(conf) _validate_unlimited_amount(conf) _validate_ask_orderbook(conf) _validate_freqai_hyperopt(conf) @@ -196,41 +194,6 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None: raise ConfigurationError("StaticPairList requires pair_whitelist to be set.") -def _validate_protections(conf: Dict[str, Any]) -> None: - """ - Validate protection configuration validity - """ - - for prot in conf.get("protections", []): - parsed_unlock_at = None - if (config_unlock_at := prot.get("unlock_at")) is not None: - try: - parsed_unlock_at = datetime.strptime(config_unlock_at, "%H:%M") - except ValueError: - raise ConfigurationError(f"Invalid date format for unlock_at: {config_unlock_at}.") - - if "stop_duration" in prot and "stop_duration_candles" in prot: - raise ConfigurationError( - "Protections must specify either `stop_duration` or `stop_duration_candles`.\n" - f"Please fix the protection {prot.get('method')}." - ) - - if "lookback_period" in prot and "lookback_period_candles" in prot: - raise ConfigurationError( - "Protections must specify either `lookback_period` or `lookback_period_candles`.\n" - f"Please fix the protection {prot.get('method')}." - ) - - if parsed_unlock_at is not None and ( - "stop_duration" in prot or "stop_duration_candles" in prot - ): - raise ConfigurationError( - "Protections must specify either `unlock_at`, `stop_duration` or " - "`stop_duration_candles`.\n" - f"Please fix the protection {prot.get('method')}." - ) - - def _validate_ask_orderbook(conf: Dict[str, Any]) -> None: ask_strategy = conf.get("exit_pricing", {}) ob_min = ask_strategy.get("order_book_min") diff --git a/freqtrade/plugins/protectionmanager.py b/freqtrade/plugins/protectionmanager.py index 4f60ae0e0..1b07261d4 100644 --- a/freqtrade/plugins/protectionmanager.py +++ b/freqtrade/plugins/protectionmanager.py @@ -4,9 +4,10 @@ Protection manager class import logging from datetime import datetime, timezone -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional from freqtrade.constants import Config, LongShort +from freqtrade.exceptions import ConfigurationError from freqtrade.persistence import PairLocks from freqtrade.persistence.models import PairLock from freqtrade.plugins.protections import IProtection @@ -21,6 +22,7 @@ class ProtectionManager: self._config = config self._protection_handlers: List[IProtection] = [] + self.validate_protections(protections) for protection_handler_config in protections: protection_handler = ProtectionResolver.load_protection( protection_handler_config["method"], @@ -76,3 +78,40 @@ class ProtectionManager: pair, lock.until, lock.reason, now=now, side=lock.lock_side ) return result + + @staticmethod + def validate_protections(protections: List[Dict[str, Any]]) -> None: + """ + Validate protection setup validity + """ + + for prot in protections: + parsed_unlock_at = None + if (config_unlock_at := prot.get("unlock_at")) is not None: + try: + parsed_unlock_at = datetime.strptime(config_unlock_at, "%H:%M") + except ValueError: + raise ConfigurationError( + f"Invalid date format for unlock_at: {config_unlock_at}." + ) + + if "stop_duration" in prot and "stop_duration_candles" in prot: + raise ConfigurationError( + "Protections must specify either `stop_duration` or `stop_duration_candles`.\n" + f"Please fix the protection {prot.get('method')}." + ) + + if "lookback_period" in prot and "lookback_period_candles" in prot: + raise ConfigurationError( + "Protections must specify either `lookback_period` or " + f"`lookback_period_candles`.\n Please fix the protection {prot.get('method')}." + ) + + if parsed_unlock_at is not None and ( + "stop_duration" in prot or "stop_duration_candles" in prot + ): + raise ConfigurationError( + "Protections must specify either `unlock_at`, `stop_duration` or " + "`stop_duration_candles`.\n" + f"Please fix the protection {prot.get('method')}." + ) From 87367284781d2e2f48f583362312a4dcda1639c5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2024 15:06:22 +0200 Subject: [PATCH 7/9] chore: remove unused import --- freqtrade/configuration/config_schema.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index 5aa9ef5e3..13f8b8703 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -4,7 +4,6 @@ from typing import Dict from freqtrade.constants import ( AVAILABLE_DATAHANDLERS, AVAILABLE_PAIRLISTS, - AVAILABLE_PROTECTIONS, BACKTEST_BREAKDOWNS, DRY_RUN_WALLET, EXPORT_OPTIONS, From 39c582dac2b2f234ddafe2e0208b67d5cf3f81ec Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2024 15:06:43 +0200 Subject: [PATCH 8/9] tests: move protection-validation test to protection test file --- tests/plugins/test_protections.py | 58 ++++++++++++++++++++++++++++++ tests/test_configuration.py | 59 ------------------------------- 2 files changed, 58 insertions(+), 59 deletions(-) diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 45a523c92..85c03f4fc 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -5,6 +5,7 @@ import pytest from freqtrade import constants from freqtrade.enums import ExitType +from freqtrade.exceptions import OperationalException from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence.trade_model import Order from freqtrade.plugins.protectionmanager import ProtectionManager @@ -101,6 +102,63 @@ def test_protectionmanager(mocker, default_conf): assert handler.stop_per_pair("XRP/BTC", datetime.now(timezone.utc), "*") is None +@pytest.mark.parametrize( + "protconf,expected", + [ + ([], None), + ([{"method": "StoplossGuard", "lookback_period": 2000, "stop_duration_candles": 10}], None), + ([{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 10}], None), + ( + [ + { + "method": "StoplossGuard", + "lookback_period_candles": 20, + "lookback_period": 2000, + "stop_duration": 10, + } + ], + r"Protections must specify either `lookback_period`.*", + ), + ( + [ + { + "method": "StoplossGuard", + "lookback_period": 20, + "stop_duration": 10, + "stop_duration_candles": 10, + } + ], + r"Protections must specify either `stop_duration`.*", + ), + ( + [ + { + "method": "StoplossGuard", + "lookback_period": 20, + "stop_duration": 10, + "unlock_at": "20:02", + } + ], + r"Protections must specify either `unlock_at`, `stop_duration` or.*", + ), + ( + [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "20:02"}], + None, + ), + ( + [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "55:102"}], + "Invalid date format for unlock_at: 55:102.", + ), + ], +) +def test_validate_protections(protconf, expected): + if expected: + with pytest.raises(OperationalException, match=expected): + ProtectionManager.validate_protections(protconf) + else: + ProtectionManager.validate_protections(protconf) + + @pytest.mark.parametrize( "timeframe,expected_lookback,expected_stop,protconf", [ diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 9c76272db..829bf699a 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -812,65 +812,6 @@ def test_validate_whitelist(default_conf): validate_config_consistency(conf) -@pytest.mark.parametrize( - "protconf,expected", - [ - ([], None), - ([{"method": "StoplossGuard", "lookback_period": 2000, "stop_duration_candles": 10}], None), - ([{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 10}], None), - ( - [ - { - "method": "StoplossGuard", - "lookback_period_candles": 20, - "lookback_period": 2000, - "stop_duration": 10, - } - ], - r"Protections must specify either `lookback_period`.*", - ), - ( - [ - { - "method": "StoplossGuard", - "lookback_period": 20, - "stop_duration": 10, - "stop_duration_candles": 10, - } - ], - r"Protections must specify either `stop_duration`.*", - ), - ( - [ - { - "method": "StoplossGuard", - "lookback_period": 20, - "stop_duration": 10, - "unlock_at": "20:02", - } - ], - r"Protections must specify either `unlock_at`, `stop_duration` or.*", - ), - ( - [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "20:02"}], - None, - ), - ( - [{"method": "StoplossGuard", "lookback_period_candles": 20, "unlock_at": "55:102"}], - "Invalid date format for unlock_at: 55:102.", - ), - ], -) -def test_validate_protections(default_conf, protconf, expected): - conf = deepcopy(default_conf) - conf["protections"] = protconf - if expected: - with pytest.raises(OperationalException, match=expected): - validate_config_consistency(conf) - else: - validate_config_consistency(conf) - - def test_validate_ask_orderbook(default_conf, caplog) -> None: conf = deepcopy(default_conf) conf["exit_pricing"]["use_order_book"] = True From f77fedbea45268a1f50ac02377f6af9b710a7bea Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2024 15:08:57 +0200 Subject: [PATCH 9/9] chore: move available_protections constant to test file (it's only used there) --- docs/developer.md | 1 - freqtrade/constants.py | 1 - tests/plugins/test_protections.py | 8 +++++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/developer.md b/docs/developer.md index 127e8e5d5..401de07c1 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -241,7 +241,6 @@ No protection should use datetime directly, but use the provided `date_now` vari !!! Tip "Writing a new Protection" Best copy one of the existing Protections to have a good example. - Don't forget to register your protection in `constants.py` under the variable `AVAILABLE_PROTECTIONS` - otherwise it will not be selectable. #### Implementation of a new protection diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 120f463f3..f1c44d204 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -57,7 +57,6 @@ AVAILABLE_PAIRLISTS = [ "SpreadFilter", "VolatilityFilter", ] -AVAILABLE_PROTECTIONS = ["CooldownPeriod", "LowProfitPairs", "MaxDrawdown", "StoplossGuard"] AVAILABLE_DATAHANDLERS = ["json", "jsongz", "hdf5", "feather", "parquet"] BACKTEST_BREAKDOWNS = ["day", "week", "month"] BACKTEST_CACHE_AGE = ["none", "day", "week", "month"] diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 85c03f4fc..bec2671eb 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -3,7 +3,6 @@ from datetime import datetime, timedelta, timezone import pytest -from freqtrade import constants from freqtrade.enums import ExitType from freqtrade.exceptions import OperationalException from freqtrade.persistence import PairLocks, Trade @@ -12,6 +11,9 @@ from freqtrade.plugins.protectionmanager import ProtectionManager from tests.conftest import get_patched_freqtradebot, log_has_re +AVAILABLE_PROTECTIONS = ["CooldownPeriod", "LowProfitPairs", "MaxDrawdown", "StoplossGuard"] + + def generate_mock_trade( pair: str, fee: float, @@ -90,12 +92,12 @@ def generate_mock_trade( def test_protectionmanager(mocker, default_conf): default_conf["_strategy_protections"] = [ - {"method": protection} for protection in constants.AVAILABLE_PROTECTIONS + {"method": protection} for protection in AVAILABLE_PROTECTIONS ] freqtrade = get_patched_freqtradebot(mocker, default_conf) for handler in freqtrade.protections._protection_handlers: - assert handler.name in constants.AVAILABLE_PROTECTIONS + assert handler.name in AVAILABLE_PROTECTIONS if not handler.has_global_stop: assert handler.global_stop(datetime.now(timezone.utc), "*") is None if not handler.has_local_stop: