rpc: remove tuple return madness

This commit is contained in:
gcarq 2018-06-08 04:52:50 +02:00
parent cddb062db5
commit 4048859912
5 changed files with 223 additions and 292 deletions

View File

@ -5,7 +5,7 @@ import logging
from abc import abstractmethod from abc import abstractmethod
from datetime import datetime, timedelta, date from datetime import datetime, timedelta, date
from decimal import Decimal from decimal import Decimal
from typing import Dict, Tuple, Any from typing import Dict, Tuple, Any, List
import arrow import arrow
import sqlalchemy as sql import sqlalchemy as sql
@ -20,6 +20,10 @@ from freqtrade.state import State
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class RPCException(Exception):
pass
class RPC(object): class RPC(object):
""" """
RPC class can be used to have extra feature, like bot data, and access to DB data 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 self.freqtrade = freqtrade
@abstractmethod @abstractmethod
def cleanup(self) -> str: def cleanup(self) -> None:
""" Cleanup pending module resources """ """ Cleanup pending module resources """
@property @property
@abstractmethod @abstractmethod
def name(self) -> None: def name(self) -> str:
""" Returns the lowercase name of this module """ """ Returns the lowercase name of this module """
@abstractmethod @abstractmethod
def send_msg(self, msg: str) -> None: def send_msg(self, msg: str) -> None:
""" Sends a message to all registered rpc modules """ """ 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 Below follows the RPC backend it is prefixed with rpc_ to raise awareness that it is
a remotely exposed function a remotely exposed function
:return:
""" """
# Fetch open trade # Fetch open trade
trades = Trade.query.filter(Trade.is_open.is_(True)).all() trades = Trade.query.filter(Trade.is_open.is_(True)).all()
if self.freqtrade.state != State.RUNNING: if self.freqtrade.state != State.RUNNING:
return True, '*Status:* `trader is not running`' raise RPCException('*Status:* `trader is not running`')
elif not trades: elif not trades:
return True, '*Status:* `no active trade`' raise RPCException('*Status:* `no active trade`')
else: else:
result = [] result = []
for trade in trades: for trade in trades:
@ -95,14 +98,14 @@ class RPC(object):
) if order else None, ) if order else None,
) )
result.append(message) 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() trades = Trade.query.filter(Trade.is_open.is_(True)).all()
if self.freqtrade.state != State.RUNNING: if self.freqtrade.state != State.RUNNING:
return True, '*Status:* `trader is not running`' raise RPCException('*Status:* `trader is not running`')
elif not trades: elif not trades:
return True, '*Status:* `no active order`' raise RPCException('*Status:* `no active order`')
else: else:
trades_list = [] trades_list = []
for trade in trades: for trade in trades:
@ -118,20 +121,16 @@ class RPC(object):
columns = ['ID', 'Pair', 'Since', 'Profit'] columns = ['ID', 'Pair', 'Since', 'Profit']
df_statuses = DataFrame.from_records(trades_list, columns=columns) df_statuses = DataFrame.from_records(trades_list, columns=columns)
df_statuses = df_statuses.set_index(columns[0]) df_statuses = df_statuses.set_index(columns[0])
# The style used throughout is to return a tuple return df_statuses
# consisting of (error_occured?, result)
# Another approach would be to just return the
# result, or raise error
return False, df_statuses
def rpc_daily_profit( def _rpc_daily_profit(
self, timescale: int, 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() today = datetime.utcnow().date()
profit_days: Dict[date, Dict] = {} profit_days: Dict[date, Dict] = {}
if not (isinstance(timescale, int) and timescale > 0): 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): for day in range(0, timescale):
@ -148,7 +147,7 @@ class RPC(object):
'trades': len(trades) 'trades': len(trades)
} }
stats = [ return [
[ [
key, key,
'{value:.8f} {symbol}'.format( '{value:.8f} {symbol}'.format(
@ -170,13 +169,10 @@ class RPC(object):
] ]
for key, value in profit_days.items() for key, value in profit_days.items()
] ]
return False, stats
def rpc_trade_statistics( def _rpc_trade_statistics(
self, stake_currency: str, fiat_display_currency: str) -> Tuple[bool, Any]: self, stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
""" """ Returns cumulative profit statistics """
:return: cumulative profit statistics.
"""
trades = Trade.query.order_by(Trade.id).all() trades = Trade.query.order_by(Trade.id).all()
profit_all_coin = [] profit_all_coin = []
@ -214,7 +210,7 @@ class RPC(object):
.order_by(sql.text('profit_sum DESC')).first() .order_by(sql.text('profit_sum DESC')).first()
if not best_pair: if not best_pair:
return True, '*Status:* `no closed trade`' raise RPCException('*Status:* `no closed trade`')
bp_pair, bp_rate = best_pair bp_pair, bp_rate = best_pair
@ -237,35 +233,29 @@ class RPC(object):
fiat_display_currency fiat_display_currency
) )
num = float(len(durations) or 1) num = float(len(durations) or 1)
return ( return {
False, 'profit_closed_coin': profit_closed_coin,
{ 'profit_closed_percent': profit_closed_percent,
'profit_closed_coin': profit_closed_coin, 'profit_closed_fiat': profit_closed_fiat,
'profit_closed_percent': profit_closed_percent, 'profit_all_coin': profit_all_coin,
'profit_closed_fiat': profit_closed_fiat, 'profit_all_percent': profit_all_percent,
'profit_all_coin': profit_all_coin, 'profit_all_fiat': profit_all_fiat,
'profit_all_percent': profit_all_percent, 'trade_count': len(trades),
'profit_all_fiat': profit_all_fiat, 'first_trade_date': arrow.get(trades[0].open_date).humanize(),
'trade_count': len(trades), 'latest_trade_date': arrow.get(trades[-1].open_date).humanize(),
'first_trade_date': arrow.get(trades[0].open_date).humanize(), 'avg_duration': str(timedelta(seconds=sum(durations) / num)).split('.')[0],
'latest_trade_date': arrow.get(trades[-1].open_date).humanize(), 'best_pair': bp_pair,
'avg_duration': str(timedelta(seconds=sum(durations) / num)).split('.')[0], 'best_rate': round(bp_rate * 100, 2),
'best_pair': bp_pair, }
'best_rate': round(bp_rate * 100, 2)
}
)
def rpc_balance(self, fiat_display_currency: str) -> Tuple[bool, Any]: def _rpc_balance(self, fiat_display_currency: str) -> Tuple[List[Dict], float, str, float]:
""" """ Returns current account balance per crypto """
:return: current account balance per crypto
"""
output = [] output = []
total = 0.0 total = 0.0
for coin, balance in exchange.get_balances().items(): for coin, balance in exchange.get_balances().items():
if not balance['total']: if not balance['total']:
continue continue
rate = None
if coin == 'BTC': if coin == 'BTC':
rate = 1.0 rate = 1.0
else: else:
@ -285,32 +275,28 @@ class RPC(object):
} }
) )
if total == 0.0: 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 symbol = fiat_display_currency
value = fiat.convert_amount(total, 'BTC', symbol) 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]: def _rpc_start(self) -> str:
""" """ Handler for start """
Handler for start.
"""
if self.freqtrade.state == State.RUNNING: if self.freqtrade.state == State.RUNNING:
return True, '*Status:* `already running`' return '*Status:* `already running`'
self.freqtrade.state = State.RUNNING self.freqtrade.state = State.RUNNING
return False, '`Starting trader ...`' return '`Starting trader ...`'
def rpc_stop(self) -> Tuple[bool, str]: def _rpc_stop(self) -> str:
""" """ Handler for stop """
Handler for stop.
"""
if self.freqtrade.state == State.RUNNING: if self.freqtrade.state == State.RUNNING:
self.freqtrade.state = State.STOPPED 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: def rpc_reload_conf(self) -> str:
""" Handler for reload_conf. """ """ Handler for reload_conf. """
@ -318,11 +304,10 @@ class RPC(object):
return '*Status:* `Reloading config ...`' return '*Status:* `Reloading config ...`'
# FIX: no test for this!!!! # FIX: no test for this!!!!
def rpc_forcesell(self, trade_id) -> Tuple[bool, Any]: def _rpc_forcesell(self, trade_id) -> None:
""" """
Handler for forcesell <id>. Handler for forcesell <id>.
Sells the given trade at current price Sells the given trade at current price
:return: error or None
""" """
def _exec_forcesell(trade: Trade) -> None: def _exec_forcesell(trade: Trade) -> None:
# Check if there is there is an open order # Check if there is there is an open order
@ -352,13 +337,13 @@ class RPC(object):
# ---- EOF def _exec_forcesell ---- # ---- EOF def _exec_forcesell ----
if self.freqtrade.state != State.RUNNING: if self.freqtrade.state != State.RUNNING:
return True, '`trader is not running`' raise RPCException('`trader is not running`')
if trade_id == 'all': if trade_id == 'all':
# Execute sell for all open orders # Execute sell for all open orders
for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
_exec_forcesell(trade) _exec_forcesell(trade)
return False, '' return
# Query for trade # Query for trade
trade = Trade.query.filter( trade = Trade.query.filter(
@ -369,19 +354,18 @@ class RPC(object):
).first() ).first()
if not trade: if not trade:
logger.warning('forcesell: Invalid argument received') logger.warning('forcesell: Invalid argument received')
return True, 'Invalid argument.' raise RPCException('Invalid argument.')
_exec_forcesell(trade) _exec_forcesell(trade)
Trade.session.flush() Trade.session.flush()
return False, ''
def rpc_performance(self) -> Tuple[bool, Any]: def _rpc_performance(self) -> List[Dict]:
""" """
Handler for performance. Handler for performance.
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
""" """
if self.freqtrade.state != State.RUNNING: 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, pair_rates = Trade.session.query(Trade.pair,
sql.func.sum(Trade.close_profit).label('profit_sum'), sql.func.sum(Trade.close_profit).label('profit_sum'),
@ -390,19 +374,14 @@ class RPC(object):
.group_by(Trade.pair) \ .group_by(Trade.pair) \
.order_by(sql.text('profit_sum DESC')) \ .order_by(sql.text('profit_sum DESC')) \
.all() .all()
trades = [] return [
for (pair, rate, count) in pair_rates: {'pair': pair, 'profit': round(rate * 100, 2), 'count': count}
trades.append({'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 """
def rpc_count(self) -> Tuple[bool, Any]:
"""
Returns the number of trades running
:return: None
"""
if self.freqtrade.state != State.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 Trade.query.filter(Trade.is_open.is_(True)).all()
return False, trades

