forked from freqtrade/freqtrade
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
217 additions
and
131 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
# flake8: noqa: F401 | ||
|
||
from freqtrade.persistence.models import (Order, PairLock, Trade, clean_dry_run_db, cleanup_db, | ||
init_db) | ||
from freqtrade.persistence.models import Order, Trade, clean_dry_run_db, cleanup_db, init_db | ||
from freqtrade.persistence.pairlock_middleware import PairLocks |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
|
||
|
||
import logging | ||
from datetime import datetime, timezone | ||
from typing import List, Optional | ||
|
||
from freqtrade.persistence.models import PairLock | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class PairLocks(): | ||
""" | ||
Pairlocks intermediate class | ||
""" | ||
|
||
use_db = True | ||
locks: List[PairLock] = [] | ||
|
||
@staticmethod | ||
def lock_pair(pair: str, until: datetime, reason: str = None) -> None: | ||
lock = PairLock( | ||
pair=pair, | ||
lock_time=datetime.now(timezone.utc), | ||
lock_end_time=until, | ||
reason=reason, | ||
active=True | ||
) | ||
if PairLocks.use_db: | ||
PairLock.session.add(lock) | ||
PairLock.session.flush() | ||
else: | ||
PairLocks.locks.append(lock) | ||
|
||
@staticmethod | ||
def get_pair_locks(pair: Optional[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 | ||
:param now: Datetime object (generated via datetime.now(timezone.utc)). | ||
defaults to datetime.utcnow() | ||
""" | ||
if not now: | ||
now = datetime.now(timezone.utc) | ||
|
||
if PairLocks.use_db: | ||
return PairLock.query_pair_locks(pair, now).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) | ||
)] | ||
return locks | ||
|
||
@staticmethod | ||
def unlock_pair(pair: str, now: Optional[datetime] = None) -> None: | ||
""" | ||
Release all locks for this pair. | ||
:param pair: Pair 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) | ||
|
||
logger.info(f"Releasing all locks for {pair}.") | ||
locks = PairLocks.get_pair_locks(pair, now) | ||
for lock in locks: | ||
lock.active = False | ||
if PairLocks.use_db: | ||
PairLock.session.flush() | ||
|
||
@staticmethod | ||
def is_global_lock(now: Optional[datetime] = None) -> bool: | ||
""" | ||
:param now: Datetime object (generated via datetime.now(timezone.utc)). | ||
defaults to datetime.now(timezone.utc) | ||
""" | ||
if not now: | ||
now = datetime.now(timezone.utc) | ||
|
||
return len(PairLocks.get_pair_locks('*', now)) > 0 | ||
|
||
@staticmethod | ||
def is_pair_locked(pair: str, now: Optional[datetime] = None) -> bool: | ||
""" | ||
:param pair: Pair to check for | ||
:param now: Datetime object (generated via datetime.now(timezone.utc)). | ||
defaults to datetime.now(timezone.utc) | ||
""" | ||
if not now: | ||
now = datetime.now(timezone.utc) | ||
|
||
return len(PairLocks.get_pair_locks(pair, now)) > 0 or PairLocks.is_global_lock(now) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
from datetime import datetime, timedelta, timezone | ||
|
||
import arrow | ||
import pytest | ||
|
||
from freqtrade.persistence import PairLocks | ||
from freqtrade.persistence.models import PairLock | ||
|
||
|
||
@pytest.mark.parametrize('use_db', (False, True)) | ||
@pytest.mark.usefixtures("init_persistence") | ||
def test_PairLocks(use_db): | ||
# 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) | ||
|
||
# XRP/BTC should not be locked now | ||
pair = 'XRP/BTC' | ||
assert not PairLocks.is_pair_locked(pair) | ||
# Unlocking a pair that's not locked should not raise an error | ||
PairLocks.unlock_pair(pair) | ||
|
||
PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime) | ||
assert PairLocks.is_pair_locked(pair) | ||
|
||
# Get both locks from above | ||
locks = PairLocks.get_pair_locks(None) | ||
assert len(locks) == 2 | ||
|
||
# Unlock original pair | ||
pair = 'ETH/BTC' | ||
PairLocks.unlock_pair(pair) | ||
assert not PairLocks.is_pair_locked(pair) | ||
assert not PairLocks.is_global_lock() | ||
|
||
pair = 'BTC/USDT' | ||
# Lock until 14:30 | ||
lock_time = datetime(2020, 5, 1, 14, 30, 0, tzinfo=timezone.utc) | ||
PairLocks.lock_pair(pair, lock_time) | ||
|
||
assert not PairLocks.is_pair_locked(pair) | ||
assert PairLocks.is_pair_locked(pair, lock_time + timedelta(minutes=-10)) | ||
assert not PairLocks.is_global_lock(lock_time + timedelta(minutes=-10)) | ||
assert PairLocks.is_pair_locked(pair, lock_time + timedelta(minutes=-50)) | ||
assert not PairLocks.is_global_lock(lock_time + timedelta(minutes=-50)) | ||
|
||
# Should not be locked after time expired | ||
assert not PairLocks.is_pair_locked(pair, lock_time + timedelta(minutes=10)) | ||
|
||
locks = PairLocks.get_pair_locks(pair, lock_time + timedelta(minutes=-2)) | ||
assert len(locks) == 1 | ||
assert 'PairLock' in str(locks[0]) | ||
|
||
# Unlock all | ||
PairLocks.unlock_pair(pair, lock_time + timedelta(minutes=-2)) | ||
assert not PairLocks.is_global_lock(lock_time + timedelta(minutes=-50)) | ||
|
||
# Global lock | ||
PairLocks.lock_pair('*', lock_time) | ||
assert PairLocks.is_global_lock(lock_time + timedelta(minutes=-50)) | ||
# Global lock also locks every pair seperately | ||
assert PairLocks.is_pair_locked(pair, lock_time + timedelta(minutes=-50)) | ||
assert PairLocks.is_pair_locked('XRP/USDT', lock_time + timedelta(minutes=-50)) | ||
|
||
if use_db: | ||
assert len(PairLock.query.all()) > 0 | ||
else: | ||
# Nothing was pushed to the database | ||
assert len(PairLock.query.all()) == 0 | ||
# Reset use-db variable | ||
PairLocks.use_db = True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.