Merge pull request #10723 from freqtrade/remove/deprecated_protection-setting

Remove long deprecated protections from config setting
This commit is contained in:
Matthias
2024-09-30 06:41:22 +02:00
committed by GitHub
20 changed files with 132 additions and 250 deletions

View File

@@ -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": { "telegram": {
"description": "Telegram settings.", "description": "Telegram settings.",
"type": "object", "type": "object",

View File

@@ -229,7 +229,6 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| | **Plugins** | | **Plugins**
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation of all possible configuration options. | `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). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts | `pairlists` | Define one or more pairlists to be used. [More information](plugins.md#pairlists-and-pairlist-handlers). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts
| `protections` | Define one or more protections to be used. [More information](plugins.md#protections). <br> **Datatype:** List of Dicts
| | **Telegram** | | **Telegram**
| `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean | `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean
| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String | `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String

View File

@@ -75,7 +75,10 @@ Webhook terminology changed from "sell" to "exit", and from "buy" to "entry", re
* `webhooksellfill`, `webhookexitfill` -> `exit_fill` * `webhooksellfill`, `webhookexitfill` -> `exit_fill`
* `webhooksellcancel`, `webhookexitcancel` -> `exit_cancel` * `webhooksellcancel`, `webhookexitcancel` -> `exit_cancel`
## Removal of `populate_any_indicators` ## 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. 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.

View File

@@ -241,7 +241,6 @@ No protection should use datetime directly, but use the provided `date_now` vari
!!! Tip "Writing a new Protection" !!! Tip "Writing a new Protection"
Best copy one of the existing Protections to have a good example. 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 #### Implementation of a new protection

View File

@@ -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. 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 ## Optimizing protections
Freqtrade can also optimize protections. How you optimize protections is up to you, and the following should be considered as example only. Freqtrade can also optimize protections. How you optimize protections is up to you, and the following should be considered as example only.

View File

@@ -1,24 +1,16 @@
## Protections ## 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. 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. 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. 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). Each Protection can be configured multiple times with different parameters, to allow different levels of protection (short-term / long-term).
!!! Note "Backtesting" !!! Note "Backtesting"
Protections are supported by backtesting and hyperopt, but must be explicitly enabled by using the `--enable-protections` flag. 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 ### Available Protections
* [`StoplossGuard`](#stoploss-guard) Stop trading if a certain amount of stoploss occurred within a certain time window. * [`StoplossGuard`](#stoploss-guard) Stop trading if a certain amount of stoploss occurred within a certain time window.

View File

@@ -4,7 +4,6 @@ from typing import Dict
from freqtrade.constants import ( from freqtrade.constants import (
AVAILABLE_DATAHANDLERS, AVAILABLE_DATAHANDLERS,
AVAILABLE_PAIRLISTS, AVAILABLE_PAIRLISTS,
AVAILABLE_PROTECTIONS,
BACKTEST_BREAKDOWNS, BACKTEST_BREAKDOWNS,
DRY_RUN_WALLET, DRY_RUN_WALLET,
EXPORT_OPTIONS, EXPORT_OPTIONS,
@@ -449,60 +448,6 @@ CONF_SCHEMA = {
"required": ["method"], "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 # RPC section
"telegram": { "telegram": {
"description": "Telegram settings.", "description": "Telegram settings.",

View File

@@ -1,7 +1,6 @@
import logging import logging
from collections import Counter from collections import Counter
from copy import deepcopy from copy import deepcopy
from datetime import datetime
from typing import Any, Dict from typing import Any, Dict
from jsonschema import Draft4Validator, validators 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_price_config(conf)
_validate_edge(conf) _validate_edge(conf)
_validate_whitelist(conf) _validate_whitelist(conf)
_validate_protections(conf)
_validate_unlimited_amount(conf) _validate_unlimited_amount(conf)
_validate_ask_orderbook(conf) _validate_ask_orderbook(conf)
_validate_freqai_hyperopt(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.") 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: def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
ask_strategy = conf.get("exit_pricing", {}) ask_strategy = conf.get("exit_pricing", {})
ob_min = ask_strategy.get("order_book_min") ob_min = ask_strategy.get("order_book_min")

View File

@@ -177,4 +177,6 @@ def process_temporary_deprecated_settings(config: Config) -> None:
) )
if "protections" in config: if "protections" in config:
logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.") raise ConfigurationError(
"DEPRECATED: Setting 'protections' in the configuration is deprecated."
)

View File

@@ -57,7 +57,6 @@ AVAILABLE_PAIRLISTS = [
"SpreadFilter", "SpreadFilter",
"VolatilityFilter", "VolatilityFilter",
] ]
AVAILABLE_PROTECTIONS = ["CooldownPeriod", "LowProfitPairs", "MaxDrawdown", "StoplossGuard"]
AVAILABLE_DATAHANDLERS = ["json", "jsongz", "hdf5", "feather", "parquet"] AVAILABLE_DATAHANDLERS = ["json", "jsongz", "hdf5", "feather", "parquet"]
BACKTEST_BREAKDOWNS = ["day", "week", "month"] BACKTEST_BREAKDOWNS = ["day", "week", "month"]
BACKTEST_CACHE_AGE = ["none", "day", "week", "month"] BACKTEST_CACHE_AGE = ["none", "day", "week", "month"]

View File

@@ -273,10 +273,6 @@ class Backtesting:
def _load_protections(self, strategy: IStrategy): def _load_protections(self, strategy: IStrategy):
if self.config.get("enable_protections", False): 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) self.protections = ProtectionManager(self.config, strategy.protections)
def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]: def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]:

View File

@@ -4,9 +4,10 @@ Protection manager class
import logging import logging
from datetime import datetime, timezone 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.constants import Config, LongShort
from freqtrade.exceptions import ConfigurationError
from freqtrade.persistence import PairLocks from freqtrade.persistence import PairLocks
from freqtrade.persistence.models import PairLock from freqtrade.persistence.models import PairLock
from freqtrade.plugins.protections import IProtection from freqtrade.plugins.protections import IProtection
@@ -21,6 +22,7 @@ class ProtectionManager:
self._config = config self._config = config
self._protection_handlers: List[IProtection] = [] self._protection_handlers: List[IProtection] = []
self.validate_protections(protections)
for protection_handler_config in protections: for protection_handler_config in protections:
protection_handler = ProtectionResolver.load_protection( protection_handler = ProtectionResolver.load_protection(
protection_handler_config["method"], protection_handler_config["method"],
@@ -76,3 +78,40 @@ class ProtectionManager:
pair, lock.until, lock.reason, now=now, side=lock.lock_side pair, lock.until, lock.reason, now=now, side=lock.lock_side
) )
return result 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')}."
)

View File

@@ -69,7 +69,6 @@ class StrategyResolver(IResolver):
("order_time_in_force", None), ("order_time_in_force", None),
("stake_currency", None), ("stake_currency", None),
("stake_amount", None), ("stake_amount", None),
("protections", None),
("startup_candle_count", None), ("startup_candle_count", None),
("unfilledtimeout", None), ("unfilledtimeout", None),
("use_exit_signal", True), ("use_exit_signal", True),

View File

@@ -77,7 +77,6 @@ def __run_backtest_bg(btconfig: Config):
lastconfig["timerange"] = btconfig["timerange"] lastconfig["timerange"] = btconfig["timerange"]
lastconfig["timeframe"] = strat.timeframe lastconfig["timeframe"] = strat.timeframe
lastconfig["protections"] = btconfig.get("protections", [])
lastconfig["enable_protections"] = btconfig.get("enable_protections") lastconfig["enable_protections"] = btconfig.get("enable_protections")
lastconfig["dry_run_wallet"] = btconfig.get("dry_run_wallet") lastconfig["dry_run_wallet"] = btconfig.get("dry_run_wallet")

