Introduce Pairlocks middleware
This commit is contained in:
@@ -345,23 +345,23 @@ class FreqtradeBot:
|
||||
whitelist = copy.deepcopy(self.active_pair_whitelist)
|
||||
if not whitelist:
|
||||
logger.info("Active pair whitelist is empty.")
|
||||
else:
|
||||
# Remove pairs for currently opened trades from the whitelist
|
||||
for trade in Trade.get_open_trades():
|
||||
if trade.pair in whitelist:
|
||||
whitelist.remove(trade.pair)
|
||||
logger.debug('Ignoring %s in pair whitelist', trade.pair)
|
||||
return trades_created
|
||||
# Remove pairs for currently opened trades from the whitelist
|
||||
for trade in Trade.get_open_trades():
|
||||
if trade.pair in whitelist:
|
||||
whitelist.remove(trade.pair)
|
||||
logger.debug('Ignoring %s in pair whitelist', trade.pair)
|
||||
|
||||
if not whitelist:
|
||||
logger.info("No currency pair in active pair whitelist, "
|
||||
"but checking to sell open trades.")
|
||||
else:
|
||||
# Create entity and execute trade for each pair from whitelist
|
||||
for pair in whitelist:
|
||||
try:
|
||||
trades_created += self.create_trade(pair)
|
||||
except DependencyException as exception:
|
||||
logger.warning('Unable to create trade for %s: %s', pair, exception)
|
||||
if not whitelist:
|
||||
logger.info("No currency pair in active pair whitelist, "
|
||||
"but checking to sell open trades.")
|
||||
return trades_created
|
||||
# Create entity and execute trade for each pair from whitelist
|
||||
for pair in whitelist:
|
||||
try:
|
||||
trades_created += self.create_trade(pair)
|
||||
except DependencyException as exception:
|
||||
logger.warning('Unable to create trade for %s: %s', pair, exception)
|
||||
|
||||
if not trades_created:
|
||||
logger.debug("Found no buy signals for whitelisted currencies. "
|
||||
|
@@ -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
|
||||
|
@@ -684,19 +684,7 @@ class PairLock(_DECL_BASE):
|
||||
f'lock_end_time={lock_end_time})')
|
||||
|
||||
@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
|
||||
)
|
||||
PairLock.session.add(lock)
|
||||
PairLock.session.flush()
|
||||
|
||||
@staticmethod
|
||||
def get_pair_locks(pair: Optional[str], now: Optional[datetime] = None) -> List['PairLock']:
|
||||
def query_pair_locks(pair: Optional[str], now: Optional[datetime] = None) -> Query:
|
||||
"""
|
||||
Get all locks for this pair
|
||||
:param pair: Pair to check for. Returns all current locks if pair is empty
|
||||
@@ -713,41 +701,7 @@ class PairLock(_DECL_BASE):
|
||||
filters.append(PairLock.pair == pair)
|
||||
return PairLock.query.filter(
|
||||
*filters
|
||||
).all()
|
||||
|
||||
@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.utcnow()
|
||||
"""
|
||||
if not now:
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
logger.info(f"Releasing all locks for {pair}.")
|
||||
locks = PairLock.get_pair_locks(pair, now)
|
||||
for lock in locks:
|
||||
lock.active = False
|
||||
PairLock.session.flush()
|
||||
|
||||
@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.utcnow()
|
||||
"""
|
||||
if not now:
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
return PairLock.query.filter(
|
||||
PairLock.pair == pair,
|
||||
func.datetime(PairLock.lock_end_time) >= now,
|
||||
# Only active locks
|
||||
PairLock.active.is_(True),
|
||||
).first() is not None
|
||||
)
|
||||
|
||||
def to_json(self) -> Dict[str, Any]:
|
||||
return {
|
||||
|
97
freqtrade/persistence/pairlock_middleware.py
Normal file
97
freqtrade/persistence/pairlock_middleware.py
Normal file
@@ -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)
|
@@ -19,7 +19,7 @@ from freqtrade.exceptions import ExchangeError, PricingError
|
||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs
|
||||
from freqtrade.loggers import bufferHandler
|
||||
from freqtrade.misc import shorten_date
|
||||
from freqtrade.persistence import PairLock, Trade
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||
from freqtrade.state import State
|
||||
from freqtrade.strategy.interface import SellType
|
||||
@@ -604,7 +604,7 @@ class RPC:
|
||||
if self._freqtrade.state != State.RUNNING:
|
||||
raise RPCException('trader is not running')
|
||||
|
||||
locks = PairLock.get_pair_locks(None)
|
||||
locks = PairLocks.get_pair_locks(None)
|
||||
return {
|
||||
'lock_count': len(locks),
|
||||
'locks': [lock.to_json() for lock in locks]
|
||||
|
@@ -17,7 +17,7 @@ from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.exceptions import OperationalException, StrategyError
|
||||
from freqtrade.exchange import timeframe_to_minutes
|
||||
from freqtrade.exchange.exchange import timeframe_to_next_date
|
||||
from freqtrade.persistence import PairLock, Trade
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||
from freqtrade.wallets import Wallets
|
||||
|
||||
@@ -288,7 +288,7 @@ class IStrategy(ABC):
|
||||
Needs to be timezone aware `datetime.now(timezone.utc)`
|
||||
:param reason: Optional string explaining why the pair was locked.
|
||||
"""
|
||||
PairLock.lock_pair(pair, until, reason)
|
||||
PairLocks.lock_pair(pair, until, reason)
|
||||
|
||||
def unlock_pair(self, pair: str) -> None:
|
||||
"""
|
||||
@@ -297,7 +297,7 @@ class IStrategy(ABC):
|
||||
manually from within the strategy, to allow an easy way to unlock pairs.
|
||||
:param pair: Unlock pair to allow trading again
|
||||
"""
|
||||
PairLock.unlock_pair(pair, datetime.now(timezone.utc))
|
||||
PairLocks.unlock_pair(pair, datetime.now(timezone.utc))
|
||||
|
||||
def is_pair_locked(self, pair: str, candle_date: datetime = None) -> bool:
|
||||
"""
|
||||
@@ -312,10 +312,10 @@ class IStrategy(ABC):
|
||||
|
||||
if not candle_date:
|
||||
# Simple call ...
|
||||
return PairLock.is_pair_locked(pair, candle_date)
|
||||
return PairLocks.is_pair_locked(pair, candle_date)
|
||||
else:
|
||||
lock_time = timeframe_to_next_date(self.timeframe, candle_date)
|
||||
return PairLock.is_pair_locked(pair, lock_time)
|
||||
return PairLocks.is_pair_locked(pair, lock_time)
|
||||
|
||||
def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
|
Reference in New Issue
Block a user