Merge pull request #5798 from incrementby1/personal-branch

Add function to unlock PairLocks by reason
This commit is contained in:
Matthias 2021-10-30 10:15:21 +02:00 committed by GitHub
commit c34b8a95d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 77 additions and 4 deletions

View File

@ -896,7 +896,8 @@ Sometimes it may be desired to lock a pair after certain events happen (e.g. mul
Freqtrade has an easy method to do this from within the strategy, by calling `self.lock_pair(pair, until, [reason])`. Freqtrade has an easy method to do this from within the strategy, by calling `self.lock_pair(pair, until, [reason])`.
`until` must be a datetime object in the future, after which trading will be re-enabled for that pair, while `reason` is an optional string detailing why the pair was locked. `until` must be a datetime object in the future, after which trading will be re-enabled for that pair, while `reason` is an optional string detailing why the pair was locked.
Locks can also be lifted manually, by calling `self.unlock_pair(pair)`. Locks can also be lifted manually, by calling `self.unlock_pair(pair)` or `self.unlock_reason(<reason>)` - providing reason the pair was locked with.
`self.unlock_reason(<reason>)` will unlock all pairs currently locked with the provided reason.
To verify if a pair is currently locked, use `self.is_pair_locked(pair)`. To verify if a pair is currently locked, use `self.is_pair_locked(pair)`.

View File

@ -896,7 +896,7 @@ class PairLock(_DECL_BASE):
lock_time = self.lock_time.strftime(DATETIME_PRINT_FORMAT) lock_time = self.lock_time.strftime(DATETIME_PRINT_FORMAT)
lock_end_time = self.lock_end_time.strftime(DATETIME_PRINT_FORMAT) lock_end_time = self.lock_end_time.strftime(DATETIME_PRINT_FORMAT)
return (f'PairLock(id={self.id}, pair={self.pair}, lock_time={lock_time}, ' return (f'PairLock(id={self.id}, pair={self.pair}, lock_time={lock_time}, '
f'lock_end_time={lock_end_time})') f'lock_end_time={lock_end_time}, reason={self.reason}, active={self.active})')
@staticmethod @staticmethod
def query_pair_locks(pair: Optional[str], now: datetime) -> Query: def query_pair_locks(pair: Optional[str], now: datetime) -> Query:
@ -905,7 +905,6 @@ class PairLock(_DECL_BASE):
:param pair: Pair to check for. Returns all current locks if pair is empty :param pair: Pair to check for. Returns all current locks if pair is empty
:param now: Datetime object (generated via datetime.now(timezone.utc)). :param now: Datetime object (generated via datetime.now(timezone.utc)).
""" """
filters = [PairLock.lock_end_time > now, filters = [PairLock.lock_end_time > now,
# Only active locks # Only active locks
PairLock.active.is_(True), ] PairLock.active.is_(True), ]

View File

@ -103,6 +103,36 @@ class PairLocks():
if PairLocks.use_db: if PairLocks.use_db:
PairLock.query.session.commit() PairLock.query.session.commit()
@staticmethod
def unlock_reason(reason: str, now: Optional[datetime] = None) -> None:
"""
Release all locks for this reason.
:param reason: Which reason to unlock
:param now: Datetime object (generated via datetime.now(timezone.utc)).
defaults to datetime.now(timezone.utc)
"""
if not now:
now = datetime.now(timezone.utc)
if PairLocks.use_db:
# used in live modes
logger.info(f"Releasing all locks with reason '{reason}':")
filters = [PairLock.lock_end_time > now,
PairLock.active.is_(True),
PairLock.reason == reason
]
locks = PairLock.query.filter(*filters)
for lock in locks:
logger.info(f"Releasing lock for {lock.pair} with reason '{reason}'.")
lock.active = False
PairLock.query.session.commit()
else:
# used in backtesting mode; don't show log messages for speed
locks = PairLocks.get_pair_locks(None)
for lock in locks:
if lock.reason == reason:
lock.active = False
@staticmethod @staticmethod
def is_global_lock(now: Optional[datetime] = None) -> bool: def is_global_lock(now: Optional[datetime] = None) -> bool:
""" """
@ -128,7 +158,9 @@ class PairLocks():
@staticmethod @staticmethod
def get_all_locks() -> List[PairLock]: def get_all_locks() -> List[PairLock]:
"""
Return all locks, also locks with expired end date
"""
if PairLocks.use_db: if PairLocks.use_db:
return PairLock.query.all() return PairLock.query.all()
else: else:

View File

@ -443,6 +443,15 @@ class IStrategy(ABC, HyperStrategyMixin):
""" """
PairLocks.unlock_pair(pair, datetime.now(timezone.utc)) PairLocks.unlock_pair(pair, datetime.now(timezone.utc))
def unlock_reason(self, reason: str) -> None:
"""
Unlocks all pairs previously locked using lock_pair with specified reason.
Not used by freqtrade itself, but intended to be used if users lock pairs
manually from within the strategy, to allow an easy way to unlock pairs.
:param reason: Unlock pairs to allow trading again
"""
PairLocks.unlock_reason(reason, datetime.now(timezone.utc))
def is_pair_locked(self, pair: str, candle_date: datetime = None) -> bool: def is_pair_locked(self, pair: str, candle_date: datetime = None) -> bool:
""" """
Checks if a pair is currently locked Checks if a pair is currently locked

View File

@ -116,3 +116,28 @@ def test_PairLocks_getlongestlock(use_db):
PairLocks.reset_locks() PairLocks.reset_locks()
PairLocks.use_db = True PairLocks.use_db = True
@pytest.mark.parametrize('use_db', (False, True))
@pytest.mark.usefixtures("init_persistence")
def test_PairLocks_reason(use_db):
PairLocks.timeframe = '5m'
PairLocks.use_db = use_db
# No lock should be present
if use_db:
assert len(PairLock.query.all()) == 0
assert PairLocks.use_db == use_db
PairLocks.lock_pair('XRP/USDT', arrow.utcnow().shift(minutes=4).datetime, 'TestLock1')
PairLocks.lock_pair('ETH/USDT', arrow.utcnow().shift(minutes=4).datetime, 'TestLock2')
assert PairLocks.is_pair_locked('XRP/USDT')
assert PairLocks.is_pair_locked('ETH/USDT')
PairLocks.unlock_reason('TestLock1')
assert not PairLocks.is_pair_locked('XRP/USDT')
assert PairLocks.is_pair_locked('ETH/USDT')
PairLocks.reset_locks()
PairLocks.use_db = True

View File

@ -575,6 +575,13 @@ def test_is_pair_locked(default_conf):
strategy.unlock_pair(pair) strategy.unlock_pair(pair)
assert not strategy.is_pair_locked(pair) assert not strategy.is_pair_locked(pair)
# Lock with reason
reason = "TestLockR"
strategy.lock_pair(pair, arrow.now(timezone.utc).shift(minutes=4).datetime, reason)
assert strategy.is_pair_locked(pair)
strategy.unlock_reason(reason)
assert not strategy.is_pair_locked(pair)
pair = 'BTC/USDT' pair = 'BTC/USDT'
# Lock until 14:30 # Lock until 14:30
lock_time = datetime(2020, 5, 1, 14, 30, 0, tzinfo=timezone.utc) lock_time = datetime(2020, 5, 1, 14, 30, 0, tzinfo=timezone.utc)