diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 98aeacee9..611b084a9 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -1445,7 +1445,7 @@ class PairLock(_DECL_BASE): f'lock_end_time={lock_end_time}, reason={self.reason}, active={self.active})') @staticmethod - def query_pair_locks(pair: Optional[str], now: datetime) -> Query: + def query_pair_locks(pair: Optional[str], now: datetime, side: str = '*') -> Query: """ Get all currently active locks for this pair :param pair: Pair to check for. Returns all current locks if pair is empty @@ -1456,6 +1456,9 @@ class PairLock(_DECL_BASE): PairLock.active.is_(True), ] if pair: filters.append(PairLock.pair == pair) + if side != '*': + filters.append(PairLock.direction == side) + return PairLock.query.filter( *filters ) diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index afbd9781b..b8a092365 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -31,7 +31,7 @@ class PairLocks(): @staticmethod def lock_pair(pair: str, until: datetime, reason: str = None, *, - now: datetime = None) -> PairLock: + now: datetime = None, side: str) -> PairLock: """ Create PairLock from now to "until". Uses database by default, unless PairLocks.use_db is set to False, @@ -40,12 +40,14 @@ class PairLocks(): :param until: End time of the lock. Will be rounded up to the next candle. :param reason: Reason string that will be shown as reason for the lock :param now: Current timestamp. Used to determine lock start time. + :param side: Side to lock pair, can be 'long', 'short' or '*' """ lock = PairLock( pair=pair, lock_time=now or datetime.now(timezone.utc), lock_end_time=timeframe_to_next_date(PairLocks.timeframe, until), reason=reason, + direction=side, active=True ) if PairLocks.use_db: @@ -56,7 +58,8 @@ class PairLocks(): return lock @staticmethod - def get_pair_locks(pair: Optional[str], now: Optional[datetime] = None) -> List[PairLock]: + def get_pair_locks( + pair: Optional[str], now: Optional[datetime] = None, side: str = '*') -> List[PairLock]: """ Get all currently active locks for this pair :param pair: Pair to check for. Returns all current locks if pair is empty @@ -67,12 +70,13 @@ class PairLocks(): now = datetime.now(timezone.utc) if PairLocks.use_db: - return PairLock.query_pair_locks(pair, now).all() + return PairLock.query_pair_locks(pair, now, side).all() else: locks = [lock for lock in PairLocks.locks if ( lock.lock_end_time >= now and lock.active is True and (pair is None or lock.pair == pair) + and (side == '*' or lock.direction == side) )] return locks @@ -134,7 +138,7 @@ class PairLocks(): lock.active = False @staticmethod - def is_global_lock(now: Optional[datetime] = None) -> bool: + def is_global_lock(now: Optional[datetime] = None, side: str = '*') -> bool: """ :param now: Datetime object (generated via datetime.now(timezone.utc)). defaults to datetime.now(timezone.utc) @@ -142,10 +146,10 @@ class PairLocks(): if not now: now = datetime.now(timezone.utc) - return len(PairLocks.get_pair_locks('*', now)) > 0 + return len(PairLocks.get_pair_locks('*', now, side)) > 0 @staticmethod - def is_pair_locked(pair: str, now: Optional[datetime] = None) -> bool: + def is_pair_locked(pair: str, now: Optional[datetime] = None, side: str = '*') -> bool: """ :param pair: Pair to check for :param now: Datetime object (generated via datetime.now(timezone.utc)). @@ -154,7 +158,10 @@ class PairLocks(): if not now: now = datetime.now(timezone.utc) - return len(PairLocks.get_pair_locks(pair, now)) > 0 or PairLocks.is_global_lock(now) + return ( + len(PairLocks.get_pair_locks(pair, now, side)) > 0 + or PairLocks.is_global_lock(now, side) + ) @staticmethod def get_all_locks() -> List[PairLock]: diff --git a/freqtrade/plugins/protectionmanager.py b/freqtrade/plugins/protectionmanager.py index d46826605..4868f2c33 100644 --- a/freqtrade/plugins/protectionmanager.py +++ b/freqtrade/plugins/protectionmanager.py @@ -54,8 +54,9 @@ class ProtectionManager(): if protection_handler.has_global_stop: lock = protection_handler.global_stop(date_now=now, side=side) if lock and lock.until: - if not PairLocks.is_global_lock(lock.until): - result = PairLocks.lock_pair('*', lock.until, lock.reason, now=now) + if not PairLocks.is_global_lock(lock.until, lock.lock_side): + result = PairLocks.lock_pair( + '*', lock.until, lock.reason, now=now, side=lock.lock_side) return result def stop_per_pair(self, pair, now: Optional[datetime] = None, @@ -68,6 +69,7 @@ class ProtectionManager(): lock = protection_handler.stop_per_pair( pair=pair, date_now=now, side=side) if lock and lock.until: - if not PairLocks.is_pair_locked(pair, lock.until): - result = PairLocks.lock_pair(pair, lock.until, lock.reason, now=now) + if not PairLocks.is_pair_locked(pair, lock.until, lock.lock_side): + result = PairLocks.lock_pair( + pair, lock.until, lock.reason, now=now, side=lock.lock_side) return result diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index 5ec1c0779..890988226 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -20,7 +20,7 @@ class ProtectionReturn: lock: bool until: datetime reason: Optional[str] - lock_side: Optional[str] = None + lock_side: str = '*' class IProtection(LoggingMixin, ABC): diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index d0ac2783d..1943513ca 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -65,7 +65,7 @@ class StoplossGuard(IProtection): lock=True, until=until, reason=self._reason(), - lock_side=(side if self._only_per_side else None) + lock_side=(side if self._only_per_side else '*') ) def global_stop(self, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index afcc1aa99..0a20de08b 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -541,7 +541,7 @@ class IStrategy(ABC, HyperStrategyMixin): """ return self.__class__.__name__ - def lock_pair(self, pair: str, until: datetime, reason: str = None) -> None: + def lock_pair(self, pair: str, until: datetime, reason: str = None, side: str = '*') -> None: """ Locks pair until a given timestamp happens. Locked pairs are not analyzed, and are prevented from opening new trades. @@ -552,7 +552,7 @@ class IStrategy(ABC, HyperStrategyMixin): Needs to be timezone aware `datetime.now(timezone.utc)` :param reason: Optional string explaining why the pair was locked. """ - PairLocks.lock_pair(pair, until, reason) + PairLocks.lock_pair(pair, until, reason, side=side) def unlock_pair(self, pair: str) -> None: """