Document *candles settings, implement validations
This commit is contained in:
parent
eb952d77be
commit
a93bb6853b
@ -21,10 +21,14 @@ All protection end times are rounded up to the next candle to avoid sudden, unex
|
|||||||
|
|
||||||
### Common settings to all Protections
|
### Common settings to all Protections
|
||||||
|
|
||||||
* `method` - Protection name to use.
|
| Parameter| Description |
|
||||||
* `stop_duration` (minutes) - how long should protections be locked.
|
|------------|-------------|
|
||||||
* `lookback_period` (minutes) - Only trades that completed after `current_time - lookback_period` will be considered (may be ignored by some Protections).
|
| method | Protection name to use. <br> **Datatype:** String, selected from [available Protections](#available-protections)
|
||||||
* `trade_limit` - How many trades are required at minimum (not used by all Protections).
|
| stop_duration_candles | For how many candles should the lock be set? <br> **Datatype:** Positive integer (in candles)
|
||||||
|
| stop_duration | how many minutes should protections be locked. <br>Cannot be used together with `stop_duration_candles`. <br> **Datatype:** Float (in minutes)
|
||||||
|
| `lookback_period_candles` | Only trades that completed within the last `lookback_period_candles` candles will be considered. This setting may be ignored by some Protections. <br> **Datatype:** Positive integer (in candles).
|
||||||
|
| lookback_period | Only trades that completed after `current_time - lookback_period` will be considered. <br>Cannot be used together with `lookback_period_candles`. <br>This setting may be ignored by some Protections. <br> **Datatype:** Float (in minutes)
|
||||||
|
| trade_limit | Number of trades required at minimum (not used by all Protections). <br> **Datatype:** Positive integer
|
||||||
|
|
||||||
#### Stoploss Guard
|
#### Stoploss Guard
|
||||||
|
|
||||||
|
@ -74,6 +74,7 @@ def validate_config_consistency(conf: Dict[str, Any]) -> None:
|
|||||||
_validate_trailing_stoploss(conf)
|
_validate_trailing_stoploss(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 configuration before returning
|
# validate configuration before returning
|
||||||
@ -155,3 +156,22 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None:
|
|||||||
if (pl.get('method') == 'StaticPairList'
|
if (pl.get('method') == 'StaticPairList'
|
||||||
and not conf.get('exchange', {}).get('pair_whitelist')):
|
and not conf.get('exchange', {}).get('pair_whitelist')):
|
||||||
raise OperationalException("StaticPairList requires pair_whitelist to be set.")
|
raise OperationalException("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', []):
|
||||||
|
if ('stop_duration' in prot and 'stop_duration_candles' in prot):
|
||||||
|
raise OperationalException(
|
||||||
|
"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_candle' in prot):
|
||||||
|
raise OperationalException(
|
||||||
|
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
|
||||||
|
f"Please fix the protection {prot.get('method')}"
|
||||||
|
)
|
||||||
|
@ -204,8 +204,10 @@ CONF_SCHEMA = {
|
|||||||
'properties': {
|
'properties': {
|
||||||
'method': {'type': 'string', 'enum': AVAILABLE_PROTECTIONS},
|
'method': {'type': 'string', 'enum': AVAILABLE_PROTECTIONS},
|
||||||
'stop_duration': {'type': 'number', 'minimum': 0.0},
|
'stop_duration': {'type': 'number', 'minimum': 0.0},
|
||||||
|
'stop_duration_candles': {'type': 'number', 'minimum': 0},
|
||||||
'trade_limit': {'type': 'number', 'minimum': 1},
|
'trade_limit': {'type': 'number', 'minimum': 1},
|
||||||
'lookback_period': {'type': 'number', 'minimum': 1},
|
'lookback_period': {'type': 'number', 'minimum': 1},
|
||||||
|
'lookback_period_candles': {'type': 'number', 'minimum': 1},
|
||||||
},
|
},
|
||||||
'required': ['method'],
|
'required': ['method'],
|
||||||
}
|
}
|
||||||
|
@ -103,6 +103,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
|
|||||||
"method": "StoplossGuard",
|
"method": "StoplossGuard",
|
||||||
"lookback_period": 60,
|
"lookback_period": 60,
|
||||||
"trade_limit": 1,
|
"trade_limit": 1,
|
||||||
|
"stop_duration": 60,
|
||||||
"only_per_pair": only_per_pair
|
"only_per_pair": only_per_pair
|
||||||
}]
|
}]
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
@ -158,7 +159,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
|
|||||||
def test_CooldownPeriod(mocker, default_conf, fee, caplog):
|
def test_CooldownPeriod(mocker, default_conf, fee, caplog):
|
||||||
default_conf['protections'] = [{
|
default_conf['protections'] = [{
|
||||||
"method": "CooldownPeriod",
|
"method": "CooldownPeriod",
|
||||||
"stopduration": 60,
|
"stop_duration": 60,
|
||||||
}]
|
}]
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
message = r"Trading stopped due to .*"
|
message = r"Trading stopped due to .*"
|
||||||
@ -195,7 +196,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
|
|||||||
default_conf['protections'] = [{
|
default_conf['protections'] = [{
|
||||||
"method": "LowProfitPairs",
|
"method": "LowProfitPairs",
|
||||||
"lookback_period": 400,
|
"lookback_period": 400,
|
||||||
"stopduration": 60,
|
"stop_duration": 60,
|
||||||
"trade_limit": 2,
|
"trade_limit": 2,
|
||||||
"required_profit": 0.0,
|
"required_profit": 0.0,
|
||||||
}]
|
}]
|
||||||
@ -254,7 +255,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
|||||||
default_conf['protections'] = [{
|
default_conf['protections'] = [{
|
||||||
"method": "MaxDrawdown",
|
"method": "MaxDrawdown",
|
||||||
"lookback_period": 1000,
|
"lookback_period": 1000,
|
||||||
"stopduration": 60,
|
"stop_duration": 60,
|
||||||
"trade_limit": 3,
|
"trade_limit": 3,
|
||||||
"max_allowed_drawdown": 0.15
|
"max_allowed_drawdown": 0.15
|
||||||
}]
|
}]
|
||||||
@ -315,21 +316,21 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("protectionconf,desc_expected,exception_expected", [
|
@pytest.mark.parametrize("protectionconf,desc_expected,exception_expected", [
|
||||||
({"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2},
|
({"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2, "stop_duration": 60},
|
||||||
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
|
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
|
||||||
"2 stoplosses within 60 minutes.'}]",
|
"2 stoplosses within 60 minutes.'}]",
|
||||||
None
|
None
|
||||||
),
|
),
|
||||||
({"method": "CooldownPeriod", "stopduration": 60},
|
({"method": "CooldownPeriod", "stop_duration": 60},
|
||||||
"[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 60 min.'}]",
|
"[{'CooldownPeriod': 'CooldownPeriod - Cooldown period of 60 min.'}]",
|
||||||
None
|
None
|
||||||
),
|
),
|
||||||
({"method": "LowProfitPairs", "stopduration": 60},
|
({"method": "LowProfitPairs", "lookback_period": 60, "stop_duration": 60},
|
||||||
"[{'LowProfitPairs': 'LowProfitPairs - Low Profit Protection, locks pairs with "
|
"[{'LowProfitPairs': 'LowProfitPairs - Low Profit Protection, locks pairs with "
|
||||||
"profit < 0.0 within 60 minutes.'}]",
|
"profit < 0.0 within 60 minutes.'}]",
|
||||||
None
|
None
|
||||||
),
|
),
|
||||||
({"method": "MaxDrawdown", "stopduration": 60},
|
({"method": "MaxDrawdown", "lookback_period": 60, "stop_duration": 60},
|
||||||
"[{'MaxDrawdown': 'MaxDrawdown - Max drawdown protection, stop trading if drawdown is > 0.0 "
|
"[{'MaxDrawdown': 'MaxDrawdown - Max drawdown protection, stop trading if drawdown is > 0.0 "
|
||||||
"within 60 minutes.'}]",
|
"within 60 minutes.'}]",
|
||||||
None
|
None
|
||||||
|
@ -879,6 +879,24 @@ 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_candle": 20, "stop_duration": 10}], None),
|
||||||
|
([{"method": "StoplossGuard", "lookback_period_candle": 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`.*'),
|
||||||
|
])
|
||||||
|
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_load_config_test_comments() -> None:
|
def test_load_config_test_comments() -> None:
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user