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.
|
`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`).
|
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.
|
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
|
``` python
|
||||||
@ -107,7 +109,8 @@ def protections(self):
|
|||||||
"lookback_period_candles": 6,
|
"lookback_period_candles": 6,
|
||||||
"trade_limit": 2,
|
"trade_limit": 2,
|
||||||
"stop_duration": 60,
|
"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._trade_limit = protection_config.get('trade_limit', 1)
|
||||||
self._required_profit = protection_config.get('required_profit', 0.0)
|
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:
|
def short_desc(self) -> str:
|
||||||
"""
|
"""
|
||||||
@ -36,7 +37,8 @@ class LowProfitPairs(IProtection):
|
|||||||
return (f'{profit} < {self._required_profit} in {self.lookback_period_str}, '
|
return (f'{profit} < {self._required_profit} in {self.lookback_period_str}, '
|
||||||
f'locking for {self.stop_duration_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
|
Evaluate recent trades for pair
|
||||||
"""
|
"""
|
||||||
@ -54,7 +56,10 @@ class LowProfitPairs(IProtection):
|
|||||||
# Not enough trades in the relevant period
|
# Not enough trades in the relevant period
|
||||||
return None
|
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:
|
if profit < self._required_profit:
|
||||||
self.log_once(
|
self.log_once(
|
||||||
f"Trading for {pair} stopped due to {profit:.2f} < {self._required_profit} "
|
f"Trading for {pair} stopped due to {profit:.2f} < {self._required_profit} "
|
||||||
@ -65,6 +70,7 @@ class LowProfitPairs(IProtection):
|
|||||||
lock=True,
|
lock=True,
|
||||||
until=until,
|
until=until,
|
||||||
reason=self._reason(profit),
|
reason=self._reason(profit),
|
||||||
|
lock_side=(side if self._only_per_side else '*')
|
||||||
)
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
@ -86,4 +92,4 @@ class LowProfitPairs(IProtection):
|
|||||||
:return: Tuple of [bool, until, reason].
|
:return: Tuple of [bool, until, reason].
|
||||||
If true, this pair will be locked with <reason> until <until>
|
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, '
|
return (f'{self._trade_limit} stoplosses in {self._lookback_period} min, '
|
||||||
f'locking for {self._stop_duration} min.')
|
f'locking for {self._stop_duration} min.')
|
||||||
|
|
||||||
def _stoploss_guard(
|
def _stoploss_guard(self, date_now: datetime, pair: Optional[str],
|
||||||
self, date_now: datetime, pair: Optional[str], side: str) -> Optional[ProtectionReturn]:
|
side: LongShort) -> Optional[ProtectionReturn]:
|
||||||
"""
|
"""
|
||||||
Evaluate recent trades
|
Evaluate recent trades
|
||||||
"""
|
"""
|
||||||
|
@ -250,14 +250,16 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
|
|||||||
assert not PairLocks.is_global_lock()
|
assert not PairLocks.is_global_lock()
|
||||||
|
|
||||||
|
|
||||||
|
@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):
|
def test_LowProfitPairs(mocker, default_conf, fee, caplog, only_per_side):
|
||||||
default_conf['protections'] = [{
|
default_conf['protections'] = [{
|
||||||
"method": "LowProfitPairs",
|
"method": "LowProfitPairs",
|
||||||
"lookback_period": 400,
|
"lookback_period": 400,
|
||||||
"stop_duration": 60,
|
"stop_duration": 60,
|
||||||
"trade_limit": 2,
|
"trade_limit": 2,
|
||||||
"required_profit": 0.0,
|
"required_profit": 0.0,
|
||||||
|
"only_per_side": only_per_side,
|
||||||
}]
|
}]
|
||||||
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 .*"
|
||||||
@ -292,10 +294,11 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
|
|||||||
# Add positive trade
|
# Add positive trade
|
||||||
Trade.query.session.add(generate_mock_trade(
|
Trade.query.session.add(generate_mock_trade(
|
||||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value,
|
'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 freqtrade.protections.stop_per_pair('XRP/BTC') != only_per_side
|
||||||
assert not PairLocks.is_pair_locked('XRP/BTC')
|
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(
|
Trade.query.session.add(generate_mock_trade(
|
||||||
'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value,
|
'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
|
# Locks due to 2nd trade
|
||||||
assert not freqtrade.protections.global_stop()
|
assert freqtrade.protections.global_stop() != only_per_side
|
||||||
assert freqtrade.protections.stop_per_pair('XRP/BTC')
|
assert freqtrade.protections.stop_per_pair('XRP/BTC') != only_per_side
|
||||||
assert PairLocks.is_pair_locked('XRP/BTC')
|
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()
|
assert not PairLocks.is_global_lock()
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user