View File

@ -12,7 +12,7 @@ from telegram.error import NetworkError, TelegramError
from telegram.ext import CommandHandler, Updater from telegram.ext import CommandHandler, Updater
from freqtrade.__init__ import __version__ from freqtrade.__init__ import __version__
from freqtrade.rpc.rpc import RPC from freqtrade.rpc.rpc import RPC, RPCException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -151,13 +151,11 @@ class Telegram(RPC):
self._status_table(bot, update) self._status_table(bot, update)
return return
# Fetch open trade try:
(error, trades) = self.rpc_trade_status() for trade_msg in self._rpc_trade_status():
if error: self._send_msg(trade_msg, bot=bot)
self._send_msg(trades, bot=bot) except RPCException as e:
else: self._send_msg(str(e), bot=bot)
for trademsg in trades:
self._send_msg(trademsg, bot=bot)
@authorized_only @authorized_only
def _status_table(self, bot: Bot, update: Update) -> None: def _status_table(self, bot: Bot, update: Update) -> None:
@ -168,15 +166,12 @@ class Telegram(RPC):
:param update: message update :param update: message update
:return: None :return: None
""" """
# Fetch open trade try:
(err, df_statuses) = self.rpc_status_table() df_statuses = self._rpc_status_table()
if err:
self._send_msg(df_statuses, bot=bot)
else:
message = tabulate(df_statuses, headers='keys', tablefmt='simple') message = tabulate(df_statuses, headers='keys', tablefmt='simple')
message = "<pre>{}</pre>".format(message) self._send_msg("<pre>{}</pre>".format(message), parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(message, parse_mode=ParseMode.HTML) self._send_msg(str(e), bot=bot)
@authorized_only @authorized_only
def _daily(self, bot: Bot, update: Update) -> None: def _daily(self, bot: Bot, update: Update) -> None:
@ -191,14 +186,12 @@ class Telegram(RPC):
timescale = int(update.message.text.replace('/daily', '').strip()) timescale = int(update.message.text.replace('/daily', '').strip())
except (TypeError, ValueError): except (TypeError, ValueError):
timescale = 7 timescale = 7
(error, stats) = self.rpc_daily_profit( try:
timescale, stats = self._rpc_daily_profit(
self._config['stake_currency'], timescale,
self._config['fiat_display_currency'] self._config['stake_currency'],
) self._config['fiat_display_currency']
if error: )
self._send_msg(stats, bot=bot)
else:
stats = tabulate(stats, stats = tabulate(stats,
headers=[ headers=[
'Day', 'Day',
@ -207,11 +200,10 @@ class Telegram(RPC):
], ],
tablefmt='simple') tablefmt='simple')
message = '<b>Daily Profit over the last {} days</b>:\n<pre>{}</pre>'\ message = '<b>Daily Profit over the last {} days</b>:\n<pre>{}</pre>'\
.format( .format(timescale, stats)
timescale,
stats
)
self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML) self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only @authorized_only
def _profit(self, bot: Bot, update: Update) -> None: def _profit(self, bot: Bot, update: Update) -> None:
@ -222,67 +214,65 @@ class Telegram(RPC):
:param update: message update :param update: message update
:return: None :return: None
""" """
(error, stats) = self.rpc_trade_statistics( try:
self._config['stake_currency'], stats = self._rpc_trade_statistics(
self._config['fiat_display_currency'] self._config['stake_currency'],
) self._config['fiat_display_currency'])
if error:
self._send_msg(stats, bot=bot)
return
# Message to display # Message to display
markdown_msg = "*ROI:* Close trades\n" \ markdown_msg = "*ROI:* Close trades\n" \
"∙ `{profit_closed_coin:.8f} {coin} ({profit_closed_percent:.2f}%)`\n" \ "∙ `{profit_closed_coin:.8f} {coin} ({profit_closed_percent:.2f}%)`\n" \
"∙ `{profit_closed_fiat:.3f} {fiat}`\n" \ "∙ `{profit_closed_fiat:.3f} {fiat}`\n" \
"*ROI:* All trades\n" \ "*ROI:* All trades\n" \
"∙ `{profit_all_coin:.8f} {coin} ({profit_all_percent:.2f}%)`\n" \ "∙ `{profit_all_coin:.8f} {coin} ({profit_all_percent:.2f}%)`\n" \
"∙ `{profit_all_fiat:.3f} {fiat}`\n" \ "∙ `{profit_all_fiat:.3f} {fiat}`\n" \
"*Total Trade Count:* `{trade_count}`\n" \ "*Total Trade Count:* `{trade_count}`\n" \
"*First Trade opened:* `{first_trade_date}`\n" \ "*First Trade opened:* `{first_trade_date}`\n" \
"*Latest Trade opened:* `{latest_trade_date}`\n" \ "*Latest Trade opened:* `{latest_trade_date}`\n" \
"*Avg. Duration:* `{avg_duration}`\n" \ "*Avg. Duration:* `{avg_duration}`\n" \
"*Best Performing:* `{best_pair}: {best_rate:.2f}%`"\ "*Best Performing:* `{best_pair}: {best_rate:.2f}%`"\
.format( .format(
coin=self._config['stake_currency'], coin=self._config['stake_currency'],
fiat=self._config['fiat_display_currency'], fiat=self._config['fiat_display_currency'],
profit_closed_coin=stats['profit_closed_coin'], profit_closed_coin=stats['profit_closed_coin'],
profit_closed_percent=stats['profit_closed_percent'], profit_closed_percent=stats['profit_closed_percent'],
profit_closed_fiat=stats['profit_closed_fiat'], profit_closed_fiat=stats['profit_closed_fiat'],
profit_all_coin=stats['profit_all_coin'], profit_all_coin=stats['profit_all_coin'],
profit_all_percent=stats['profit_all_percent'], profit_all_percent=stats['profit_all_percent'],
profit_all_fiat=stats['profit_all_fiat'], profit_all_fiat=stats['profit_all_fiat'],
trade_count=stats['trade_count'], trade_count=stats['trade_count'],
first_trade_date=stats['first_trade_date'], first_trade_date=stats['first_trade_date'],
latest_trade_date=stats['latest_trade_date'], latest_trade_date=stats['latest_trade_date'],
avg_duration=stats['avg_duration'], avg_duration=stats['avg_duration'],
best_pair=stats['best_pair'], best_pair=stats['best_pair'],
best_rate=stats['best_rate'] best_rate=stats['best_rate']
) )
self._send_msg(markdown_msg, bot=bot) self._send_msg(markdown_msg, bot=bot)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only @authorized_only
def _balance(self, bot: Bot, update: Update) -> None: def _balance(self, bot: Bot, update: Update) -> None:
""" """
Handler for /balance Handler for /balance
""" """
(error, result) = self.rpc_balance(self._config['fiat_display_currency']) try:
if error: currencys, total, symbol, value = \
self._send_msg('`All balances are zero.`') self._rpc_balance(self._config['fiat_display_currency'])
return 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 += "\n*Estimated Value*:\n" \
output = '' "\t`BTC: {0: .8f}`\n" \
for currency in currencys: "\t`{1}: {2: .2f}`\n".format(total, symbol, value)
output += "*{currency}:*\n" \ self._send_msg(output, bot=bot)
"\t`Available: {available: .8f}`\n" \ except RPCException as e:
"\t`Balance: {balance: .8f}`\n" \ self._send_msg(str(e), bot=bot)
"\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)
@authorized_only @authorized_only
def _start(self, bot: Bot, update: Update) -> None: def _start(self, bot: Bot, update: Update) -> None:
@ -293,9 +283,8 @@ class Telegram(RPC):
:param update: message update :param update: message update
:return: None :return: None
""" """
(error, msg) = self.rpc_start() msg = self._rpc_start()
if error: self._send_msg(msg, bot=bot)
self._send_msg(msg, bot=bot)
@authorized_only @authorized_only
def _stop(self, bot: Bot, update: Update) -> None: def _stop(self, bot: Bot, update: Update) -> None:
@ -306,7 +295,7 @@ class Telegram(RPC):
:param update: message update :param update: message update
:return: None :return: None
""" """
(error, msg) = self.rpc_stop() msg = self._rpc_stop()
self._send_msg(msg, bot=bot) self._send_msg(msg, bot=bot)
@authorized_only @authorized_only
@ -332,10 +321,10 @@ class Telegram(RPC):
""" """
trade_id = update.message.text.replace('/forcesell', '').strip() trade_id = update.message.text.replace('/forcesell', '').strip()
(error, message) = self.rpc_forcesell(trade_id) try:
if error: self._rpc_forcesell(trade_id)
self._send_msg(message, bot=bot) except RPCException as e:
return self._send_msg(str(e), bot=bot)
@authorized_only @authorized_only
def _performance(self, bot: Bot, update: Update) -> None: def _performance(self, bot: Bot, update: Update) -> None:
@ -346,19 +335,18 @@ class Telegram(RPC):
:param update: message update :param update: message update
:return: None :return: None
""" """
(error, trades) = self.rpc_performance() try:
if error: trades = self._rpc_performance()
self._send_msg(trades, bot=bot) stats = '\n'.join('{index}.\t<code>{pair}\t{profit:.2f}% ({count})</code>'.format(
return index=i + 1,
pair=trade['pair'],
stats = '\n'.join('{index}.\t<code>{pair}\t{profit:.2f}% ({count})</code>'.format( profit=trade['profit'],
index=i + 1, count=trade['count']
pair=trade['pair'], ) for i, trade in enumerate(trades))
profit=trade['profit'], message = '<b>Performance:</b>\n{}'.format(stats)
count=trade['count'] self._send_msg(message, parse_mode=ParseMode.HTML)
) for i, trade in enumerate(trades)) except RPCException as e:
message = '<b>Performance:</b>\n{}'.format(stats) self._send_msg(str(e), bot=bot)
self._send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only @authorized_only
def _count(self, bot: Bot, update: Update) -> None: def _count(self, bot: Bot, update: Update) -> None:
@ -369,19 +357,18 @@ class Telegram(RPC):
:param update: message update :param update: message update
:return: None :return: None
""" """
(error, trades) = self.rpc_count() try:
if error: trades = self._rpc_count()
self._send_msg(trades, bot=bot) message = tabulate({
return 'current': [len(trades)],
'max': [self._config['max_open_trades']],
message = tabulate({ 'total stake': [sum((trade.open_rate * trade.amount) for trade in trades)]
'current': [len(trades)], }, headers=['current', 'max', 'total stake'], tablefmt='simple')
'max': [self._config['max_open_trades']], message = "<pre>{}</pre>".format(message)
'total stake': [sum((trade.open_rate * trade.amount) for trade in trades)] logger.debug(message)
}, headers=['current', 'max', 'total stake'], tablefmt='simple') self._send_msg(message, parse_mode=ParseMode.HTML)
message = "<pre>{}</pre>".format(message) except RPCException as e:
logger.debug(message) self._send_msg(str(e), bot=bot)
self._send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only @authorized_only
def _help(self, bot: Bot, update: Update) -> None: def _help(self, bot: Bot, update: Update) -> None:

View File

@ -7,9 +7,11 @@ Unit test file for rpc/rpc.py
from datetime import datetime from datetime import datetime
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade 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.state import State
from freqtrade.tests.test_freqtradebot import patch_get_signal, patch_coinmarketcap 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) rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
(error, result) = rpc.rpc_trade_status() with pytest.raises(RPCException, match=r'.*trader is not running*'):
assert error rpc._rpc_trade_status()
assert 'trader is not running' in result
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
(error, result) = rpc.rpc_trade_status() with pytest.raises(RPCException, match=r'.*no active trade*'):
assert error rpc._rpc_trade_status()
assert 'no active trade' in result
freqtradebot.create_trade() freqtradebot.create_trade()
(error, result) = rpc.rpc_trade_status() trades = rpc._rpc_trade_status()
assert not error trade = trades[0]
trade = result[0]
result_message = [ result_message = [
'*Trade ID:* `1`\n' '*Trade ID:* `1`\n'
@ -68,7 +67,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'*Current Profit:* `-0.59%`\n' '*Current Profit:* `-0.59%`\n'
'*Open Order:* `(limit buy rem=0.00000000)`' '*Open Order:* `(limit buy rem=0.00000000)`'
] ]
assert result == result_message assert trades == result_message
assert trade.find('[ETH/BTC]') >= 0 assert trade.find('[ETH/BTC]') >= 0
@ -90,17 +89,15 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
(error, result) = rpc.rpc_status_table() with pytest.raises(RPCException, match=r'.*\*Status:\* `trader is not running``*'):
assert error rpc._rpc_status_table()
assert '*Status:* `trader is not running`' in result
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
(error, result) = rpc.rpc_status_table() with pytest.raises(RPCException, match=r'.*\*Status:\* `no active order`*'):
assert error rpc._rpc_status_table()
assert '*Status:* `no active order`' in result
freqtradebot.create_trade() freqtradebot.create_trade()
(error, result) = rpc.rpc_status_table() result = rpc._rpc_status_table()
assert 'just now' in result['Since'].all() assert 'just now' in result['Since'].all()
assert 'ETH/BTC' in result['Pair'].all() assert 'ETH/BTC' in result['Pair'].all()
assert '-0.59%' in result['Profit'].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 # Try valid data
update.message.text = '/daily 2' update.message.text = '/daily 2'
(error, days) = rpc.rpc_daily_profit(7, stake_currency, fiat_display_currency) days = rpc._rpc_daily_profit(7, stake_currency, fiat_display_currency)
assert not error
assert len(days) == 7 assert len(days) == 7
for day in days: for day in days:
# [datetime.date(2018, 1, 11), '0.00000000 BTC', '0.000 USD'] # [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()) assert str(days[0][0]) == str(datetime.utcnow().date())
# Try invalid data # Try invalid data
(error, days) = rpc.rpc_daily_profit(0, stake_currency, fiat_display_currency) with pytest.raises(RPCException, match=r'.*must be an integer greater than 0*'):
assert error rpc._rpc_daily_profit(0, stake_currency, fiat_display_currency)
assert days.find('must be an integer greater than 0') >= 0
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, 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) rpc = RPC(freqtradebot)
(error, stats) = rpc.rpc_trade_statistics(stake_currency, fiat_display_currency) with pytest.raises(RPCException, match=r'.*no closed trade*'):
assert error rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
assert stats.find('no closed trade') >= 0
# Create some test data # Create some test data
freqtradebot.create_trade() 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.close_date = datetime.utcnow()
trade.is_open = False trade.is_open = False
(error, stats) = rpc.rpc_trade_statistics(stake_currency, fiat_display_currency) stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
assert not error
assert prec_satoshi(stats['profit_closed_coin'], 6.217e-05) 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_percent'], 6.2)
assert prec_satoshi(stats['profit_closed_fiat'], 0.93255) 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(): for trade in Trade.query.order_by(Trade.id).all():
trade.open_rate = None trade.open_rate = None
(error, stats) = rpc.rpc_trade_statistics(stake_currency, fiat_display_currency) stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
assert not error
assert prec_satoshi(stats['profit_closed_coin'], 0) assert prec_satoshi(stats['profit_closed_coin'], 0)
assert prec_satoshi(stats['profit_closed_percent'], 0) assert prec_satoshi(stats['profit_closed_percent'], 0)
assert prec_satoshi(stats['profit_closed_fiat'], 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) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
(error, res) = rpc.rpc_balance(default_conf['fiat_display_currency']) output, total, symbol, value = rpc._rpc_balance(default_conf['fiat_display_currency'])
assert not error assert prec_satoshi(total, 12)
(trade, x, y, z) = res assert prec_satoshi(value, 180000)
assert prec_satoshi(x, 12) assert 'USD' in symbol
assert prec_satoshi(z, 180000) assert len(output) == 1
assert 'USD' in y assert 'BTC' in output[0]['currency']
assert len(trade) == 1 assert prec_satoshi(output[0]['available'], 10)
assert 'BTC' in trade[0]['currency'] assert prec_satoshi(output[0]['balance'], 12)
assert prec_satoshi(trade[0]['available'], 10) assert prec_satoshi(output[0]['pending'], 2)
assert prec_satoshi(trade[0]['balance'], 12) assert prec_satoshi(output[0]['est_btc'], 12)
assert prec_satoshi(trade[0]['pending'], 2)
assert prec_satoshi(trade[0]['est_btc'], 12)
def test_rpc_start(mocker, default_conf) -> None: def test_rpc_start(mocker, default_conf) -> None:
@ -361,13 +351,11 @@ def test_rpc_start(mocker, default_conf) -> None:
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
(error, result) = rpc.rpc_start() result = rpc._rpc_start()
assert not error
assert '`Starting trader ...`' in result assert '`Starting trader ...`' in result
assert freqtradebot.state == State.RUNNING assert freqtradebot.state == State.RUNNING
(error, result) = rpc.rpc_start() result = rpc._rpc_start()
assert error
assert '*Status:* `already running`' in result assert '*Status:* `already running`' in result
assert freqtradebot.state == State.RUNNING assert freqtradebot.state == State.RUNNING
@ -389,13 +377,11 @@ def test_rpc_stop(mocker, default_conf) -> None:
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
(error, result) = rpc.rpc_stop() result = rpc._rpc_stop()
assert not error
assert '`Stopping trader ...`' in result assert '`Stopping trader ...`' in result
assert freqtradebot.state == State.STOPPED assert freqtradebot.state == State.STOPPED
(error, result) = rpc.rpc_stop() result = rpc._rpc_stop()
assert error
assert '*Status:* `already stopped`' in result assert '*Status:* `already stopped`' in result
assert freqtradebot.state == State.STOPPED assert freqtradebot.state == State.STOPPED
@ -428,36 +414,26 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
(error, res) = rpc.rpc_forcesell(None) with pytest.raises(RPCException, match=r'.*`trader is not running`*'):
assert error rpc._rpc_forcesell(None)
assert res == '`trader is not running`'
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
(error, res) = rpc.rpc_forcesell(None) with pytest.raises(RPCException, match=r'.*Invalid argument.*'):
assert error rpc._rpc_forcesell(None)
assert res == 'Invalid argument.'
(error, res) = rpc.rpc_forcesell('all') rpc._rpc_forcesell('all')
assert not error
assert res == ''
freqtradebot.create_trade() freqtradebot.create_trade()
(error, res) = rpc.rpc_forcesell('all') rpc._rpc_forcesell('all')
assert not error
assert res == ''
(error, res) = rpc.rpc_forcesell('1') rpc._rpc_forcesell('1')
assert not error
assert res == ''
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
(error, res) = rpc.rpc_forcesell(None) with pytest.raises(RPCException, match=r'.*`trader is not running`*'):
assert error rpc._rpc_forcesell(None)
assert res == '`trader is not running`'
(error, res) = rpc.rpc_forcesell('all') with pytest.raises(RPCException, match=r'.*`trader is not running`*'):
assert error rpc._rpc_forcesell('all')
assert res == '`trader is not running`'
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
assert cancel_order_mock.call_count == 0 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 # check that the trade is called, which is done by ensuring exchange.cancel_order is called
# and trade amount is updated # and trade amount is updated
(error, res) = rpc.rpc_forcesell('1') rpc._rpc_forcesell('1')
assert not error
assert res == ''
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert trade.amount == filled_amount 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 # check that the trade is called, which is done by ensuring exchange.cancel_order is called
(error, res) = rpc.rpc_forcesell('2') rpc._rpc_forcesell('2')
assert not error
assert res == ''
assert cancel_order_mock.call_count == 2 assert cancel_order_mock.call_count == 2
assert trade.amount == amount assert trade.amount == amount
@ -511,9 +483,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
'side': 'sell' 'side': 'sell'
} }
) )
(error, res) = rpc.rpc_forcesell('3') rpc._rpc_forcesell('3')
assert not error
assert res == ''
# status quo, no exchange calls # status quo, no exchange calls
assert cancel_order_mock.call_count == 2 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.close_date = datetime.utcnow()
trade.is_open = False trade.is_open = False
(error, res) = rpc.rpc_performance() res = rpc._rpc_performance()
assert not error
assert len(res) == 1 assert len(res) == 1
assert res[0]['pair'] == 'ETH/BTC' assert res[0]['pair'] == 'ETH/BTC'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
@ -576,14 +545,12 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
(error, trades) = rpc.rpc_count() trades = rpc._rpc_count()
nb_trades = len(trades) nb_trades = len(trades)
assert not error
assert nb_trades == 0 assert nb_trades == 0
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trade()
(error, trades) = rpc.rpc_count() trades = rpc._rpc_count()
nb_trades = len(trades) nb_trades = len(trades)
assert not error
assert nb_trades == 1 assert nb_trades == 1

View File

@ -104,7 +104,7 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
rpc_manager = RPCManager(freqtradebot) rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg('test') 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 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 = RPCManager(freqtradebot)
rpc_manager.send_msg('test') 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 assert telegram_mock.call_count == 1

View File

@ -60,9 +60,7 @@ def test__init__(default_conf, mocker) -> None:
def test_init(default_conf, mocker, caplog) -> None: def test_init(default_conf, mocker, caplog) -> None:
""" """ Test _init() method """
Test _init() method
"""
start_polling = MagicMock() start_polling = MagicMock()
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock(return_value=start_polling)) 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( mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram', 'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(), _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, _status_table=status_table,
_send_msg=msg_mock _send_msg=msg_mock
) )
@ -667,7 +665,7 @@ def test_start_handle(default_conf, update, mocker) -> None:
assert freqtradebot.state == State.STOPPED assert freqtradebot.state == State.STOPPED
telegram._start(bot=MagicMock(), update=update) telegram._start(bot=MagicMock(), update=update)
assert freqtradebot.state == State.RUNNING 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: def test_start_handle_already_running(default_conf, update, mocker) -> None: