From 6d0f16920f47961f9bae3d3be0e316fbbf368bea Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Nov 2020 11:54:11 +0100 Subject: [PATCH] Get Longest lock logic --- freqtrade/freqtradebot.py | 20 +++++++++--- freqtrade/persistence/pairlock_middleware.py | 11 ++++++- tests/plugins/test_pairlocks.py | 32 ++++++++++++++++++++ tests/test_freqtradebot.py | 8 ++--- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 265a8ce10..1e0f5fdf0 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -367,7 +367,13 @@ class FreqtradeBot(LoggingMixin): "but checking to sell open trades.") return trades_created if PairLocks.is_global_lock(): - self.log_once("Global pairlock active. Not creating new trades.", logger.info) + lock = PairLocks.get_pair_longest_lock('*') + if lock: + self.log_once(f"Global pairlock active until " + f"{lock.lock_end_time.strftime(constants.DATETIME_PRINT_FORMAT)}. " + "Not creating new trades.", logger.info) + else: + self.log_once("Global pairlock active. Not creating new trades.", logger.info) return trades_created # Create entity and execute trade for each pair from whitelist for pair in whitelist: @@ -551,9 +557,15 @@ class FreqtradeBot(LoggingMixin): logger.debug(f"create_trade for pair {pair}") analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(pair, self.strategy.timeframe) - if self.strategy.is_pair_locked( - pair, analyzed_df.iloc[-1]['date'] if len(analyzed_df) > 0 else None): - self.log_once(f"Pair {pair} is currently locked.", logger.info) + nowtime = analyzed_df.iloc[-1]['date'] if len(analyzed_df) > 0 else None + if self.strategy.is_pair_locked(pair, nowtime): + lock = PairLocks.get_pair_longest_lock(pair, nowtime) + if lock: + self.log_once(f"Pair {pair} is still locked until " + f"{lock.lock_end_time.strftime(constants.DATETIME_PRINT_FORMAT)}.", + logger.info) + else: + self.log_once(f"Pair {pair} is still locked.", logger.info) return False # get_free_open_trades is checked before create_trade is called diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index 38b5a5d63..de804f025 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -46,7 +46,7 @@ class PairLocks(): PairLocks.locks.append(lock) @staticmethod - def get_pair_locks(pair: Optional[str], now: Optional[datetime] = None) -> List[PairLock]: + def get_pair_locks(pair: str, now: Optional[datetime] = None) -> List[PairLock]: """ Get all currently active locks for this pair :param pair: Pair to check for. Returns all current locks if pair is empty @@ -66,6 +66,15 @@ class PairLocks(): )] return locks + @staticmethod + def get_pair_longest_lock(pair: str, now: Optional[datetime] = None) -> Optional[PairLock]: + """ + Get the lock that expires the latest for the pair given. + """ + locks = PairLocks.get_pair_locks(pair, now) + locks = sorted(locks, key=lambda l: l.lock_end_time, reverse=True) + return locks[0] if locks else None + @staticmethod def unlock_pair(pair: str, now: Optional[datetime] = None) -> None: """ diff --git a/tests/plugins/test_pairlocks.py b/tests/plugins/test_pairlocks.py index 0b6b89717..db7d9f46f 100644 --- a/tests/plugins/test_pairlocks.py +++ b/tests/plugins/test_pairlocks.py @@ -80,3 +80,35 @@ def test_PairLocks(use_db): assert len(PairLock.query.all()) == 0 # Reset use-db variable PairLocks.use_db = True + + +@pytest.mark.parametrize('use_db', (False, True)) +@pytest.mark.usefixtures("init_persistence") +def test_PairLocks_getlongestlock(use_db): + PairLocks.timeframe = '5m' + # No lock should be present + if use_db: + assert len(PairLock.query.all()) == 0 + else: + PairLocks.use_db = False + + assert PairLocks.use_db == use_db + + pair = 'ETH/BTC' + assert not PairLocks.is_pair_locked(pair) + PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime) + # ETH/BTC locked for 4 minutes + assert PairLocks.is_pair_locked(pair) + lock = PairLocks.get_pair_longest_lock(pair) + + assert lock.lock_end_time.replace(tzinfo=timezone.utc) > arrow.utcnow().shift(minutes=3) + assert lock.lock_end_time.replace(tzinfo=timezone.utc) < arrow.utcnow().shift(minutes=14) + + PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=15).datetime) + assert PairLocks.is_pair_locked(pair) + + lock = PairLocks.get_pair_longest_lock(pair) + # Must be longer than above + assert lock.lock_end_time.replace(tzinfo=timezone.utc) > arrow.utcnow().shift(minutes=14) + + PairLocks.use_db = True diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 94ed06cd9..142729f4d 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -692,16 +692,16 @@ def test_enter_positions_global_pairlock(default_conf, ticker, limit_buy_order, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) n = freqtrade.enter_positions() - message = "Global pairlock active. Not creating new trades." + message = r"Global pairlock active until.* Not creating new trades." n = freqtrade.enter_positions() # 0 trades, but it's not because of pairlock. assert n == 0 - assert not log_has(message, caplog) + assert not log_has_re(message, caplog) PairLocks.lock_pair('*', arrow.utcnow().shift(minutes=20).datetime, 'Just because') n = freqtrade.enter_positions() assert n == 0 - assert log_has(message, caplog) + assert log_has_re(message, caplog) def test_create_trade_no_signal(default_conf, fee, mocker) -> None: @@ -3289,7 +3289,7 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo caplog.clear() freqtrade.enter_positions() - assert log_has(f"Pair {trade.pair} is currently locked.", caplog) + assert log_has_re(f"Pair {trade.pair} is still locked.*", caplog) def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_buy_order_open,