diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 33cfc3e8f..34802f920 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -2,24 +2,34 @@ This module contains class to define a RPC communications """ import logging +from abc import abstractmethod from datetime import datetime, timedelta, date from decimal import Decimal -from typing import Dict, Tuple, Any +from typing import Dict, Tuple, Any, List import arrow import sqlalchemy as sql -from pandas import DataFrame from numpy import mean, nan_to_num +from pandas import DataFrame from freqtrade import exchange from freqtrade.misc import shorten_date from freqtrade.persistence import Trade from freqtrade.state import State - logger = logging.getLogger(__name__) +class RPCException(Exception): + """ + Should be raised with a rpc-formatted message in an _rpc_* method + if the required state is wrong, i.e.: + + raise RPCException('*Status:* `no active trade`') + """ + pass + + class RPC(object): """ RPC class can be used to have extra feature, like bot data, and access to DB data @@ -30,20 +40,32 @@ class RPC(object): :param freqtrade: Instance of a freqtrade bot :return: None """ - self.freqtrade = freqtrade + self._freqtrade = freqtrade - def rpc_trade_status(self) -> Tuple[bool, Any]: + @abstractmethod + def cleanup(self) -> None: + """ Cleanup pending module resources """ + + @property + @abstractmethod + def name(self) -> str: + """ Returns the lowercase name of this module """ + + @abstractmethod + def send_msg(self, msg: str) -> None: + """ Sends a message to all registered rpc modules """ + + def _rpc_trade_status(self) -> List[str]: """ Below follows the RPC backend it is prefixed with rpc_ to raise awareness that it is a remotely exposed function - :return: """ # Fetch open trade trades = Trade.query.filter(Trade.is_open.is_(True)).all() - if self.freqtrade.state != State.RUNNING: - return True, '*Status:* `trader is not running`' + if self._freqtrade.state != State.RUNNING: + raise RPCException('*Status:* `trader is not running`') elif not trades: - return True, '*Status:* `no active trade`' + raise RPCException('*Status:* `no active trade`') else: result = [] for trade in trades: @@ -82,14 +104,14 @@ class RPC(object): ) if order else None, ) result.append(message) - return False, result + return result - def rpc_status_table(self) -> Tuple[bool, Any]: + def _rpc_status_table(self) -> DataFrame: trades = Trade.query.filter(Trade.is_open.is_(True)).all() - if self.freqtrade.state != State.RUNNING: - return True, '*Status:* `trader is not running`' + if self._freqtrade.state != State.RUNNING: + raise RPCException('*Status:* `trader is not running`') elif not trades: - return True, '*Status:* `no active order`' + raise RPCException('*Status:* `no active order`') else: trades_list = [] for trade in trades: @@ -105,22 +127,18 @@ class RPC(object): columns = ['ID', 'Pair', 'Since', 'Profit'] df_statuses = DataFrame.from_records(trades_list, columns=columns) df_statuses = df_statuses.set_index(columns[0]) - # The style used throughout is to return a tuple - # consisting of (error_occured?, result) - # Another approach would be to just return the - # result, or raise error - return False, df_statuses + return df_statuses - def rpc_daily_profit( + def _rpc_daily_profit( self, timescale: int, - stake_currency: str, fiat_display_currency: str) -> Tuple[bool, Any]: + stake_currency: str, fiat_display_currency: str) -> List[List[Any]]: today = datetime.utcnow().date() profit_days: Dict[date, Dict] = {} if not (isinstance(timescale, int) and timescale > 0): - return True, '*Daily [n]:* `must be an integer greater than 0`' + raise RPCException('*Daily [n]:* `must be an integer greater than 0`') - fiat = self.freqtrade.fiat_converter + fiat = self._freqtrade.fiat_converter for day in range(0, timescale): profitday = today - timedelta(days=day) trades = Trade.query \ @@ -135,7 +153,7 @@ class RPC(object): 'trades': len(trades) } - stats = [ + return [ [ key, '{value:.8f} {symbol}'.format( @@ -157,13 +175,10 @@ class RPC(object): ] for key, value in profit_days.items() ] - return False, stats - def rpc_trade_statistics( - self, stake_currency: str, fiat_display_currency: str) -> Tuple[bool, Any]: - """ - :return: cumulative profit statistics. - """ + def _rpc_trade_statistics( + self, stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]: + """ Returns cumulative profit statistics """ trades = Trade.query.order_by(Trade.id).all() profit_all_coin = [] @@ -201,13 +216,13 @@ class RPC(object): .order_by(sql.text('profit_sum DESC')).first() if not best_pair: - return True, '*Status:* `no closed trade`' + raise RPCException('*Status:* `no closed trade`') bp_pair, bp_rate = best_pair # FIX: we want to keep fiatconverter in a state/environment, # doing this will utilize its caching functionallity, instead we reinitialize it here - fiat = self.freqtrade.fiat_converter + fiat = self._freqtrade.fiat_converter # Prepare data to display profit_closed_coin = round(sum(profit_closed_coin), 8) profit_closed_percent = round(nan_to_num(mean(profit_closed_percent)) * 100, 2) @@ -224,35 +239,29 @@ class RPC(object): fiat_display_currency ) num = float(len(durations) or 1) - return ( - False, - { - 'profit_closed_coin': profit_closed_coin, - 'profit_closed_percent': profit_closed_percent, - 'profit_closed_fiat': profit_closed_fiat, - 'profit_all_coin': profit_all_coin, - 'profit_all_percent': profit_all_percent, - 'profit_all_fiat': profit_all_fiat, - 'trade_count': len(trades), - 'first_trade_date': arrow.get(trades[0].open_date).humanize(), - 'latest_trade_date': arrow.get(trades[-1].open_date).humanize(), - 'avg_duration': str(timedelta(seconds=sum(durations) / num)).split('.')[0], - 'best_pair': bp_pair, - 'best_rate': round(bp_rate * 100, 2) - } - ) + return { + 'profit_closed_coin': profit_closed_coin, + 'profit_closed_percent': profit_closed_percent, + 'profit_closed_fiat': profit_closed_fiat, + 'profit_all_coin': profit_all_coin, + 'profit_all_percent': profit_all_percent, + 'profit_all_fiat': profit_all_fiat, + 'trade_count': len(trades), + 'first_trade_date': arrow.get(trades[0].open_date).humanize(), + 'latest_trade_date': arrow.get(trades[-1].open_date).humanize(), + 'avg_duration': str(timedelta(seconds=sum(durations) / num)).split('.')[0], + 'best_pair': bp_pair, + 'best_rate': round(bp_rate * 100, 2), + } - def rpc_balance(self, fiat_display_currency: str) -> Tuple[bool, Any]: - """ - :return: current account balance per crypto - """ + def _rpc_balance(self, fiat_display_currency: str) -> Tuple[List[Dict], float, str, float]: + """ Returns current account balance per crypto """ output = [] total = 0.0 for coin, balance in exchange.get_balances().items(): if not balance['total']: continue - rate = None if coin == 'BTC': rate = 1.0 else: @@ -272,44 +281,39 @@ class RPC(object): } ) if total == 0.0: - return True, '`All balances are zero.`' + raise RPCException('`All balances are zero.`') - fiat = self.freqtrade.fiat_converter + fiat = self._freqtrade.fiat_converter symbol = fiat_display_currency value = fiat.convert_amount(total, 'BTC', symbol) - return False, (output, total, symbol, value) + return output, total, symbol, value - def rpc_start(self) -> Tuple[bool, str]: - """ - Handler for start. - """ - if self.freqtrade.state == State.RUNNING: - return True, '*Status:* `already running`' + def _rpc_start(self) -> str: + """ Handler for start """ + if self._freqtrade.state == State.RUNNING: + return '*Status:* `already running`' - self.freqtrade.state = State.RUNNING - return False, '`Starting trader ...`' + self._freqtrade.state = State.RUNNING + return '`Starting trader ...`' - def rpc_stop(self) -> Tuple[bool, str]: - """ - Handler for stop. - """ - if self.freqtrade.state == State.RUNNING: - self.freqtrade.state = State.STOPPED - return False, '`Stopping trader ...`' + def _rpc_stop(self) -> str: + """ Handler for stop """ + if self._freqtrade.state == State.RUNNING: + self._freqtrade.state = State.STOPPED + return '`Stopping trader ...`' - return True, '*Status:* `already stopped`' + return '*Status:* `already stopped`' - def rpc_reload_conf(self) -> str: + def _rpc_reload_conf(self) -> str: """ Handler for reload_conf. """ - self.freqtrade.state = State.RELOAD_CONF + self._freqtrade.state = State.RELOAD_CONF return '*Status:* `Reloading config ...`' # FIX: no test for this!!!! - def rpc_forcesell(self, trade_id) -> Tuple[bool, Any]: + def _rpc_forcesell(self, trade_id) -> None: """ Handler for forcesell . Sells the given trade at current price - :return: error or None """ def _exec_forcesell(trade: Trade) -> None: # Check if there is there is an open order @@ -335,17 +339,17 @@ class RPC(object): # Get current rate and execute sell current_rate = exchange.get_ticker(trade.pair, False)['bid'] - self.freqtrade.execute_sell(trade, current_rate) + self._freqtrade.execute_sell(trade, current_rate) # ---- EOF def _exec_forcesell ---- - if self.freqtrade.state != State.RUNNING: - return True, '`trader is not running`' + if self._freqtrade.state != State.RUNNING: + raise RPCException('`trader is not running`') if trade_id == 'all': # Execute sell for all open orders for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): _exec_forcesell(trade) - return False, '' + return # Query for trade trade = Trade.query.filter( @@ -356,19 +360,18 @@ class RPC(object): ).first() if not trade: logger.warning('forcesell: Invalid argument received') - return True, 'Invalid argument.' + raise RPCException('Invalid argument.') _exec_forcesell(trade) Trade.session.flush() - return False, '' - def rpc_performance(self) -> Tuple[bool, Any]: + def _rpc_performance(self) -> List[Dict]: """ Handler for performance. Shows a performance statistic from finished trades """ - if self.freqtrade.state != State.RUNNING: - return True, '`trader is not running`' + if self._freqtrade.state != State.RUNNING: + raise RPCException('`trader is not running`') pair_rates = Trade.session.query(Trade.pair, sql.func.sum(Trade.close_profit).label('profit_sum'), @@ -377,19 +380,14 @@ class RPC(object): .group_by(Trade.pair) \ .order_by(sql.text('profit_sum DESC')) \ .all() - trades = [] - for (pair, rate, count) in pair_rates: - trades.append({'pair': pair, 'profit': round(rate * 100, 2), 'count': count}) + return [ + {'pair': pair, 'profit': round(rate * 100, 2), 'count': count} + for pair, rate, count in pair_rates + ] - return False, trades + def _rpc_count(self) -> List[Trade]: + """ Returns the number of trades running """ + if self._freqtrade.state != State.RUNNING: + raise RPCException('`trader is not running`') - def rpc_count(self) -> Tuple[bool, Any]: - """ - Returns the number of trades running - :return: None - """ - if self.freqtrade.state != State.RUNNING: - return True, '`trader is not running`' - - trades = Trade.query.filter(Trade.is_open.is_(True)).all() - return False, trades + return Trade.query.filter(Trade.is_open.is_(True)).all() diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index 58e9bf2b9..252bbcdd8 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -1,11 +1,10 @@ """ This module contains class to manage RPC communications (Telegram, Slack, ...) """ -from typing import Any, List import logging +from typing import List -from freqtrade.rpc.telegram import Telegram - +from freqtrade.rpc.rpc import RPC logger = logging.getLogger(__name__) @@ -15,36 +14,23 @@ class RPCManager(object): Class to manage RPC objects (Telegram, Slack, ...) """ def __init__(self, freqtrade) -> None: - """ - Initializes all enabled rpc modules - :param config: config to use - :return: None - """ - self.freqtrade = freqtrade + """ Initializes all enabled rpc modules """ + self.registered_modules: List[RPC] = [] - self.registered_modules: List[str] = [] - self.telegram: Any = None - self._init() - - def _init(self) -> None: - """ - Init RPC modules - :return: - """ - if self.freqtrade.config['telegram'].get('enabled', False): + # Enable telegram + if freqtrade.config['telegram'].get('enabled', False): logger.info('Enabling rpc.telegram ...') - self.registered_modules.append('telegram') - self.telegram = Telegram(self.freqtrade) + from freqtrade.rpc.telegram import Telegram + self.registered_modules.append(Telegram(freqtrade)) def cleanup(self) -> None: - """ - Stops all enabled rpc modules - :return: None - """ - if 'telegram' in self.registered_modules: - logger.info('Cleaning up rpc.telegram ...') - self.registered_modules.remove('telegram') - self.telegram.cleanup() + """ Stops all enabled rpc modules """ + logger.info('Cleaning up rpc modules ...') + while self.registered_modules: + mod = self.registered_modules.pop() + logger.debug('Cleaning up rpc.%s ...', mod.name) + mod.cleanup() + del mod def send_msg(self, msg: str) -> None: """ @@ -52,6 +38,7 @@ class RPCManager(object): :param msg: message :return: None """ - logger.info(msg) - if 'telegram' in self.registered_modules: - self.telegram.send_msg(msg) + logger.info('Sending rpc message: %s', msg) + for mod in self.registered_modules: + logger.debug('Forwarding message to rpc.%s', mod.name) + mod.send_msg(msg) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 43383fe43..4dd23971b 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -12,11 +12,12 @@ from telegram.error import NetworkError, TelegramError from telegram.ext import CommandHandler, Updater from freqtrade.__init__ import __version__ -from freqtrade.rpc.rpc import RPC - +from freqtrade.rpc.rpc import RPC, RPCException logger = logging.getLogger(__name__) +logger.debug('Included module rpc.telegram ...') + def authorized_only(command_handler: Callable[[Any, Bot, Update], None]) -> Callable[..., Any]: """ @@ -25,9 +26,7 @@ def authorized_only(command_handler: Callable[[Any, Bot, Update], None]) -> Call :return: decorated function """ def wrapper(self, *args, **kwargs): - """ - Decorator logic - """ + """ Decorator logic """ update = kwargs.get('update') or args[1] # Reject unauthorized messages @@ -54,9 +53,12 @@ def authorized_only(command_handler: Callable[[Any, Bot, Update], None]) -> Call class Telegram(RPC): - """ - Telegram, this class send messages to Telegram - """ + """ This class handles all telegram communication """ + + @property + def name(self) -> str: + return "telegram" + def __init__(self, freqtrade) -> None: """ Init the Telegram call, and init the super class RPC @@ -74,12 +76,7 @@ class Telegram(RPC): Initializes this module with the given config, registers all known command handlers and starts polling for message updates - :param config: config to use - :return: None """ - if not self.is_enabled(): - return - self._updater = Updater(token=self._config['telegram']['token'], workers=0) # Register command handler and start telegram message polling @@ -115,16 +112,11 @@ class Telegram(RPC): Stops all running telegram threads. :return: None """ - if not self.is_enabled(): - return - self._updater.stop() - def is_enabled(self) -> bool: - """ - Returns True if the telegram module is activated, False otherwise - """ - return bool(self._config.get('telegram', {}).get('enabled', False)) + def send_msg(self, msg: str) -> None: + """ Send a message to telegram channel """ + self._send_msg(msg) @authorized_only def _status(self, bot: Bot, update: Update) -> None: @@ -143,13 +135,11 @@ class Telegram(RPC): self._status_table(bot, update) return - # Fetch open trade - (error, trades) = self.rpc_trade_status() - if error: - self.send_msg(trades, bot=bot) - else: - for trademsg in trades: - self.send_msg(trademsg, bot=bot) + try: + for trade_msg in self._rpc_trade_status(): + self._send_msg(trade_msg, bot=bot) + except RPCException as e: + self._send_msg(str(e), bot=bot) @authorized_only def _status_table(self, bot: Bot, update: Update) -> None: @@ -160,15 +150,12 @@ class Telegram(RPC): :param update: message update :return: None """ - # Fetch open trade - (err, df_statuses) = self.rpc_status_table() - if err: - self.send_msg(df_statuses, bot=bot) - else: + try: + df_statuses = self._rpc_status_table() message = tabulate(df_statuses, headers='keys', tablefmt='simple') - message = "
{}
".format(message) - - self.send_msg(message, parse_mode=ParseMode.HTML) + self._send_msg("
{}
".format(message), parse_mode=ParseMode.HTML) + except RPCException as e: + self._send_msg(str(e), bot=bot) @authorized_only def _daily(self, bot: Bot, update: Update) -> None: @@ -183,14 +170,12 @@ class Telegram(RPC): timescale = int(update.message.text.replace('/daily', '').strip()) except (TypeError, ValueError): timescale = 7 - (error, stats) = self.rpc_daily_profit( - timescale, - self._config['stake_currency'], - self._config['fiat_display_currency'] - ) - if error: - self.send_msg(stats, bot=bot) - else: + try: + stats = self._rpc_daily_profit( + timescale, + self._config['stake_currency'], + self._config['fiat_display_currency'] + ) stats = tabulate(stats, headers=[ 'Day', @@ -199,11 +184,10 @@ class Telegram(RPC): ], tablefmt='simple') message = 'Daily Profit over the last {} days:\n
{}
'\ - .format( - timescale, - stats - ) - self.send_msg(message, bot=bot, parse_mode=ParseMode.HTML) + .format(timescale, stats) + self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML) + except RPCException as e: + self._send_msg(str(e), bot=bot) @authorized_only def _profit(self, bot: Bot, update: Update) -> None: @@ -214,67 +198,63 @@ class Telegram(RPC): :param update: message update :return: None """ - (error, stats) = self.rpc_trade_statistics( - self._config['stake_currency'], - self._config['fiat_display_currency'] - ) - if error: - self.send_msg(stats, bot=bot) - return + try: + stats = self._rpc_trade_statistics( + self._config['stake_currency'], + self._config['fiat_display_currency']) - # Message to display - markdown_msg = "*ROI:* Close trades\n" \ - "∙ `{profit_closed_coin:.8f} {coin} ({profit_closed_percent:.2f}%)`\n" \ - "∙ `{profit_closed_fiat:.3f} {fiat}`\n" \ - "*ROI:* All trades\n" \ - "∙ `{profit_all_coin:.8f} {coin} ({profit_all_percent:.2f}%)`\n" \ - "∙ `{profit_all_fiat:.3f} {fiat}`\n" \ - "*Total Trade Count:* `{trade_count}`\n" \ - "*First Trade opened:* `{first_trade_date}`\n" \ - "*Latest Trade opened:* `{latest_trade_date}`\n" \ - "*Avg. Duration:* `{avg_duration}`\n" \ - "*Best Performing:* `{best_pair}: {best_rate:.2f}%`"\ - .format( - coin=self._config['stake_currency'], - fiat=self._config['fiat_display_currency'], - profit_closed_coin=stats['profit_closed_coin'], - profit_closed_percent=stats['profit_closed_percent'], - profit_closed_fiat=stats['profit_closed_fiat'], - profit_all_coin=stats['profit_all_coin'], - profit_all_percent=stats['profit_all_percent'], - profit_all_fiat=stats['profit_all_fiat'], - trade_count=stats['trade_count'], - first_trade_date=stats['first_trade_date'], - latest_trade_date=stats['latest_trade_date'], - avg_duration=stats['avg_duration'], - best_pair=stats['best_pair'], - best_rate=stats['best_rate'] - ) - self.send_msg(markdown_msg, bot=bot) + # Message to display + markdown_msg = "*ROI:* Close trades\n" \ + "∙ `{profit_closed_coin:.8f} {coin} ({profit_closed_percent:.2f}%)`\n" \ + "∙ `{profit_closed_fiat:.3f} {fiat}`\n" \ + "*ROI:* All trades\n" \ + "∙ `{profit_all_coin:.8f} {coin} ({profit_all_percent:.2f}%)`\n" \ + "∙ `{profit_all_fiat:.3f} {fiat}`\n" \ + "*Total Trade Count:* `{trade_count}`\n" \ + "*First Trade opened:* `{first_trade_date}`\n" \ + "*Latest Trade opened:* `{latest_trade_date}`\n" \ + "*Avg. Duration:* `{avg_duration}`\n" \ + "*Best Performing:* `{best_pair}: {best_rate:.2f}%`"\ + .format( + coin=self._config['stake_currency'], + fiat=self._config['fiat_display_currency'], + profit_closed_coin=stats['profit_closed_coin'], + profit_closed_percent=stats['profit_closed_percent'], + profit_closed_fiat=stats['profit_closed_fiat'], + profit_all_coin=stats['profit_all_coin'], + profit_all_percent=stats['profit_all_percent'], + profit_all_fiat=stats['profit_all_fiat'], + trade_count=stats['trade_count'], + first_trade_date=stats['first_trade_date'], + latest_trade_date=stats['latest_trade_date'], + avg_duration=stats['avg_duration'], + best_pair=stats['best_pair'], + best_rate=stats['best_rate'] + ) + self._send_msg(markdown_msg, bot=bot) + except RPCException as e: + self._send_msg(str(e), bot=bot) @authorized_only def _balance(self, bot: Bot, update: Update) -> None: - """ - Handler for /balance - """ - (error, result) = self.rpc_balance(self._config['fiat_display_currency']) - if error: - self.send_msg('`All balances are zero.`') - return + """ Handler for /balance """ + try: + currencys, total, symbol, value = \ + self._rpc_balance(self._config['fiat_display_currency']) + output = '' + for currency in currencys: + output += "*{currency}:*\n" \ + "\t`Available: {available: .8f}`\n" \ + "\t`Balance: {balance: .8f}`\n" \ + "\t`Pending: {pending: .8f}`\n" \ + "\t`Est. BTC: {est_btc: .8f}`\n".format(**currency) - (currencys, total, symbol, value) = result - output = '' - for currency in currencys: - output += "*{currency}:*\n" \ - "\t`Available: {available: .8f}`\n" \ - "\t`Balance: {balance: .8f}`\n" \ - "\t`Pending: {pending: .8f}`\n" \ - "\t`Est. BTC: {est_btc: .8f}`\n".format(**currency) - - output += "\n*Estimated Value*:\n" \ - "\t`BTC: {0: .8f}`\n" \ - "\t`{1}: {2: .2f}`\n".format(total, symbol, value) - self.send_msg(output) + output += "\n*Estimated Value*:\n" \ + "\t`BTC: {0: .8f}`\n" \ + "\t`{1}: {2: .2f}`\n".format(total, symbol, value) + self._send_msg(output, bot=bot) + except RPCException as e: + self._send_msg(str(e), bot=bot) @authorized_only def _start(self, bot: Bot, update: Update) -> None: @@ -285,9 +265,8 @@ class Telegram(RPC): :param update: message update :return: None """ - (error, msg) = self.rpc_start() - if error: - self.send_msg(msg, bot=bot) + msg = self._rpc_start() + self._send_msg(msg, bot=bot) @authorized_only def _stop(self, bot: Bot, update: Update) -> None: @@ -298,8 +277,8 @@ class Telegram(RPC): :param update: message update :return: None """ - (error, msg) = self.rpc_stop() - self.send_msg(msg, bot=bot) + msg = self._rpc_stop() + self._send_msg(msg, bot=bot) @authorized_only def _reload_conf(self, bot: Bot, update: Update) -> None: @@ -310,8 +289,8 @@ class Telegram(RPC): :param update: message update :return: None """ - msg = self.rpc_reload_conf() - self.send_msg(msg, bot=bot) + msg = self._rpc_reload_conf() + self._send_msg(msg, bot=bot) @authorized_only def _forcesell(self, bot: Bot, update: Update) -> None: @@ -324,10 +303,10 @@ class Telegram(RPC): """ trade_id = update.message.text.replace('/forcesell', '').strip() - (error, message) = self.rpc_forcesell(trade_id) - if error: - self.send_msg(message, bot=bot) - return + try: + self._rpc_forcesell(trade_id) + except RPCException as e: + self._send_msg(str(e), bot=bot) @authorized_only def _performance(self, bot: Bot, update: Update) -> None: @@ -338,19 +317,18 @@ class Telegram(RPC): :param update: message update :return: None """ - (error, trades) = self.rpc_performance() - if error: - self.send_msg(trades, bot=bot) - return - - stats = '\n'.join('{index}.\t{pair}\t{profit:.2f}% ({count})'.format( - index=i + 1, - pair=trade['pair'], - profit=trade['profit'], - count=trade['count'] - ) for i, trade in enumerate(trades)) - message = 'Performance:\n{}'.format(stats) - self.send_msg(message, parse_mode=ParseMode.HTML) + try: + trades = self._rpc_performance() + stats = '\n'.join('{index}.\t{pair}\t{profit:.2f}% ({count})'.format( + index=i + 1, + pair=trade['pair'], + profit=trade['profit'], + count=trade['count'] + ) for i, trade in enumerate(trades)) + message = 'Performance:\n{}'.format(stats) + self._send_msg(message, parse_mode=ParseMode.HTML) + except RPCException as e: + self._send_msg(str(e), bot=bot) @authorized_only def _count(self, bot: Bot, update: Update) -> None: @@ -361,19 +339,18 @@ class Telegram(RPC): :param update: message update :return: None """ - (error, trades) = self.rpc_count() - if error: - self.send_msg(trades, bot=bot) - return - - message = tabulate({ - 'current': [len(trades)], - 'max': [self._config['max_open_trades']], - 'total stake': [sum((trade.open_rate * trade.amount) for trade in trades)] - }, headers=['current', 'max', 'total stake'], tablefmt='simple') - message = "
{}
".format(message) - logger.debug(message) - self.send_msg(message, parse_mode=ParseMode.HTML) + try: + trades = self._rpc_count() + message = tabulate({ + 'current': [len(trades)], + 'max': [self._config['max_open_trades']], + 'total stake': [sum((trade.open_rate * trade.amount) for trade in trades)] + }, headers=['current', 'max', 'total stake'], 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), bot=bot) @authorized_only def _help(self, bot: Bot, update: Update) -> None: @@ -399,7 +376,7 @@ class Telegram(RPC): "*/help:* `This help message`\n" \ "*/version:* `Show version`" - self.send_msg(message, bot=bot) + self._send_msg(message, bot=bot) @authorized_only def _version(self, bot: Bot, update: Update) -> None: @@ -410,10 +387,10 @@ class Telegram(RPC): :param update: message update :return: None """ - self.send_msg('*Version:* `{}`'.format(__version__), bot=bot) + self._send_msg('*Version:* `{}`'.format(__version__), bot=bot) - def send_msg(self, msg: str, bot: Bot = None, - parse_mode: ParseMode = ParseMode.MARKDOWN) -> None: + def _send_msg(self, msg: str, bot: Bot = None, + parse_mode: ParseMode = ParseMode.MARKDOWN) -> None: """ Send given markdown message :param msg: message @@ -421,9 +398,6 @@ class Telegram(RPC): :param parse_mode: telegram parse mode :return: None """ - if not self.is_enabled(): - return - bot = bot or self._updater.bot keyboard = [['/daily', '/profit', '/balance'], diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 5cdd22c7a..b49b7fdcb 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -7,9 +7,11 @@ Unit test file for rpc/rpc.py from datetime import datetime from unittest.mock import MagicMock +import pytest + from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade -from freqtrade.rpc.rpc import RPC +from freqtrade.rpc.rpc import RPC, RPCException from freqtrade.state import State from freqtrade.tests.test_freqtradebot import patch_get_signal, patch_coinmarketcap @@ -29,7 +31,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: """ patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), @@ -41,19 +43,16 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: rpc = RPC(freqtradebot) freqtradebot.state = State.STOPPED - (error, result) = rpc.rpc_trade_status() - assert error - assert 'trader is not running' in result + with pytest.raises(RPCException, match=r'.*trader is not running*'): + rpc._rpc_trade_status() freqtradebot.state = State.RUNNING - (error, result) = rpc.rpc_trade_status() - assert error - assert 'no active trade' in result + with pytest.raises(RPCException, match=r'.*no active trade*'): + rpc._rpc_trade_status() freqtradebot.create_trade() - (error, result) = rpc.rpc_trade_status() - assert not error - trade = result[0] + trades = rpc._rpc_trade_status() + trade = trades[0] result_message = [ '*Trade ID:* `1`\n' @@ -68,7 +67,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: '*Current Profit:* `-0.59%`\n' '*Open Order:* `(limit buy rem=0.00000000)`' ] - assert result == result_message + assert trades == result_message assert trade.find('[ETH/BTC]') >= 0 @@ -78,7 +77,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: """ patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), @@ -90,17 +89,15 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: rpc = RPC(freqtradebot) freqtradebot.state = State.STOPPED - (error, result) = rpc.rpc_status_table() - assert error - assert '*Status:* `trader is not running`' in result + with pytest.raises(RPCException, match=r'.*\*Status:\* `trader is not running``*'): + rpc._rpc_status_table() freqtradebot.state = State.RUNNING - (error, result) = rpc.rpc_status_table() - assert error - assert '*Status:* `no active order`' in result + with pytest.raises(RPCException, match=r'.*\*Status:\* `no active order`*'): + rpc._rpc_status_table() freqtradebot.create_trade() - (error, result) = rpc.rpc_status_table() + result = rpc._rpc_status_table() assert 'just now' in result['Since'].all() assert 'ETH/BTC' in result['Pair'].all() assert '-0.59%' in result['Profit'].all() @@ -113,7 +110,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, """ patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) - mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), @@ -140,8 +137,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, # Try valid data update.message.text = '/daily 2' - (error, days) = rpc.rpc_daily_profit(7, stake_currency, fiat_display_currency) - assert not error + days = rpc._rpc_daily_profit(7, stake_currency, fiat_display_currency) assert len(days) == 7 for day in days: # [datetime.date(2018, 1, 11), '0.00000000 BTC', '0.000 USD'] @@ -154,9 +150,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, assert str(days[0][0]) == str(datetime.utcnow().date()) # Try invalid data - (error, days) = rpc.rpc_daily_profit(0, stake_currency, fiat_display_currency) - assert error - assert days.find('must be an integer greater than 0') >= 0 + with pytest.raises(RPCException, match=r'.*must be an integer greater than 0*'): + rpc._rpc_daily_profit(0, stake_currency, fiat_display_currency) def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, @@ -170,7 +165,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, ticker=MagicMock(return_value={'price_usd': 15000.0}), ) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) - mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), @@ -184,9 +179,8 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, rpc = RPC(freqtradebot) - (error, stats) = rpc.rpc_trade_statistics(stake_currency, fiat_display_currency) - assert error - assert stats.find('no closed trade') >= 0 + with pytest.raises(RPCException, match=r'.*no closed trade*'): + rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) # Create some test data freqtradebot.create_trade() @@ -219,8 +213,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, trade.close_date = datetime.utcnow() trade.is_open = False - (error, stats) = rpc.rpc_trade_statistics(stake_currency, fiat_display_currency) - assert not error + stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) assert prec_satoshi(stats['profit_closed_coin'], 6.217e-05) assert prec_satoshi(stats['profit_closed_percent'], 6.2) assert prec_satoshi(stats['profit_closed_fiat'], 0.93255) @@ -248,7 +241,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, ticker=MagicMock(return_value={'price_usd': 15000.0}), ) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) - mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), @@ -281,8 +274,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, for trade in Trade.query.order_by(Trade.id).all(): trade.open_rate = None - (error, stats) = rpc.rpc_trade_statistics(stake_currency, fiat_display_currency) - assert not error + stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) assert prec_satoshi(stats['profit_closed_coin'], 0) assert prec_satoshi(stats['profit_closed_percent'], 0) assert prec_satoshi(stats['profit_closed_fiat'], 0) @@ -320,7 +312,7 @@ def test_rpc_balance_handle(default_conf, mocker): ticker=MagicMock(return_value={'price_usd': 15000.0}), ) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) - mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), @@ -330,18 +322,16 @@ def test_rpc_balance_handle(default_conf, mocker): freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) - (error, res) = rpc.rpc_balance(default_conf['fiat_display_currency']) - assert not error - (trade, x, y, z) = res - assert prec_satoshi(x, 12) - assert prec_satoshi(z, 180000) - assert 'USD' in y - assert len(trade) == 1 - assert 'BTC' in trade[0]['currency'] - assert prec_satoshi(trade[0]['available'], 10) - assert prec_satoshi(trade[0]['balance'], 12) - assert prec_satoshi(trade[0]['pending'], 2) - assert prec_satoshi(trade[0]['est_btc'], 12) + output, total, symbol, value = rpc._rpc_balance(default_conf['fiat_display_currency']) + assert prec_satoshi(total, 12) + assert prec_satoshi(value, 180000) + assert 'USD' in symbol + assert len(output) == 1 + assert 'BTC' in output[0]['currency'] + assert prec_satoshi(output[0]['available'], 10) + assert prec_satoshi(output[0]['balance'], 12) + assert prec_satoshi(output[0]['pending'], 2) + assert prec_satoshi(output[0]['est_btc'], 12) def test_rpc_start(mocker, default_conf) -> None: @@ -350,7 +340,7 @@ def test_rpc_start(mocker, default_conf) -> None: """ patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), @@ -361,13 +351,11 @@ def test_rpc_start(mocker, default_conf) -> None: rpc = RPC(freqtradebot) freqtradebot.state = State.STOPPED - (error, result) = rpc.rpc_start() - assert not error + result = rpc._rpc_start() assert '`Starting trader ...`' in result assert freqtradebot.state == State.RUNNING - (error, result) = rpc.rpc_start() - assert error + result = rpc._rpc_start() assert '*Status:* `already running`' in result assert freqtradebot.state == State.RUNNING @@ -378,7 +366,7 @@ def test_rpc_stop(mocker, default_conf) -> None: """ patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), @@ -389,13 +377,11 @@ def test_rpc_stop(mocker, default_conf) -> None: rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING - (error, result) = rpc.rpc_stop() - assert not error + result = rpc._rpc_stop() assert '`Stopping trader ...`' in result assert freqtradebot.state == State.STOPPED - (error, result) = rpc.rpc_stop() - assert error + result = rpc._rpc_stop() assert '*Status:* `already stopped`' in result assert freqtradebot.state == State.STOPPED @@ -406,7 +392,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: """ patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) cancel_order_mock = MagicMock() mocker.patch.multiple( @@ -428,36 +414,26 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: rpc = RPC(freqtradebot) freqtradebot.state = State.STOPPED - (error, res) = rpc.rpc_forcesell(None) - assert error - assert res == '`trader is not running`' + with pytest.raises(RPCException, match=r'.*`trader is not running`*'): + rpc._rpc_forcesell(None) freqtradebot.state = State.RUNNING - (error, res) = rpc.rpc_forcesell(None) - assert error - assert res == 'Invalid argument.' + with pytest.raises(RPCException, match=r'.*Invalid argument.*'): + rpc._rpc_forcesell(None) - (error, res) = rpc.rpc_forcesell('all') - assert not error - assert res == '' + rpc._rpc_forcesell('all') freqtradebot.create_trade() - (error, res) = rpc.rpc_forcesell('all') - assert not error - assert res == '' + rpc._rpc_forcesell('all') - (error, res) = rpc.rpc_forcesell('1') - assert not error - assert res == '' + rpc._rpc_forcesell('1') freqtradebot.state = State.STOPPED - (error, res) = rpc.rpc_forcesell(None) - assert error - assert res == '`trader is not running`' + with pytest.raises(RPCException, match=r'.*`trader is not running`*'): + rpc._rpc_forcesell(None) - (error, res) = rpc.rpc_forcesell('all') - assert error - assert res == '`trader is not running`' + with pytest.raises(RPCException, match=r'.*`trader is not running`*'): + rpc._rpc_forcesell('all') freqtradebot.state = State.RUNNING assert cancel_order_mock.call_count == 0 @@ -475,9 +451,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: ) # check that the trade is called, which is done by ensuring exchange.cancel_order is called # and trade amount is updated - (error, res) = rpc.rpc_forcesell('1') - assert not error - assert res == '' + rpc._rpc_forcesell('1') assert cancel_order_mock.call_count == 1 assert trade.amount == filled_amount @@ -495,9 +469,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: } ) # check that the trade is called, which is done by ensuring exchange.cancel_order is called - (error, res) = rpc.rpc_forcesell('2') - assert not error - assert res == '' + rpc._rpc_forcesell('2') assert cancel_order_mock.call_count == 2 assert trade.amount == amount @@ -511,9 +483,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: 'side': 'sell' } ) - (error, res) = rpc.rpc_forcesell('3') - assert not error - assert res == '' + rpc._rpc_forcesell('3') # status quo, no exchange calls assert cancel_order_mock.call_count == 2 @@ -525,7 +495,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, """ patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), @@ -550,8 +520,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, trade.close_date = datetime.utcnow() trade.is_open = False - (error, res) = rpc.rpc_performance() - assert not error + res = rpc._rpc_performance() assert len(res) == 1 assert res[0]['pair'] == 'ETH/BTC' assert res[0]['count'] == 1 @@ -564,7 +533,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None: """ patch_get_signal(mocker, (True, False)) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), @@ -576,14 +545,12 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None: freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) - (error, trades) = rpc.rpc_count() + trades = rpc._rpc_count() nb_trades = len(trades) - assert not error assert nb_trades == 0 # Create some test data freqtradebot.create_trade() - (error, trades) = rpc.rpc_count() + trades = rpc._rpc_count() nb_trades = len(trades) - assert not error assert nb_trades == 1 diff --git a/freqtrade/tests/rpc/test_rpc_manager.py b/freqtrade/tests/rpc/test_rpc_manager.py index 1d56dea3a..805424d26 100644 --- a/freqtrade/tests/rpc/test_rpc_manager.py +++ b/freqtrade/tests/rpc/test_rpc_manager.py @@ -7,49 +7,35 @@ from copy import deepcopy from unittest.mock import MagicMock from freqtrade.rpc.rpc_manager import RPCManager -from freqtrade.rpc.telegram import Telegram from freqtrade.tests.conftest import log_has, get_patched_freqtradebot def test_rpc_manager_object() -> None: - """ - Test the Arguments object has the mandatory methods - :return: None - """ - assert hasattr(RPCManager, '_init') + """ Test the Arguments object has the mandatory methods """ assert hasattr(RPCManager, 'send_msg') assert hasattr(RPCManager, 'cleanup') def test__init__(mocker, default_conf) -> None: - """ - Test __init__() method - """ - init_mock = mocker.patch('freqtrade.rpc.rpc_manager.RPCManager._init', MagicMock()) - freqtradebot = get_patched_freqtradebot(mocker, default_conf) + """ Test __init__() method """ + conf = deepcopy(default_conf) + conf['telegram']['enabled'] = False - rpc_manager = RPCManager(freqtradebot) - assert rpc_manager.freqtrade == freqtradebot + rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf)) assert rpc_manager.registered_modules == [] - assert rpc_manager.telegram is None - assert init_mock.call_count == 1 def test_init_telegram_disabled(mocker, default_conf, caplog) -> None: - """ - Test _init() method with Telegram disabled - """ + """ Test _init() method with Telegram disabled """ caplog.set_level(logging.DEBUG) conf = deepcopy(default_conf) conf['telegram']['enabled'] = False - freqtradebot = get_patched_freqtradebot(mocker, conf) - rpc_manager = RPCManager(freqtradebot) + rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf)) assert not log_has('Enabling rpc.telegram ...', caplog.record_tuples) assert rpc_manager.registered_modules == [] - assert rpc_manager.telegram is None def test_init_telegram_enabled(mocker, default_conf, caplog) -> None: @@ -59,14 +45,12 @@ def test_init_telegram_enabled(mocker, default_conf, caplog) -> None: caplog.set_level(logging.DEBUG) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) - freqtradebot = get_patched_freqtradebot(mocker, default_conf) - rpc_manager = RPCManager(freqtradebot) + rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) assert log_has('Enabling rpc.telegram ...', caplog.record_tuples) len_modules = len(rpc_manager.registered_modules) assert len_modules == 1 - assert 'telegram' in rpc_manager.registered_modules - assert isinstance(rpc_manager.telegram, Telegram) + assert 'telegram' in [mod.name for mod in rpc_manager.registered_modules] def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None: @@ -99,11 +83,11 @@ def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None: rpc_manager = RPCManager(freqtradebot) # Check we have Telegram as a registered modules - assert 'telegram' in rpc_manager.registered_modules + assert 'telegram' in [mod.name for mod in rpc_manager.registered_modules] rpc_manager.cleanup() assert log_has('Cleaning up rpc.telegram ...', caplog.record_tuples) - assert 'telegram' not in rpc_manager.registered_modules + assert 'telegram' not in [mod.name for mod in rpc_manager.registered_modules] assert telegram_mock.call_count == 1 @@ -120,7 +104,7 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None: rpc_manager = RPCManager(freqtradebot) rpc_manager.send_msg('test') - assert log_has('test', caplog.record_tuples) + assert log_has('Sending rpc message: test', caplog.record_tuples) assert telegram_mock.call_count == 0 @@ -135,5 +119,5 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None: rpc_manager = RPCManager(freqtradebot) rpc_manager.send_msg('test') - assert log_has('test', caplog.record_tuples) + assert log_has('Sending rpc message: test', caplog.record_tuples) assert telegram_mock.call_count == 1 diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 0919455ad..f022c09e4 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -32,6 +32,9 @@ class DummyCls(Telegram): super().__init__(freqtrade) self.state = {'called': False} + def _init(self): + pass + @authorized_only def dummy_handler(self, *args, **kwargs) -> None: """ @@ -60,9 +63,7 @@ def test__init__(default_conf, mocker) -> None: def test_init(default_conf, mocker, caplog) -> None: - """ - Test _init() method - """ + """ Test _init() method """ start_polling = MagicMock() mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock(return_value=start_polling)) @@ -80,21 +81,6 @@ def test_init(default_conf, mocker, caplog) -> None: assert log_has(message_str, caplog.record_tuples) -def test_init_disabled(default_conf, mocker, caplog) -> None: - """ - Test _init() method when Telegram is disabled - """ - conf = deepcopy(default_conf) - conf['telegram']['enabled'] = False - Telegram(get_patched_freqtradebot(mocker, conf)) - - message_str = "rpc.telegram is listening for following commands: [['status'], ['profit'], " \ - "['balance'], ['start'], ['stop'], ['forcesell'], ['performance'], ['daily'], " \ - "['count'], ['help'], ['version']]" - - assert not log_has(message_str, caplog.record_tuples) - - def test_cleanup(default_conf, mocker) -> None: """ Test cleanup() method @@ -103,44 +89,11 @@ def test_cleanup(default_conf, mocker) -> None: updater_mock.stop = MagicMock() mocker.patch('freqtrade.rpc.telegram.Updater', updater_mock) - # not enabled - conf = deepcopy(default_conf) - conf['telegram']['enabled'] = False - telegram = Telegram(get_patched_freqtradebot(mocker, conf)) - telegram.cleanup() - assert telegram._updater is None - assert updater_mock.call_count == 0 - assert not hasattr(telegram._updater, 'stop') - assert updater_mock.stop.call_count == 0 - - # enabled - conf['telegram']['enabled'] = True - telegram = Telegram(get_patched_freqtradebot(mocker, conf)) + telegram = Telegram(get_patched_freqtradebot(mocker, default_conf)) telegram.cleanup() assert telegram._updater.stop.call_count == 1 -def test_is_enabled(default_conf, mocker) -> None: - """ - Test is_enabled() method - """ - mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) - - telegram = Telegram(get_patched_freqtradebot(mocker, default_conf)) - assert telegram.is_enabled() - - -def test_is_not_enabled(default_conf, mocker) -> None: - """ - Test is_enabled() method - """ - conf = deepcopy(default_conf) - conf['telegram']['enabled'] = False - telegram = Telegram(get_patched_freqtradebot(mocker, conf)) - - assert not telegram.is_enabled() - - def test_authorized_only(default_conf, mocker, caplog) -> None: """ Test authorized_only() method when we are authorized @@ -256,9 +209,9 @@ def test_status(default_conf, update, mocker, fee, ticker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - rpc_trade_status=MagicMock(return_value=(False, [1, 2, 3])), + _rpc_trade_status=MagicMock(return_value=[1, 2, 3]), _status_table=status_table, - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) @@ -296,7 +249,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), _status_table=status_table, - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) @@ -341,7 +294,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) @@ -397,7 +350,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) @@ -465,7 +418,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) @@ -506,7 +459,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) @@ -604,7 +557,7 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) freqtradebot = FreqtradeBot(default_conf) @@ -634,7 +587,7 @@ def test_zero_balance_handle(default_conf, update, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) freqtradebot = FreqtradeBot(default_conf) @@ -656,7 +609,7 @@ def test_start_handle(default_conf, update, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) @@ -667,7 +620,7 @@ def test_start_handle(default_conf, update, mocker) -> None: assert freqtradebot.state == State.STOPPED telegram._start(bot=MagicMock(), update=update) assert freqtradebot.state == State.RUNNING - assert msg_mock.call_count == 0 + assert msg_mock.call_count == 1 def test_start_handle_already_running(default_conf, update, mocker) -> None: @@ -680,7 +633,7 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) @@ -705,7 +658,7 @@ def test_stop_handle(default_conf, update, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) @@ -730,7 +683,7 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) @@ -753,7 +706,7 @@ def test_reload_conf_handle(default_conf, update, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) @@ -898,7 +851,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) @@ -940,7 +893,7 @@ def test_performance_handle(default_conf, update, ticker, fee, mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', @@ -981,7 +934,7 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) freqtradebot = FreqtradeBot(default_conf) @@ -1004,7 +957,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', @@ -1047,7 +1000,7 @@ def test_help_handle(default_conf, update, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) freqtradebot = FreqtradeBot(default_conf) telegram = Telegram(freqtradebot) @@ -1067,7 +1020,7 @@ def test_version_handle(default_conf, update, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - send_msg=msg_mock + _send_msg=msg_mock ) freqtradebot = FreqtradeBot(default_conf) telegram = Telegram(freqtradebot) @@ -1089,13 +1042,8 @@ def test_send_msg(default_conf, mocker) -> None: freqtradebot = FreqtradeBot(conf) telegram = Telegram(freqtradebot) - telegram._config['telegram']['enabled'] = False - telegram.send_msg('test', bot) - assert not bot.method_calls - bot.reset_mock() - telegram._config['telegram']['enabled'] = True - telegram.send_msg('test', bot) + telegram._send_msg('test', bot) assert len(bot.method_calls) == 1 @@ -1113,7 +1061,7 @@ def test_send_msg_network_error(default_conf, mocker, caplog) -> None: telegram = Telegram(freqtradebot) telegram._config['telegram']['enabled'] = True - telegram.send_msg('test', bot) + telegram._send_msg('test', bot) # Bot should've tried to send it twice assert len(bot.method_calls) == 2 diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 5339ebc24..1d272428e 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -57,7 +57,7 @@ def patch_RPCManager(mocker) -> MagicMock: :param mocker: mocker to patch RPCManager class :return: RPCManager.send_msg MagicMock to track if this method is called """ - mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) rpc_mock = mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock()) return rpc_mock