Merge pull request #5798 from incrementby1/personal-branch
Add function to unlock PairLocks by reason
This commit is contained in:
commit
c34b8a95d7
@ -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)`.
|
||||||
|
|
||||||
|
@ -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), ]
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user