diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 5ac78f2cb..2270214ab 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -5,7 +5,7 @@ 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 @@ -20,6 +20,10 @@ from freqtrade.state import State logger = logging.getLogger(__name__) +class RPCException(Exception): + pass + + class RPC(object): """ RPC class can be used to have extra feature, like bot data, and access to DB data @@ -33,30 +37,29 @@ class RPC(object): self.freqtrade = freqtrade @abstractmethod - def cleanup(self) -> str: + def cleanup(self) -> None: """ Cleanup pending module resources """ @property @abstractmethod - def name(self) -> None: + 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) -> Tuple[bool, Any]: + 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`' + 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: @@ -95,14 +98,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`' + 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: @@ -118,20 +121,16 @@ 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 for day in range(0, timescale): @@ -148,7 +147,7 @@ class RPC(object): 'trades': len(trades) } - stats = [ + return [ [ key, '{value:.8f} {symbol}'.format( @@ -170,13 +169,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 = [] @@ -214,7 +210,7 @@ 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 @@ -237,35 +233,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: @@ -285,32 +275,28 @@ class RPC(object): } ) if total == 0.0: - return True, '`All balances are zero.`' + raise RPCException('`All balances are zero.`') 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. - """ + def _rpc_start(self) -> str: + """ Handler for start """ if self.freqtrade.state == State.RUNNING: - return True, '*Status:* `already running`' + return '*Status:* `already running`' self.freqtrade.state = State.RUNNING - return False, '`Starting trader ...`' + return '`Starting trader ...`' - def rpc_stop(self) -> Tuple[bool, str]: - """ - Handler for stop. - """ + def _rpc_stop(self) -> str: + """ Handler for stop """ if self.freqtrade.state == State.RUNNING: self.freqtrade.state = State.STOPPED - return False, '`Stopping trader ...`' + return '`Stopping trader ...`' - return True, '*Status:* `already stopped`' + return '*Status:* `already stopped`' def rpc_reload_conf(self) -> str: """ Handler for reload_conf. """ @@ -318,11 +304,10 @@ class RPC(object): 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 @@ -352,13 +337,13 @@ class RPC(object): # ---- EOF def _exec_forcesell ---- if self.freqtrade.state != State.RUNNING: - return True, '`trader is not 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( @@ -369,19 +354,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`' + raise RPCException('`trader is not running`') pair_rates = Trade.session.query(Trade.pair, sql.func.sum(Trade.close_profit).label('profit_sum'), @@ -390,19 +374,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) -> Tuple[bool, Any]: - """ - Returns the number of trades running - :return: None - """ + def _rpc_count(self) -> List[Trade]: + """ Returns the number of trades running """ if self.freqtrade.state != State.RUNNING: - return True, '`trader is not running`' + raise RPCException('`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/telegram.py b/freqtrade/rpc/telegram.py index c00ba6a8e..f3cb65edc 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -12,7 +12,7 @@ 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__) @@ -151,13 +151,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: @@ -168,15 +166,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: @@ -191,14 +186,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', @@ -207,11 +200,10 @@ class Telegram(RPC): ], tablefmt='simple') message = 'Daily Profit over the last {} days:\n
{}
'\ - .format( - timescale, - stats - ) + .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: @@ -222,67 +214,65 @@ 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 + 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: @@ -293,9 +283,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: @@ -306,7 +295,7 @@ class Telegram(RPC): :param update: message update :return: None """ - (error, msg) = self.rpc_stop() + msg = self._rpc_stop() self._send_msg(msg, bot=bot) @authorized_only @@ -332,10 +321,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: @@ -346,19 +335,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: @@ -369,19 +357,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: diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 5cdd22c7a..5d7b56bc5 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 @@ -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 @@ -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() @@ -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, @@ -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) @@ -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) @@ -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: @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 6c073a251..805424d26 100644 --- a/freqtrade/tests/rpc/test_rpc_manager.py +++ b/freqtrade/tests/rpc/test_rpc_manager.py @@ -104,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 @@ -119,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 47ccf4243..87e884110 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -60,9 +60,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)) @@ -256,7 +254,7 @@ 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 ) @@ -667,7 +665,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: