Implement *candle definitions
This commit is contained in:
parent
a93bb6853b
commit
d4799e6aa3
@ -30,20 +30,24 @@ All protection end times are rounded up to the next candle to avoid sudden, unex
|
|||||||
| 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)
|
| 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
|
| trade_limit | Number of trades required at minimum (not used by all Protections). <br> **Datatype:** Positive integer
|
||||||
|
|
||||||
|
!!! Note "Durations"
|
||||||
|
Durations (`stop_duration*` and `lookback_period*` can be defined in either minutes or candles).
|
||||||
|
For more flexibility when testing different timeframes, all below examples will use the "candle" definition.
|
||||||
|
|
||||||
#### Stoploss Guard
|
#### Stoploss Guard
|
||||||
|
|
||||||
`StoplossGuard` selects all trades within `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 `lookback_period`, 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.
|
This applies across all pairs, unless `only_per_pair` is set to true, which will then only look at one pair at a time.
|
||||||
|
|
||||||
The below example stops trading for all pairs for 2 hours (120min) after the last trade if the bot hit stoploss 4 times within the last 24h.
|
The below example stops trading for all pairs for 4 candles after the last trade if the bot hit stoploss 4 times within the last 24 candles.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"protections": [
|
"protections": [
|
||||||
{
|
{
|
||||||
"method": "StoplossGuard",
|
"method": "StoplossGuard",
|
||||||
"lookback_period": 1440,
|
"lookback_period_candles": 24,
|
||||||
"trade_limit": 4,
|
"trade_limit": 4,
|
||||||
"stop_duration": 120,
|
"stop_duration_candles": 4,
|
||||||
"only_per_pair": false
|
"only_per_pair": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -57,15 +61,15 @@ The below example stops trading for all pairs for 2 hours (120min) after the las
|
|||||||
|
|
||||||
`MaxDrawdown` uses all trades within `lookback_period` (in minutes) to determine the maximum drawdown. If the drawdown is below `max_allowed_drawdown`, trading will stop for `stop_duration` (in minutes) after the last trade - assuming that the bot needs some time to let markets recover.
|
`MaxDrawdown` uses all trades within `lookback_period` (in minutes) to determine the maximum drawdown. If the drawdown is below `max_allowed_drawdown`, trading will stop for `stop_duration` (in minutes) after the last trade - assuming that the bot needs some time to let markets recover.
|
||||||
|
|
||||||
The below sample stops trading for 12 hours (720min) if max-drawdown is > 20% considering all trades within the last 2 days (2880min).
|
The below sample stops trading for 12 candles if max-drawdown is > 20% considering all trades within the last 48 candles.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"protections": [
|
"protections": [
|
||||||
{
|
{
|
||||||
"method": "MaxDrawdown",
|
"method": "MaxDrawdown",
|
||||||
"lookback_period": 2880,
|
"lookback_period_candles": 48,
|
||||||
"trade_limit": 20,
|
"trade_limit": 20,
|
||||||
"stop_duration": 720,
|
"stop_duration_candles": 12,
|
||||||
"max_allowed_drawdown": 0.2
|
"max_allowed_drawdown": 0.2
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -77,13 +81,13 @@ The below sample stops trading for 12 hours (720min) if max-drawdown is > 20% co
|
|||||||
`LowProfitPairs` uses all trades for a pair within `lookback_period` (in minutes) to determine the overall profit ratio.
|
`LowProfitPairs` uses all trades for a pair within `lookback_period` (in minutes) to determine the overall profit ratio.
|
||||||
If that ratio is below `required_profit`, that pair will be locked for `stop_duration` (in minutes).
|
If that ratio is below `required_profit`, that pair will be locked for `stop_duration` (in minutes).
|
||||||
|
|
||||||
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 hours (360min).
|
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.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"protections": [
|
"protections": [
|
||||||
{
|
{
|
||||||
"method": "LowProfitPairs",
|
"method": "LowProfitPairs",
|
||||||
"lookback_period": 360,
|
"lookback_period_candles": 6,
|
||||||
"trade_limit": 2,
|
"trade_limit": 2,
|
||||||
"stop_duration": 60,
|
"stop_duration": 60,
|
||||||
"required_profit": 0.02
|
"required_profit": 0.02
|
||||||
@ -95,11 +99,13 @@ The below example will stop trading a pair for 60 minutes if the pair does not h
|
|||||||
|
|
||||||
`CooldownPeriod` locks a pair for `stop_duration` (in minutes) after selling, avoiding a re-entry for this pair for `stop_duration` minutes.
|
`CooldownPeriod` locks a pair for `stop_duration` (in minutes) after selling, avoiding a re-entry for this pair for `stop_duration` minutes.
|
||||||
|
|
||||||
|
The below example will stop trading a pair for 2 candles after closing a trade, allowing this pair to "cool down".
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"protections": [
|
"protections": [
|
||||||
{
|
{
|
||||||
"method": "CooldownPeriod",
|
"method": "CooldownPeriod",
|
||||||
"stop_duration": 60
|
"stop_duration_candle": 2
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
```
|
```
|
||||||
@ -113,46 +119,47 @@ The below example will stop trading a pair for 60 minutes if the pair does not h
|
|||||||
All protections can be combined at will, also with different parameters, creating a increasing wall for under-performing pairs.
|
All protections can be combined at will, also with different parameters, creating a increasing wall for under-performing pairs.
|
||||||
All protections are evaluated in the sequence they are defined.
|
All protections are evaluated in the sequence they are defined.
|
||||||
|
|
||||||
The below example:
|
The below example assumes a timeframe of 1 hour:
|
||||||
|
|
||||||
* Locks each pair after selling for an additional 10 minutes (`CooldownPeriod`), giving other pairs a chance to get filled.
|
* Locks each pair after selling for an additional 5 candles (`CooldownPeriod`), giving other pairs a chance to get filled.
|
||||||
* Stops trading if the last 2 days had 20 trades, which caused a max-drawdown of more than 20%. (`MaxDrawdown`).
|
* Stops trading for 4 hours (`4 * 1h candles`) if the last 2 days (`48 * 1h candles`) had 20 trades, which caused a max-drawdown of more than 20%. (`MaxDrawdown`).
|
||||||
* Stops trading if more than 4 stoploss occur for all pairs within a 1 day (1440min) limit (`StoplossGuard`).
|
* Stops trading if more than 4 stoploss occur for all pairs within a 1 day (`24 * 1h candles`) limit (`StoplossGuard`).
|
||||||
* Locks all pairs that had 4 Trades within the last 6 hours (`60 * 6 = 360`) with a combined profit ratio of below 0.02 (<2%) (`LowProfitPairs`).
|
* Locks all pairs that had 4 Trades within the last 6 hours (`6 * 1h candles`) with a combined profit ratio of below 0.02 (<2%) (`LowProfitPairs`).
|
||||||
* Locks all pairs for 120 minutes that had a profit of below 0.01 (<1%) within the last 24h (`60 * 24 = 1440`), a minimum of 4 trades.
|
* Locks all pairs for 2 candles that had a profit of below 0.01 (<1%) within the last 24h (`24 * 1h candles`), a minimum of 4 trades.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
"timeframe": "1h",
|
||||||
"protections": [
|
"protections": [
|
||||||
{
|
{
|
||||||
"method": "CooldownPeriod",
|
"method": "CooldownPeriod",
|
||||||
"stop_duration": 10
|
"stop_duration_candles": 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"method": "MaxDrawdown",
|
"method": "MaxDrawdown",
|
||||||
"lookback_period": 2880,
|
"lookback_period_candles": 48,
|
||||||
"trade_limit": 20,
|
"trade_limit": 20,
|
||||||
"stop_duration": 720,
|
"stop_duration_candles": 4,
|
||||||
"max_allowed_drawdown": 0.2
|
"max_allowed_drawdown": 0.2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"method": "StoplossGuard",
|
"method": "StoplossGuard",
|
||||||
"lookback_period": 1440,
|
"lookback_period_candles": 24,
|
||||||
"trade_limit": 4,
|
"trade_limit": 4,
|
||||||
"stop_duration": 120,
|
"stop_duration_candles": 2,
|
||||||
"only_per_pair": false
|
"only_per_pair": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"method": "LowProfitPairs",
|
"method": "LowProfitPairs",
|
||||||
"lookback_period": 360,
|
"lookback_period_candles": 6,
|
||||||
"trade_limit": 2,
|
"trade_limit": 2,
|
||||||
"stop_duration": 60,
|
"stop_duration_candles": 60,
|
||||||
"required_profit": 0.02
|
"required_profit": 0.02
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"method": "LowProfitPairs",
|
"method": "LowProfitPairs",
|
||||||
"lookback_period": 1440,
|
"lookback_period_candles": 24,
|
||||||
"trade_limit": 4,
|
"trade_limit": 4,
|
||||||
"stop_duration": 120,
|
"stop_duration_candles": 2,
|
||||||
"required_profit": 0.01
|
"required_profit": 0.01
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -170,7 +170,7 @@ def _validate_protections(conf: Dict[str, Any]) -> None:
|
|||||||
f"Please fix the protection {prot.get('method')}"
|
f"Please fix the protection {prot.get('method')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if ('lookback_period' in prot and 'lookback_period_candle' in prot):
|
if ('lookback_period' in prot and 'lookback_period_candles' in prot):
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
|
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
|
||||||
f"Please fix the protection {prot.get('method')}"
|
f"Please fix the protection {prot.get('method')}"
|
||||||
|
@ -4,6 +4,7 @@ from abc import ABC, abstractmethod
|
|||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
from freqtrade.mixins import LoggingMixin
|
from freqtrade.mixins import LoggingMixin
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
|
|
||||||
@ -23,7 +24,14 @@ class IProtection(LoggingMixin, ABC):
|
|||||||
def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None:
|
def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None:
|
||||||
self._config = config
|
self._config = config
|
||||||
self._protection_config = protection_config
|
self._protection_config = protection_config
|
||||||
|
tf_in_min = timeframe_to_minutes(config['timeframe'])
|
||||||
|
if 'stop_duration_candles' in protection_config:
|
||||||
|
self._stop_duration = (tf_in_min * protection_config.get('stop_duration_candles'))
|
||||||
|
else:
|
||||||
self._stop_duration = protection_config.get('stop_duration', 60)
|
self._stop_duration = protection_config.get('stop_duration', 60)
|
||||||
|
if 'lookback_period_candles' in protection_config:
|
||||||
|
self._lookback_period = tf_in_min * protection_config.get('lookback_period_candles', 60)
|
||||||
|
else:
|
||||||
self._lookback_period = protection_config.get('lookback_period', 60)
|
self._lookback_period = protection_config.get('lookback_period', 60)
|
||||||
|
|
||||||
LoggingMixin.__init__(self, logger)
|
LoggingMixin.__init__(self, logger)
|
||||||
|
@ -3,9 +3,10 @@ from datetime import datetime, timedelta
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade.persistence import PairLocks, Trade
|
|
||||||
from freqtrade.strategy.interface import SellType
|
|
||||||
from freqtrade import constants
|
from freqtrade import constants
|
||||||
|
from freqtrade.persistence import PairLocks, Trade
|
||||||
|
from freqtrade.plugins.protectionmanager import ProtectionManager
|
||||||
|
from freqtrade.strategy.interface import SellType
|
||||||
from tests.conftest import get_patched_freqtradebot, log_has_re
|
from tests.conftest import get_patched_freqtradebot, log_has_re
|
||||||
|
|
||||||
|
|
||||||
@ -49,6 +50,33 @@ def test_protectionmanager(mocker, default_conf):
|
|||||||
assert handler.stop_per_pair('XRP/BTC', datetime.utcnow()) == (False, None, None)
|
assert handler.stop_per_pair('XRP/BTC', datetime.utcnow()) == (False, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('timeframe,expected,protconf', [
|
||||||
|
('1m', [20, 10],
|
||||||
|
[{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 10}]),
|
||||||
|
('5m', [100, 15],
|
||||||
|
[{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 15}]),
|
||||||
|
('1h', [1200, 40],
|
||||||
|
[{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 40}]),
|
||||||
|
('1d', [1440, 5],
|
||||||
|
[{"method": "StoplossGuard", "lookback_period_candles": 1, "stop_duration": 5}]),
|
||||||
|
('1m', [20, 5],
|
||||||
|
[{"method": "StoplossGuard", "lookback_period": 20, "stop_duration_candles": 5}]),
|
||||||
|
('5m', [15, 25],
|
||||||
|
[{"method": "StoplossGuard", "lookback_period": 15, "stop_duration_candles": 5}]),
|
||||||
|
('1h', [50, 600],
|
||||||
|
[{"method": "StoplossGuard", "lookback_period": 50, "stop_duration_candles": 10}]),
|
||||||
|
('1h', [60, 540],
|
||||||
|
[{"method": "StoplossGuard", "lookback_period_candles": 1, "stop_duration_candles": 9}]),
|
||||||
|
])
|
||||||
|
def test_protections_init(mocker, default_conf, timeframe, expected, protconf):
|
||||||
|
default_conf['timeframe'] = timeframe
|
||||||
|
default_conf['protections'] = protconf
|
||||||
|
man = ProtectionManager(default_conf)
|
||||||
|
assert len(man._protection_handlers) == len(protconf)
|
||||||
|
assert man._protection_handlers[0]._lookback_period == expected[0]
|
||||||
|
assert man._protection_handlers[0]._stop_duration == expected[1]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_stoploss_guard(mocker, default_conf, fee, caplog):
|
def test_stoploss_guard(mocker, default_conf, fee, caplog):
|
||||||
default_conf['protections'] = [{
|
default_conf['protections'] = [{
|
||||||
|
@ -879,11 +879,12 @@ def test_validate_whitelist(default_conf):
|
|||||||
|
|
||||||
validate_config_consistency(conf)
|
validate_config_consistency(conf)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('protconf,expected', [
|
@pytest.mark.parametrize('protconf,expected', [
|
||||||
([], None),
|
([], None),
|
||||||
([{"method": "StoplossGuard", "lookback_period": 2000, "stop_duration_candles": 10}], 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_candles": 20, "stop_duration": 10}], None),
|
||||||
([{"method": "StoplossGuard", "lookback_period_candle": 20, "lookback_period": 2000,
|
([{"method": "StoplossGuard", "lookback_period_candles": 20, "lookback_period": 2000,
|
||||||
"stop_duration": 10}], r'Protections must specify either `lookback_period`.*'),
|
"stop_duration": 10}], r'Protections must specify either `lookback_period`.*'),
|
||||||
([{"method": "StoplossGuard", "lookback_period": 20, "stop_duration": 10,
|
([{"method": "StoplossGuard", "lookback_period": 20, "stop_duration": 10,
|
||||||
"stop_duration_candles": 10}], r'Protections must specify either `stop_duration`.*'),
|
"stop_duration_candles": 10}], r'Protections must specify either `stop_duration`.*'),
|
||||||
|
Loading…
Reference in New Issue
Block a user