Add stoploss per pair support

This commit is contained in:
Matthias 2020-11-25 11:11:55 +01:00
parent dcdf4a0503
commit dce2364672
3 changed files with 64 additions and 1 deletions

View File

@ -17,6 +17,7 @@ Protections will protect your strategy from unexpected events and market conditi
#### Stoploss Guard #### Stoploss Guard
`StoplossGuard` selects all trades within a `lookback_period` (in minutes), and determines if the amount of trades that resulted in stoploss are above `trade_limit` - in which case trading will stop for `stop_duration`. `StoplossGuard` selects all trades within a `lookback_period` (in minutes), and determines if the amount of trades that resulted in stoploss are above `trade_limit` - in which case trading will stop for `stop_duration`.
This applies across all pairs, unless `only_per_pair` is set to true, which will then only look at one pair at a time.
```json ```json
"protections": [ "protections": [
@ -24,7 +25,8 @@ Protections will protect your strategy from unexpected events and market conditi
"method": "StoplossGuard", "method": "StoplossGuard",
"lookback_period": 60, "lookback_period": 60,
"trade_limit": 4, "trade_limit": 4,
"stop_duration": 60 "stop_duration": 60,
"only_per_pair": false
} }
], ],
``` ```

View File

@ -26,6 +26,7 @@ class StoplossGuard(IProtection):
self._lookback_period = protection_config.get('lookback_period', 60) self._lookback_period = protection_config.get('lookback_period', 60)
self._trade_limit = protection_config.get('trade_limit', 10) self._trade_limit = protection_config.get('trade_limit', 10)
self._stop_duration = protection_config.get('stop_duration', 60) self._stop_duration = protection_config.get('stop_duration', 60)
self._disable_global_stop = protection_config.get('only_per_pair', False)
def short_desc(self) -> str: def short_desc(self) -> str:
""" """
@ -72,6 +73,8 @@ class StoplossGuard(IProtection):
:return: Tuple of [bool, until, reason]. :return: Tuple of [bool, until, reason].
If true, all pairs will be locked with <reason> until <until> If true, all pairs will be locked with <reason> until <until>
""" """
if self._disable_global_stop:
return False, None, None
return self._stoploss_guard(date_now, None) return self._stoploss_guard(date_now, None)
def stop_per_pair(self, pair: str, date_now: datetime) -> ProtectionReturn: def stop_per_pair(self, pair: str, date_now: datetime) -> ProtectionReturn:

View File

@ -95,6 +95,64 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
assert PairLocks.is_global_lock() assert PairLocks.is_global_lock()
@pytest.mark.parametrize('only_per_pair', [False, True])
@pytest.mark.usefixtures("init_persistence")
def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair):
default_conf['protections'] = [{
"method": "StoplossGuard",
"lookback_period": 60,
"trade_limit": 1,
"only_per_pair": only_per_pair
}]
freqtrade = get_patched_freqtradebot(mocker, default_conf)
message = r"Trading stopped due to .*"
pair = 'XRP/BTC'
assert not freqtrade.protections.stop_per_pair(pair)
assert not freqtrade.protections.global_stop()
assert not log_has_re(message, caplog)
caplog.clear()
Trade.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30, profit_rate=0.9,
))
assert not freqtrade.protections.stop_per_pair(pair)
assert not freqtrade.protections.global_stop()
assert not log_has_re(message, caplog)
caplog.clear()
# This trade does not count, as it's closed too long ago
Trade.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
min_ago_open=250, min_ago_close=100, profit_rate=0.9,
))
# Trade does not count for per pair stop as it's the wrong pair.
Trade.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
min_ago_open=240, min_ago_close=30, profit_rate=0.9,
))
# 3 Trades closed - but the 2nd has been closed too long ago.
assert not freqtrade.protections.stop_per_pair(pair)
assert freqtrade.protections.global_stop() != only_per_pair
if not only_per_pair:
assert log_has_re(message, caplog)
else:
assert not log_has_re(message, caplog)
caplog.clear()
# 2nd Trade that counts with correct pair
Trade.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
min_ago_open=180, min_ago_close=30, profit_rate=0.9,
))
assert freqtrade.protections.stop_per_pair(pair)
assert freqtrade.protections.global_stop() != only_per_pair
assert PairLocks.is_pair_locked(pair)
assert PairLocks.is_global_lock() != 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['protections'] = [{