mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-01-20 14:00:38 +00:00
Merge pull request #10723 from freqtrade/remove/deprecated_protection-setting
Remove long deprecated protections from config setting
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.",
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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."
|
||||||
|
)
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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]:
|
||||||
|
|||||||
@@ -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')}."
|
||||||
|
)
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user