Merge pull request #877 from freqtrade/feature/improve-rpc

Simplify RPCManager and RPC module to implement other clients
This commit is contained in:
Michael Egger 2018-06-13 15:49:49 +02:00 committed by GitHub
commit 2b74982a1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 346 additions and 488 deletions

View File

@ -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 <id>.
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()

View File

@ -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)

View File

@ -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 = "<pre>{}</pre>".format(message)
self.send_msg(message, parse_mode=ParseMode.HTML)
self._send_msg("<pre>{}</pre>".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 = '<b>Daily Profit over the last {} days</b>:\n<pre>{}</pre>'\
.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<code>{pair}\t{profit:.2f}% ({count})</code>'.format(
index=i + 1,
pair=trade['pair'],
profit=trade['profit'],
count=trade['count']
) for i, trade in enumerate(trades))
message = '<b>Performance:</b>\n{}'.format(stats)
self.send_msg(message, parse_mode=ParseMode.HTML)
try:
trades = self._rpc_performance()
stats = '\n'.join('{index}.\t<code>{pair}\t{profit:.2f}% ({count})</code>'.format(
index=i + 1,
pair=trade['pair'],
profit=trade['profit'],
count=trade['count']
) for i, trade in enumerate(trades))
message = '<b>Performance:</b>\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 = "<pre>{}</pre>".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 = "<pre>{}</pre>".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'],

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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