Add LowProfitPairs only_per_side option
This commit is contained in:
parent
26648e54cc
commit
f5f599c7f0
@ -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,
|
||||
}
|
||||
]
|
||||
```
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user