Add more tests

This commit is contained in:
Matthias 2020-10-15 08:07:09 +02:00
parent 2b85e7eac3
commit 56975db2ed
6 changed files with 54 additions and 21 deletions

View File

@ -79,7 +79,8 @@
{
"method": "StoplossGuard",
"lookback_period": 60,
"trade_limit": 4
"trade_limit": 4,
"stopduration": 60
}
],
"exchange": {

View File

@ -2,10 +2,10 @@
Protection manager class
"""
import logging
from datetime import datetime
from datetime import datetime, timezone
from typing import Dict, List
from freqtrade.exceptions import OperationalException
from freqtrade.persistence import PairLocks
from freqtrade.plugins.protections import IProtection
from freqtrade.resolvers import ProtectionResolver
@ -47,12 +47,13 @@ class ProtectionManager():
return [{p.name: p.short_desc()} for p in self._protection_handlers]
def global_stop(self) -> bool:
now = datetime.utcnow()
now = datetime.now(timezone.utc)
for protection_handler in self._protection_handlers:
result = protection_handler.global_stop(now)
result, until, reason = protection_handler.global_stop(now)
# Early stopping - first positive result stops the application
if result:
# Early stopping - first positive result blocks further trades
if result and until:
PairLocks.lock_pair('*', until, reason)
return True
return False

View File

@ -1,2 +1,2 @@
# flake8: noqa: F401
from freqtrade.plugins.protections.iprotection import IProtection
from freqtrade.plugins.protections.iprotection import IProtection, ProtectionReturn

View File

@ -2,13 +2,15 @@
import logging
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Any, Dict
from typing import Any, Dict, Optional, Tuple
from freqtrade.mixins import LoggingMixin
logger = logging.getLogger(__name__)
ProtectionReturn = Tuple[bool, Optional[datetime], Optional[str]]
class IProtection(LoggingMixin, ABC):
@ -29,7 +31,7 @@ class IProtection(LoggingMixin, ABC):
"""
@abstractmethod
def global_stop(self, date_now: datetime) -> bool:
def global_stop(self, date_now: datetime) -> ProtectionReturn:
"""
Stops trading (position entering) for all pairs
This must evaluate to true for the whole period of the "cooldown period".

View File

@ -1,12 +1,12 @@
import logging
from datetime import datetime, timedelta
from typing import Any, Dict
from typing import Any, Dict, Tuple
from sqlalchemy import and_, or_
from freqtrade.persistence import Trade
from freqtrade.plugins.protections import IProtection
from freqtrade.plugins.protections import IProtection, ProtectionReturn
from freqtrade.strategy.interface import SellType
@ -17,16 +17,26 @@ class StoplossGuard(IProtection):
def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None:
super().__init__(config, protection_config)
self._lookback_period = protection_config.get('lookback_period', 60)
self._trade_limit = protection_config.get('trade_limit', 10)
self._stopduration = protection_config.get('stopduration', 60)
def _reason(self) -> str:
"""
LockReason to use
"""
return (f'{self._trade_limit} stoplosses in {self._lookback_period} min, '
f'locking for {self._stopduration} min.')
def short_desc(self) -> str:
"""
Short method description - used for startup-messages
"""
return f"{self.name} - Frequent Stoploss Guard"
return (f"{self.name} - Frequent Stoploss Guard, {self._trade_limit} stoplosses "
f"within {self._lookback_period} minutes.")
def _stoploss_guard(self, date_now: datetime, pair: str = None) -> bool:
def _stoploss_guard(self, date_now: datetime, pair: str = None) -> ProtectionReturn:
"""
Evaluate recent trades
"""
@ -45,13 +55,16 @@ class StoplossGuard(IProtection):
if len(trades) > self._trade_limit:
self.log_on_refresh(logger.info, f"Trading stopped due to {self._trade_limit} "
f"stoplosses within {self._lookback_period} minutes.")
return True
until = date_now + timedelta(minutes=self._stopduration)
return True, until, self._reason()
return False
return False, None, None
def global_stop(self, date_now: datetime) -> bool:
def global_stop(self, date_now: datetime) -> ProtectionReturn:
"""
Stops trading (position entering) for all pairs
This must evaluate to true for the whole period of the "cooldown period".
:return: Tuple of [bool, until, reason].
If true, all pairs will be locked with <reason> until <until>
"""
return self._stoploss_guard(date_now, pair=None)

View File

@ -1,11 +1,10 @@
from freqtrade.strategy.interface import SellType
from unittest.mock import MagicMock, PropertyMock
import random
import pytest
from datetime import datetime, timedelta
from freqtrade.constants import AVAILABLE_PROTECTIONS
import pytest
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import SellType
from tests.conftest import get_patched_freqtradebot, log_has_re
@ -77,3 +76,20 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
assert freqtrade.protections.global_stop()
assert log_has_re(message, caplog)
@pytest.mark.parametrize("protectionconf,desc_expected,exception_expected", [
({"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2},
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
"2 stoplosses within 60 minutes.'}]",
None
),
])
def test_protection_manager_desc(mocker, default_conf, protectionconf,
desc_expected, exception_expected):
default_conf['protections'] = [protectionconf]
freqtrade = get_patched_freqtradebot(mocker, default_conf)
short_desc = str(freqtrade.protections.short_desc())
assert short_desc == desc_expected