Add LowProfitPairs only_per_side option

This commit is contained in:
Matthias 2022-05-07 15:24:31 +02:00
parent 26648e54cc
commit f5f599c7f0
4 changed files with 26 additions and 13 deletions

View File

@ -96,6 +96,8 @@ def protections(self):
`LowProfitPairs` uses all trades for a pair within `lookback_period` in minutes (or in candles when using `lookback_period_candles`) to determine the overall profit ratio.
If that ratio is below `required_profit`, that pair will be locked for `stop_duration` in minutes (or in candles when using `stop_duration_candles`).
For futures bots, setting `only_per_side` will make the bot only consider one side, and will then only lock this one side, allowing for example shorts to continue after a series of long losses.
The below example will stop trading a pair for 60 minutes if the pair does not have a required profit of 2% (and a minimum of 2 trades) within the last 6 candles.
``` python
@ -107,7 +109,8 @@ def protections(self):
"lookback_period_candles": 6,
"trade_limit": 2,
"stop_duration": 60,
"required_profit": 0.02
"required_profit": 0.02,
"only_per_pair": False,
}
]
```

View File

@ -21,6 +21,7 @@ class LowProfitPairs(IProtection):
self._trade_limit = protection_config.get('trade_limit', 1)
self._required_profit = protection_config.get('required_profit', 0.0)
self._only_per_side = protection_config.get('only_per_side', False)
def short_desc(self) -> str:
"""
@ -36,7 +37,8 @@ class LowProfitPairs(IProtection):
return (f'{profit} < {self._required_profit} in {self.lookback_period_str}, '
f'locking for {self.stop_duration_str}.')
def _low_profit(self, date_now: datetime, pair: str) -> Optional[ProtectionReturn]:
def _low_profit(
self, date_now: datetime, pair: str, side: LongShort) -> Optional[ProtectionReturn]:
"""
Evaluate recent trades for pair
"""
@ -54,7 +56,10 @@ class LowProfitPairs(IProtection):
# Not enough trades in the relevant period
return None
profit = sum(trade.close_profit for trade in trades if trade.close_profit)
profit = sum(
trade.close_profit for trade in trades if trade.close_profit
and (not self._only_per_side or trade.trade_direction == side)
)
if profit < self._required_profit:
self.log_once(
f"Trading for {pair} stopped due to {profit:.2f} < {self._required_profit} "
@ -65,6 +70,7 @@ class LowProfitPairs(IProtection):
lock=True,
until=until,
reason=self._reason(profit),
lock_side=(side if self._only_per_side else '*')
)
return None
@ -86,4 +92,4 @@ class LowProfitPairs(IProtection):
:return: Tuple of [bool, until, reason].
If true, this pair will be locked with <reason> until <until>
"""
return self._low_profit(date_now, pair=pair)
return self._low_profit(date_now, pair=pair, side=side)

View File

@ -38,8 +38,8 @@ class StoplossGuard(IProtection):
return (f'{self._trade_limit} stoplosses in {self._lookback_period} min, '
f'locking for {self._stop_duration} min.')
def _stoploss_guard(
self, date_now: datetime, pair: Optional[str], side: str) -> Optional[ProtectionReturn]:
def _stoploss_guard(self, date_now: datetime, pair: Optional[str],
side: LongShort) -> Optional[ProtectionReturn]:
"""
Evaluate recent trades
"""

View File

@ -250,14 +250,16 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
assert not PairLocks.is_global_lock()
@pytest.mark.parametrize('only_per_side', [False, True])
@pytest.mark.usefixtures("init_persistence")
def test_LowProfitPairs(mocker, default_conf, fee, caplog):
def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side):
default_conf['protections'] = [{
"method": "LowProfitPairs",
"lookback_period": 400,
"stop_duration": 60,
"trade_limit": 2,
"required_profit": 0.0,
"only_per_side": only_per_side,
}]
freqtrade = get_patched_freqtradebot(mocker, default_conf)
message = r"Trading stopped due to .*"
@ -292,10 +294,11 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
# Add positive trade
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value,
min_ago_open=20, min_ago_close=10, profit_rate=1.15,
min_ago_open=20, min_ago_close=10, profit_rate=1.15, is_short=True
))
assert not freqtrade.protections.stop_per_pair('XRP/BTC')
assert not PairLocks.is_pair_locked('XRP/BTC')
assert freqtrade.protections.stop_per_pair('XRP/BTC') != only_per_side
assert not PairLocks.is_pair_locked('XRP/BTC', side='*')
assert PairLocks.is_pair_locked('XRP/BTC', side='long') == only_per_side
Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
@ -303,9 +306,10 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
))
# Locks due to 2nd trade
assert not freqtrade.protections.global_stop()
assert freqtrade.protections.stop_per_pair('XRP/BTC')
assert PairLocks.is_pair_locked('XRP/BTC')
assert freqtrade.protections.global_stop() != only_per_side
assert freqtrade.protections.stop_per_pair('XRP/BTC') != only_per_side
assert PairLocks.is_pair_locked('XRP/BTC', side='long')
assert PairLocks.is_pair_locked('XRP/BTC', side='*') != only_per_side
assert not PairLocks.is_global_lock()