Implement *candle definitions

This commit is contained in:
Matthias 2020-12-07 10:45:35 +01:00
parent a93bb6853b
commit d4799e6aa3
5 changed files with 76 additions and 32 deletions

View File

@ -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
} }
], ],

View File

@ -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')}"

View File

@ -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,8 +24,15 @@ 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
self._stop_duration = protection_config.get('stop_duration', 60) tf_in_min = timeframe_to_minutes(config['timeframe'])
self._lookback_period = protection_config.get('lookback_period', 60) 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)
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)
LoggingMixin.__init__(self, logger) LoggingMixin.__init__(self, logger)

View File

@ -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'] = [{

View File

@ -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`.*'),