diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 4394b783a..1f9a9a5b0 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -668,14 +668,14 @@ class PairLock(_DECL_BASE): id = Column(Integer, primary_key=True) - pair = Column(String, nullable=False) + pair = Column(String, nullable=False, index=True) reason = Column(String, nullable=True) # Time the pair was locked (start time) lock_time = Column(DateTime, nullable=False) # Time until the pair is locked (end time) lock_end_time = Column(DateTime, nullable=False) - active = Column(Boolean, nullable=False, default=True) + active = Column(Boolean, nullable=False, default=True, index=True) def __repr__(self): lock_time = self.lock_time.strftime(DATETIME_PRINT_FORMAT) @@ -696,21 +696,24 @@ class PairLock(_DECL_BASE): PairLock.session.flush() @staticmethod - def get_pair_locks(pair: str, now: Optional[datetime] = None) -> List['PairLock']: + def get_pair_locks(pair: Optional[str], now: Optional[datetime] = None) -> List['PairLock']: """ Get all locks for this pair - :param pair: Pair to check for - :param now: Datetime object (generated via datetime.utcnow()). defaults to datetime.utcnow() + :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) + filters = [func.datetime(PairLock.lock_time) <= now, + func.datetime(PairLock.lock_end_time) >= now, + # Only active locks + PairLock.active.is_(True), ] + if pair: + filters.append(PairLock.pair == pair) return PairLock.query.filter( - PairLock.pair == pair, - func.datetime(PairLock.lock_time) <= now, - func.datetime(PairLock.lock_end_time) >= now, - # Only active locks - PairLock.active.is_(True), + *filters ).all() @staticmethod @@ -731,7 +734,8 @@ class PairLock(_DECL_BASE): def is_pair_locked(pair: str, now: Optional[datetime] = None) -> bool: """ :param pair: Pair to check for - :param now: Datetime object (generated via datetime.utcnow()). defaults to datetime.utcnow() + :param now: Datetime object (generated via datetime.now(timezone.utc)). + defaults to datetime.utcnow() """ if not now: now = datetime.now(timezone.utc) @@ -743,3 +747,12 @@ class PairLock(_DECL_BASE): # Only active locks PairLock.active.is_(True), ).scalar() is not None + + def to_json(self) -> Dict[str, Any]: + return { + 'pair': self.pair, + 'lock_time': self.lock_time, + 'lock_end_time': self.lock_end_time, + 'reason': self.reason, + 'active': self.active, + } diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 911b2d731..dbdb956b6 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -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 Trade +from freqtrade.persistence import PairLock, Trade from freqtrade.rpc.fiat_convert import CryptoToFiatConverter from freqtrade.state import State from freqtrade.strategy.interface import SellType @@ -599,6 +599,17 @@ class RPC: 'total_stake': sum((trade.open_rate * trade.amount) for trade in trades) } + def _rpc_locks(self) -> Dict[str, Any]: + """ Returns the current locks""" + if self._freqtrade.state != State.RUNNING: + raise RPCException('trader is not running') + + locks = PairLock.get_pair_locks(None) + return { + 'lock_count': len(locks), + 'locks': [lock.to_json() for lock in locks] + } + def _rpc_whitelist(self) -> Dict: """ Returns the currently active whitelist""" res = {'method': self._freqtrade.pairlists.name_list, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 7a6607632..6a0fd5acd 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -15,6 +15,7 @@ from telegram.ext import CallbackContext, CommandHandler, Updater from telegram.utils.helpers import escape_markdown from freqtrade.__init__ import __version__ +from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.rpc import RPC, RPCException, RPCMessageType from freqtrade.rpc.fiat_convert import CryptoToFiatConverter @@ -100,6 +101,8 @@ class Telegram(RPC): CommandHandler('performance', self._performance), CommandHandler('daily', self._daily), CommandHandler('count', self._count), + CommandHandler('locks', self._locks), + CommandHandler(['reload_config', 'reload_conf'], self._reload_config), CommandHandler(['show_config', 'show_conf'], self._show_config), CommandHandler('stopbuy', self._stopbuy), @@ -608,6 +611,29 @@ class Telegram(RPC): except RPCException as e: self._send_msg(str(e)) + @authorized_only + def _locks(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /locks. + Returns the number of trades running + :param bot: telegram bot + :param update: message update + :return: None + """ + try: + locks = self._rpc_locks() + message = tabulate([[ + lock['pair'], + lock['lock_end_time'].strftime(DATETIME_PRINT_FORMAT), + lock['reason']] for lock in locks['locks']], + headers=['Pair', 'Until', 'Reason'], + tablefmt='simple') + message = "
{}".format(message) + logger.debug(message) + self._send_msg(message, parse_mode=ParseMode.HTML) + except RPCException as e: + self._send_msg(str(e)) + @authorized_only def _whitelist(self, update: Update, context: CallbackContext) -> None: """ @@ -720,6 +746,7 @@ class Telegram(RPC): "*/performance:* `Show performance of each finished trade grouped by pair`\n" "*/daily