View File

@@ -553,7 +553,7 @@ def test_enter_positions_global_pairlock(
@pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_handle_protections(mocker, default_conf_usdt, fee, is_short): 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": "CooldownPeriod", "stop_duration": 60},
{ {
"method": "StoplossGuard", "method": "StoplossGuard",

View File

@@ -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 # 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. # results do not carry-over to the next run, which is not given by using parametrize.
patch_exchange(mocker) patch_exchange(mocker)
default_conf["protections"] = [ default_conf["_strategy_protections"] = [
{ {
"method": "CooldownPeriod", "method": "CooldownPeriod",
"stop_duration": 3, "stop_duration": 3,
@@ -1358,7 +1358,7 @@ def test_backtest_pricecontours(
default_conf, mocker, testdatadir, protections, contour, expected default_conf, mocker, testdatadir, protections, contour, expected
) -> None: ) -> None:
if protections: if protections:
default_conf["protections"] = protections default_conf["_strategy_protections"] = protections
default_conf["enable_protections"] = True default_conf["enable_protections"] = True
patch_exchange(mocker) patch_exchange(mocker)

View File

@@ -3,14 +3,17 @@ from datetime import datetime, timedelta, timezone
import pytest import pytest
from freqtrade import constants
from freqtrade.enums import ExitType from freqtrade.enums import ExitType
from freqtrade.exceptions import OperationalException
from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence import PairLocks, Trade
from freqtrade.persistence.trade_model import Order from freqtrade.persistence.trade_model import Order
from freqtrade.plugins.protectionmanager import ProtectionManager from freqtrade.plugins.protectionmanager import ProtectionManager
from tests.conftest import get_patched_freqtradebot, log_has_re from tests.conftest import get_patched_freqtradebot, log_has_re
AVAILABLE_PROTECTIONS = ["CooldownPeriod", "LowProfitPairs", "MaxDrawdown", "StoplossGuard"]
def generate_mock_trade( def generate_mock_trade(
pair: str, pair: str,
fee: float, fee: float,
@@ -88,19 +91,76 @@ def generate_mock_trade(
def test_protectionmanager(mocker, default_conf): def test_protectionmanager(mocker, default_conf):
default_conf["protections"] = [ 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) freqtrade = get_patched_freqtradebot(mocker, default_conf)
for handler in freqtrade.protections._protection_handlers: 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: if not handler.has_global_stop:
assert handler.global_stop(datetime.now(timezone.utc), "*") is None assert handler.global_stop(datetime.now(timezone.utc), "*") is None
if not handler.has_local_stop: if not handler.has_local_stop:
assert handler.stop_per_pair("XRP/BTC", datetime.now(timezone.utc), "*") is None 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( @pytest.mark.parametrize(
"timeframe,expected_lookback,expected_stop,protconf", "timeframe,expected_lookback,expected_stop,protconf",
[ [
@@ -196,7 +256,7 @@ def test_protections_init(default_conf, timeframe, expected_lookback, expected_s
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short): def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short):
# Active for both sides (long and 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} {"method": "StoplossGuard", "lookback_period": 60, "stop_duration": 40, "trade_limit": 3}
] ]
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
@@ -268,7 +328,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short):
@pytest.mark.parametrize("only_per_side", [False, True]) @pytest.mark.parametrize("only_per_side", [False, True])
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair, only_per_side): 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", "method": "StoplossGuard",
"lookback_period": 60, "lookback_period": 60,
@@ -379,7 +439,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_CooldownPeriod(mocker, default_conf, fee, caplog): def test_CooldownPeriod(mocker, default_conf, fee, caplog):
default_conf["protections"] = [ default_conf["_strategy_protections"] = [
{ {
"method": "CooldownPeriod", "method": "CooldownPeriod",
"stop_duration": 60, "stop_duration": 60,
@@ -425,7 +485,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_CooldownPeriod_unlock_at(mocker, default_conf, fee, caplog, time_machine): def test_CooldownPeriod_unlock_at(mocker, default_conf, fee, caplog, time_machine):
default_conf["protections"] = [ default_conf["_strategy_protections"] = [
{ {
"method": "CooldownPeriod", "method": "CooldownPeriod",
"unlock_at": "05:00", "unlock_at": "05:00",
@@ -509,7 +569,7 @@ def test_CooldownPeriod_unlock_at(mocker, default_conf, fee, caplog, time_machin
@pytest.mark.parametrize("only_per_side", [False, True]) @pytest.mark.parametrize("only_per_side", [False, True])
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side): def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side):
default_conf["protections"] = [ default_conf["_strategy_protections"] = [
{ {
"method": "LowProfitPairs", "method": "LowProfitPairs",
"lookback_period": 400, "lookback_period": 400,
@@ -599,7 +659,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side):
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_MaxDrawdown(mocker, default_conf, fee, caplog): def test_MaxDrawdown(mocker, default_conf, fee, caplog):
default_conf["protections"] = [ default_conf["_strategy_protections"] = [
{ {
"method": "MaxDrawdown", "method": "MaxDrawdown",
"lookback_period": 1000, "lookback_period": 1000,
@@ -812,7 +872,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
def test_protection_manager_desc( def test_protection_manager_desc(
mocker, default_conf, protectionconf, desc_expected, exception_expected mocker, default_conf, protectionconf, desc_expected, exception_expected
): ):
default_conf["protections"] = [protectionconf] default_conf["_strategy_protections"] = [protectionconf]
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
short_desc = str(freqtrade.protections.short_desc()) short_desc = str(freqtrade.protections.short_desc())

View File

@@ -173,7 +173,7 @@ def test_startupmessages_telegram_enabled(mocker, default_conf) -> None:
telegram_mock.reset_mock() telegram_mock.reset_mock()
default_conf["dry_run"] = True default_conf["dry_run"] = True
default_conf["whitelist"] = {"method": "VolumePairList", "config": {"number_assets": 20}} 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} {"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2, "stop_duration": 60}
] ]
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)

View File

@@ -75,15 +75,13 @@ class StrategyTestV3(IStrategy):
protection_cooldown_lookback = IntParameter([0, 50], default=30) protection_cooldown_lookback = IntParameter([0, 50], default=30)
# TODO: Can this work with protection tests? (replace HyperoptableStrategy implicitly ... ) # TODO: Can this work with protection tests? (replace HyperoptableStrategy implicitly ... )
# @property @property
# def protections(self): def protections(self):
# prot = [] prot = []
# if self.protection_enabled.value: if self.protection_enabled.value:
# prot.append({ # Workaround to simplify tests. This will not work in real scenarios.
# "method": "CooldownPeriod", prot = self.config.get("_strategy_protections", {})
# "stop_duration_candles": self.protection_cooldown_lookback.value return prot
# })
# return prot
bot_started = False bot_started = False

View File

@@ -812,65 +812,6 @@ def test_validate_whitelist(default_conf):
validate_config_consistency(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: def test_validate_ask_orderbook(default_conf, caplog) -> None:
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf["exit_pricing"]["use_order_book"] = True conf["exit_pricing"]["use_order_book"] = True
@@ -1533,8 +1474,8 @@ def test_process_deprecated_protections(default_conf, caplog):
assert not log_has(message, caplog) assert not log_has(message, caplog)
config["protections"] = [] config["protections"] = []
process_temporary_deprecated_settings(config) with pytest.raises(ConfigurationError, match=message):
assert log_has(message, caplog) process_temporary_deprecated_settings(config)
def test_flat_vars_to_nested_dict(caplog): def test_flat_vars_to_nested_dict(caplog):