Refactor RPC modules so handlers don't inherit RPC directly

This commit is contained in:
Matthias 2020-12-24 09:01:53 +01:00
parent 1508e08ea5
commit a87c273903
9 changed files with 160 additions and 128 deletions

View File

@ -1,3 +1,3 @@
# flake8: noqa: F401 # flake8: noqa: F401
from .rpc import RPC, RPCException, RPCMessageType from .rpc import RPC, RPCException, RPCHandler, RPCMessageType
from .rpc_manager import RPCManager from .rpc_manager import RPCManager

View File

@ -20,7 +20,7 @@ from freqtrade.__init__ import __version__
from freqtrade.constants import DATETIME_PRINT_FORMAT, USERPATH_STRATEGIES from freqtrade.constants import DATETIME_PRINT_FORMAT, USERPATH_STRATEGIES
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc.rpc import RPC, RPCException from freqtrade.rpc.rpc import RPC, RPCException, RPCHandler
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -78,7 +78,7 @@ def shutdown_session(exception=None):
Trade.session.remove() Trade.session.remove()
class ApiServer(RPC): class ApiServer(RPCHandler):
""" """
This class runs api server and provides rpc.rpc functionality to it This class runs api server and provides rpc.rpc functionality to it
@ -89,13 +89,14 @@ class ApiServer(RPC):
return (safe_str_cmp(username, self._config['api_server'].get('username')) and return (safe_str_cmp(username, self._config['api_server'].get('username')) and
safe_str_cmp(password, self._config['api_server'].get('password'))) safe_str_cmp(password, self._config['api_server'].get('password')))
def __init__(self, freqtrade) -> None: def __init__(self, rpc: RPC, config: Dict[str, Any]) -> None:
""" """
Init the api server, and init the super class RPC Init the api server, and init the super class RPCHandler
:param freqtrade: Instance of a freqtrade bot :param rpc: instance of RPC Helper class
:param config: Configuration object
:return: None :return: None
""" """
super().__init__(freqtrade) super().__init__(rpc, config)
self.app = Flask(__name__) self.app = Flask(__name__)
self._cors = CORS(self.app, self._cors = CORS(self.app,
@ -282,7 +283,7 @@ class ApiServer(RPC):
Handler for /start. Handler for /start.
Starts TradeThread in bot if stopped. Starts TradeThread in bot if stopped.
""" """
msg = self._rpc_start() msg = self._rpc._rpc_start()
return jsonify(msg) return jsonify(msg)
@require_login @require_login
@ -292,7 +293,7 @@ class ApiServer(RPC):
Handler for /stop. Handler for /stop.
Stops TradeThread in bot if running Stops TradeThread in bot if running
""" """
msg = self._rpc_stop() msg = self._rpc._rpc_stop()
return jsonify(msg) return jsonify(msg)
@require_login @require_login
@ -302,7 +303,7 @@ class ApiServer(RPC):
Handler for /stopbuy. Handler for /stopbuy.
Sets max_open_trades to 0 and gracefully sells all open trades Sets max_open_trades to 0 and gracefully sells all open trades
""" """
msg = self._rpc_stopbuy() msg = self._rpc._rpc_stopbuy()
return jsonify(msg) return jsonify(msg)
@rpc_catch_errors @rpc_catch_errors
@ -326,7 +327,7 @@ class ApiServer(RPC):
""" """
Prints the bot's version Prints the bot's version
""" """
return jsonify(RPC._rpc_show_config(self._config, self._freqtrade.state)) return jsonify(RPC._rpc_show_config(self._config, self._rpc._freqtrade.state))
@require_login @require_login
@rpc_catch_errors @rpc_catch_errors
@ -335,7 +336,7 @@ class ApiServer(RPC):
Handler for /reload_config. Handler for /reload_config.
Triggers a config file reload Triggers a config file reload
""" """
msg = self._rpc_reload_config() msg = self._rpc._rpc_reload_config()
return jsonify(msg) return jsonify(msg)
@require_login @require_login
@ -345,7 +346,7 @@ class ApiServer(RPC):
Handler for /count. Handler for /count.
Returns the number of trades running Returns the number of trades running
""" """
msg = self._rpc_count() msg = self._rpc._rpc_count()
return jsonify(msg) return jsonify(msg)
@require_login @require_login
@ -355,7 +356,7 @@ class ApiServer(RPC):
Handler for /locks. Handler for /locks.
Returns the currently active locks. Returns the currently active locks.
""" """
return jsonify(self._rpc_locks()) return jsonify(self._rpc._rpc_locks())
@require_login @require_login
@rpc_catch_errors @rpc_catch_errors
@ -368,7 +369,7 @@ class ApiServer(RPC):
timescale = request.args.get('timescale', 7) timescale = request.args.get('timescale', 7)
timescale = int(timescale) timescale = int(timescale)
stats = self._rpc_daily_profit(timescale, stats = self._rpc._rpc_daily_profit(timescale,
self._config['stake_currency'], self._config['stake_currency'],
self._config.get('fiat_display_currency', '') self._config.get('fiat_display_currency', '')
) )
@ -394,7 +395,7 @@ class ApiServer(RPC):
Returns information related to Edge. Returns information related to Edge.
:return: edge stats :return: edge stats
""" """
stats = self._rpc_edge() stats = self._rpc._rpc_edge()
return jsonify(stats) return jsonify(stats)
@ -408,7 +409,7 @@ class ApiServer(RPC):
:return: stats :return: stats
""" """
stats = self._rpc_trade_statistics(self._config['stake_currency'], stats = self._rpc._rpc_trade_statistics(self._config['stake_currency'],
self._config.get('fiat_display_currency') self._config.get('fiat_display_currency')
) )
@ -422,7 +423,7 @@ class ApiServer(RPC):
Returns a Object with "durations" and "sell_reasons" as keys. Returns a Object with "durations" and "sell_reasons" as keys.
""" """
stats = self._rpc_stats() stats = self._rpc._rpc_stats()
return jsonify(stats) return jsonify(stats)
@ -435,7 +436,7 @@ class ApiServer(RPC):
Returns a cumulative performance statistics Returns a cumulative performance statistics
:return: stats :return: stats
""" """
stats = self._rpc_performance() stats = self._rpc._rpc_performance()
return jsonify(stats) return jsonify(stats)
@ -448,7 +449,7 @@ class ApiServer(RPC):
Returns the current status of the trades in json format Returns the current status of the trades in json format
""" """
try: try:
results = self._rpc_trade_status() results = self._rpc._rpc_trade_status()
return jsonify(results) return jsonify(results)
except RPCException: except RPCException:
return jsonify([]) return jsonify([])
@ -461,7 +462,7 @@ class ApiServer(RPC):
Returns the current status of the trades in json format Returns the current status of the trades in json format
""" """
results = self._rpc_balance(self._config['stake_currency'], results = self._rpc._rpc_balance(self._config['stake_currency'],
self._config.get('fiat_display_currency', '')) self._config.get('fiat_display_currency', ''))
return jsonify(results) return jsonify(results)
@ -474,7 +475,7 @@ class ApiServer(RPC):
Returns the X last trades in json format Returns the X last trades in json format
""" """
limit = int(request.args.get('limit', 0)) limit = int(request.args.get('limit', 0))
results = self._rpc_trade_history(limit) results = self._rpc._rpc_trade_history(limit)
return jsonify(results) return jsonify(results)
@require_login @require_login
@ -487,7 +488,7 @@ class ApiServer(RPC):
param: param:
tradeid: Numeric trade-id assigned to the trade. tradeid: Numeric trade-id assigned to the trade.
""" """
result = self._rpc_delete(tradeid) result = self._rpc._rpc_delete(tradeid)
return jsonify(result) return jsonify(result)
@require_login @require_login
@ -496,7 +497,7 @@ class ApiServer(RPC):
""" """
Handler for /whitelist. Handler for /whitelist.
""" """
results = self._rpc_whitelist() results = self._rpc._rpc_whitelist()
return jsonify(results) return jsonify(results)
@require_login @require_login
@ -506,7 +507,7 @@ class ApiServer(RPC):
Handler for /blacklist. Handler for /blacklist.
""" """
add = request.json.get("blacklist", None) if request.method == 'POST' else None add = request.json.get("blacklist", None) if request.method == 'POST' else None
results = self._rpc_blacklist(add) results = self._rpc._rpc_blacklist(add)
return jsonify(results) return jsonify(results)
@require_login @require_login
@ -519,7 +520,7 @@ class ApiServer(RPC):
price = request.json.get("price", None) price = request.json.get("price", None)
price = float(price) if price is not None else price price = float(price) if price is not None else price
trade = self._rpc_forcebuy(asset, price) trade = self._rpc._rpc_forcebuy(asset, price)
if trade: if trade:
return jsonify(trade.to_json()) return jsonify(trade.to_json())
else: else:
@ -532,7 +533,7 @@ class ApiServer(RPC):
Handler for /forcesell. Handler for /forcesell.
""" """
tradeid = request.json.get("tradeid") tradeid = request.json.get("tradeid")
results = self._rpc_forcesell(tradeid) results = self._rpc._rpc_forcesell(tradeid)
return jsonify(results) return jsonify(results)
@require_login @require_login
@ -554,7 +555,7 @@ class ApiServer(RPC):
if not pair or not timeframe: if not pair or not timeframe:
return self.rest_error("Mandatory parameter missing.", 400) return self.rest_error("Mandatory parameter missing.", 400)
results = self._rpc_analysed_dataframe(pair, timeframe, limit) results = self._rpc._rpc_analysed_dataframe(pair, timeframe, limit)
return jsonify(results) return jsonify(results)
@require_login @require_login
@ -593,7 +594,7 @@ class ApiServer(RPC):
""" """
Handler for /plot_config. Handler for /plot_config.
""" """
return jsonify(self._rpc_plot_config()) return jsonify(self._rpc._rpc_plot_config())
@require_login @require_login
@rpc_catch_errors @rpc_catch_errors

View File

@ -65,6 +65,32 @@ class RPCException(Exception):
} }
class RPCHandler:
def __init__(self, rpc: 'RPC', config: Dict[str, Any]) -> None:
"""
Initializes RPCHandlers
:param rpc: instance of RPC Helper class
:param config: Configuration object
:return: None
"""
self._rpc = rpc
self._config: Dict[str, Any] = config
@property
def name(self) -> str:
""" Returns the lowercase name of the implementation """
return self.__class__.__name__.lower()
@abstractmethod
def cleanup(self) -> None:
""" Cleanup pending module resources """
@abstractmethod
def send_msg(self, msg: Dict[str, str]) -> None:
""" Sends a message to all registered rpc modules """
class RPC: class RPC:
""" """
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
@ -83,19 +109,6 @@ class RPC:
if self._config.get('fiat_display_currency', None): if self._config.get('fiat_display_currency', None):
self._fiat_converter = CryptoToFiatConverter() self._fiat_converter = CryptoToFiatConverter()
@property
def name(self) -> str:
""" Returns the lowercase name of the implementation """
return self.__class__.__name__.lower()
@abstractmethod
def cleanup(self) -> None:
""" Cleanup pending module resources """
@abstractmethod
def send_msg(self, msg: Dict[str, str]) -> None:
""" Sends a message to all registered rpc modules """
@staticmethod @staticmethod
def _rpc_show_config(config, botstate: State) -> Dict[str, Any]: def _rpc_show_config(config, botstate: State) -> Dict[str, Any]:
""" """

View File

@ -4,7 +4,7 @@ This module contains class to manage RPC communications (Telegram, Slack, ...)
import logging import logging
from typing import Any, Dict, List from typing import Any, Dict, List
from freqtrade.rpc import RPC, RPCMessageType from freqtrade.rpc import RPC, RPCHandler, RPCMessageType
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -16,25 +16,26 @@ class RPCManager:
""" """
def __init__(self, freqtrade) -> None: def __init__(self, freqtrade) -> None:
""" Initializes all enabled rpc modules """ """ Initializes all enabled rpc modules """
self.registered_modules: List[RPC] = [] self.registered_modules: List[RPCHandler] = []
self._rpc = RPC(freqtrade)
config = freqtrade.config
# Enable telegram # Enable telegram
if freqtrade.config.get('telegram', {}).get('enabled', False): if config.get('telegram', {}).get('enabled', False):
logger.info('Enabling rpc.telegram ...') logger.info('Enabling rpc.telegram ...')
from freqtrade.rpc.telegram import Telegram from freqtrade.rpc.telegram import Telegram
self.registered_modules.append(Telegram(freqtrade)) self.registered_modules.append(Telegram(self._rpc, config))
# Enable Webhook # Enable Webhook
if freqtrade.config.get('webhook', {}).get('enabled', False): if config.get('webhook', {}).get('enabled', False):
logger.info('Enabling rpc.webhook ...') logger.info('Enabling rpc.webhook ...')
from freqtrade.rpc.webhook import Webhook from freqtrade.rpc.webhook import Webhook
self.registered_modules.append(Webhook(freqtrade)) self.registered_modules.append(Webhook(self._rpc, config))
# Enable local rest api server for cmd line control # Enable local rest api server for cmd line control
if freqtrade.config.get('api_server', {}).get('enabled', False): if config.get('api_server', {}).get('enabled', False):
logger.info('Enabling rpc.api_server') logger.info('Enabling rpc.api_server')
from freqtrade.rpc.api_server import ApiServer from freqtrade.rpc.api_server import ApiServer
self.registered_modules.append(ApiServer(freqtrade)) self.registered_modules.append(ApiServer(self._rpc, config))
def cleanup(self) -> None: def cleanup(self) -> None:
""" Stops all enabled rpc modules """ """ Stops all enabled rpc modules """

View File

@ -18,7 +18,7 @@ from telegram.utils.helpers import escape_markdown
from freqtrade.__init__ import __version__ from freqtrade.__init__ import __version__
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.rpc import RPC, RPCException, RPCMessageType from freqtrade.rpc import RPC, RPCException, RPCHandler, RPCMessageType
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -62,16 +62,18 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
return wrapper return wrapper
class Telegram(RPC): class Telegram(RPCHandler):
""" This class handles all telegram communication """ """ This class handles all telegram communication """
def __init__(self, freqtrade) -> None: def __init__(self, rpc: RPC, config: Dict[str, Any]) -> None:
""" """
Init the Telegram call, and init the super class RPC Init the Telegram call, and init the super class RPCHandler
:param freqtrade: Instance of a freqtrade bot :param rpc: instance of RPC Helper class
:param config: Configuration object
:return: None :return: None
""" """
super().__init__(freqtrade) super().__init__(rpc, config)
self._updater: Updater self._updater: Updater
self._init_keyboard() self._init_keyboard()
@ -181,8 +183,8 @@ class Telegram(RPC):
return return
if msg['type'] == RPCMessageType.BUY_NOTIFICATION: if msg['type'] == RPCMessageType.BUY_NOTIFICATION:
if self._fiat_converter: if self._rpc._fiat_converter:
msg['stake_amount_fiat'] = self._fiat_converter.convert_amount( msg['stake_amount_fiat'] = self._rpc._fiat_converter.convert_amount(
msg['stake_amount'], msg['stake_currency'], msg['fiat_currency']) msg['stake_amount'], msg['stake_currency'], msg['fiat_currency'])
else: else:
msg['stake_amount_fiat'] = 0 msg['stake_amount_fiat'] = 0
@ -222,8 +224,8 @@ class Telegram(RPC):
# Check if all sell properties are available. # Check if all sell properties are available.
# This might not be the case if the message origin is triggered by /forcesell # This might not be the case if the message origin is triggered by /forcesell
if (all(prop in msg for prop in ['gain', 'fiat_currency', 'stake_currency']) if (all(prop in msg for prop in ['gain', 'fiat_currency', 'stake_currency'])
and self._fiat_converter): and self._rpc._fiat_converter):
msg['profit_fiat'] = self._fiat_converter.convert_amount( msg['profit_fiat'] = self._rpc._fiat_converter.convert_amount(
msg['profit_amount'], msg['stake_currency'], msg['fiat_currency']) msg['profit_amount'], msg['stake_currency'], msg['fiat_currency'])
message += (' `({gain}: {profit_amount:.8f} {stake_currency}' message += (' `({gain}: {profit_amount:.8f} {stake_currency}'
' / {profit_fiat:.3f} {fiat_currency})`').format(**msg) ' / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
@ -275,7 +277,7 @@ class Telegram(RPC):
return return
try: try:
results = self._rpc_trade_status() results = self._rpc._rpc_trade_status()
messages = [] messages = []
for r in results: for r in results:
@ -325,8 +327,9 @@ class Telegram(RPC):
:return: None :return: None
""" """
try: try:
statlist, head = self._rpc_status_table(self._config['stake_currency'], statlist, head = self._rpc._rpc_status_table(
self._config.get('fiat_display_currency', '')) self._config['stake_currency'], self._config.get('fiat_display_currency', ''))
message = tabulate(statlist, headers=head, tablefmt='simple') message = tabulate(statlist, headers=head, tablefmt='simple')
self._send_msg(f"<pre>{message}</pre>", parse_mode=ParseMode.HTML) self._send_msg(f"<pre>{message}</pre>", parse_mode=ParseMode.HTML)
except RPCException as e: except RPCException as e:
@ -348,7 +351,7 @@ class Telegram(RPC):
except (TypeError, ValueError, IndexError): except (TypeError, ValueError, IndexError):
timescale = 7 timescale = 7
try: try:
stats = self._rpc_daily_profit( stats = self._rpc._rpc_daily_profit(
timescale, timescale,
stake_cur, stake_cur,
fiat_disp_cur fiat_disp_cur
@ -382,7 +385,7 @@ class Telegram(RPC):
stake_cur = self._config['stake_currency'] stake_cur = self._config['stake_currency']
fiat_disp_cur = self._config.get('fiat_display_currency', '') fiat_disp_cur = self._config.get('fiat_display_currency', '')
stats = self._rpc_trade_statistics( stats = self._rpc._rpc_trade_statistics(
stake_cur, stake_cur,
fiat_disp_cur) fiat_disp_cur)
profit_closed_coin = stats['profit_closed_coin'] profit_closed_coin = stats['profit_closed_coin']
@ -433,7 +436,7 @@ class Telegram(RPC):
Handler for /stats Handler for /stats
Show stats of recent trades Show stats of recent trades
""" """
stats = self._rpc_stats() stats = self._rpc._rpc_stats()
reason_map = { reason_map = {
'roi': 'ROI', 'roi': 'ROI',
@ -473,7 +476,7 @@ class Telegram(RPC):
def _balance(self, update: Update, context: CallbackContext) -> None: def _balance(self, update: Update, context: CallbackContext) -> None:
""" Handler for /balance """ """ Handler for /balance """
try: try:
result = self._rpc_balance(self._config['stake_currency'], result = self._rpc._rpc_balance(self._config['stake_currency'],
self._config.get('fiat_display_currency', '')) self._config.get('fiat_display_currency', ''))
output = '' output = ''
@ -517,7 +520,7 @@ class Telegram(RPC):
:param update: message update :param update: message update
:return: None :return: None
""" """
msg = self._rpc_start() msg = self._rpc._rpc_start()
self._send_msg('Status: `{status}`'.format(**msg)) self._send_msg('Status: `{status}`'.format(**msg))
@authorized_only @authorized_only
@ -529,7 +532,7 @@ class Telegram(RPC):
:param update: message update :param update: message update
:return: None :return: None
""" """
msg = self._rpc_stop() msg = self._rpc._rpc_stop()
self._send_msg('Status: `{status}`'.format(**msg)) self._send_msg('Status: `{status}`'.format(**msg))
@authorized_only @authorized_only
@ -541,7 +544,7 @@ class Telegram(RPC):
:param update: message update :param update: message update
:return: None :return: None
""" """
msg = self._rpc_reload_config() msg = self._rpc._rpc_reload_config()
self._send_msg('Status: `{status}`'.format(**msg)) self._send_msg('Status: `{status}`'.format(**msg))
@authorized_only @authorized_only
@ -553,7 +556,7 @@ class Telegram(RPC):
:param update: message update :param update: message update
:return: None :return: None
""" """
msg = self._rpc_stopbuy() msg = self._rpc._rpc_stopbuy()
self._send_msg('Status: `{status}`'.format(**msg)) self._send_msg('Status: `{status}`'.format(**msg))
@authorized_only @authorized_only
@ -571,7 +574,7 @@ class Telegram(RPC):
self._send_msg("You must specify a trade-id or 'all'.") self._send_msg("You must specify a trade-id or 'all'.")
return return
try: try:
msg = self._rpc_forcesell(trade_id) msg = self._rpc._rpc_forcesell(trade_id)
self._send_msg('Forcesell Result: `{result}`'.format(**msg)) self._send_msg('Forcesell Result: `{result}`'.format(**msg))
except RPCException as e: except RPCException as e:
@ -590,7 +593,7 @@ class Telegram(RPC):
pair = context.args[0] pair = context.args[0]
price = float(context.args[1]) if len(context.args) > 1 else None price = float(context.args[1]) if len(context.args) > 1 else None
try: try:
self._rpc_forcebuy(pair, price) self._rpc._rpc_forcebuy(pair, price)
except RPCException as e: except RPCException as e:
self._send_msg(str(e)) self._send_msg(str(e))
@ -609,7 +612,7 @@ class Telegram(RPC):
except (TypeError, ValueError, IndexError): except (TypeError, ValueError, IndexError):
nrecent = 10 nrecent = 10
try: try:
trades = self._rpc_trade_history( trades = self._rpc._rpc_trade_history(
nrecent nrecent
) )
trades_tab = tabulate( trades_tab = tabulate(
@ -642,7 +645,7 @@ class Telegram(RPC):
if not context.args or len(context.args) == 0: if not context.args or len(context.args) == 0:
raise RPCException("Trade-id not set.") raise RPCException("Trade-id not set.")
trade_id = int(context.args[0]) trade_id = int(context.args[0])
msg = self._rpc_delete(trade_id) msg = self._rpc._rpc_delete(trade_id)
self._send_msg(( self._send_msg((
'`{result_msg}`\n' '`{result_msg}`\n'
'Please make sure to take care of this asset on the exchange manually.' 'Please make sure to take care of this asset on the exchange manually.'
@ -661,7 +664,7 @@ class Telegram(RPC):
:return: None :return: None
""" """
try: try:
trades = self._rpc_performance() trades = self._rpc._rpc_performance()
stats = '\n'.join('{index}.\t<code>{pair}\t{profit:.2f}% ({count})</code>'.format( stats = '\n'.join('{index}.\t<code>{pair}\t{profit:.2f}% ({count})</code>'.format(
index=i + 1, index=i + 1,
pair=trade['pair'], pair=trade['pair'],
@ -683,7 +686,7 @@ class Telegram(RPC):
:return: None :return: None
""" """
try: try:
counts = self._rpc_count() counts = self._rpc._rpc_count()
message = tabulate({k: [v] for k, v in counts.items()}, message = tabulate({k: [v] for k, v in counts.items()},
headers=['current', 'max', 'total stake'], headers=['current', 'max', 'total stake'],
tablefmt='simple') tablefmt='simple')
@ -700,7 +703,7 @@ class Telegram(RPC):
Returns the currently active locks Returns the currently active locks
""" """
try: try:
locks = self._rpc_locks() locks = self._rpc._rpc_locks()
message = tabulate([[ message = tabulate([[
lock['pair'], lock['pair'],
lock['lock_end_time'], lock['lock_end_time'],
@ -720,7 +723,7 @@ class Telegram(RPC):
Shows the currently active whitelist Shows the currently active whitelist
""" """
try: try:
whitelist = self._rpc_whitelist() whitelist = self._rpc._rpc_whitelist()
message = f"Using whitelist `{whitelist['method']}` with {whitelist['length']} pairs\n" message = f"Using whitelist `{whitelist['method']}` with {whitelist['length']} pairs\n"
message += f"`{', '.join(whitelist['whitelist'])}`" message += f"`{', '.join(whitelist['whitelist'])}`"
@ -738,7 +741,7 @@ class Telegram(RPC):
""" """
try: try:
blacklist = self._rpc_blacklist(context.args) blacklist = self._rpc._rpc_blacklist(context.args)
errmsgs = [] errmsgs = []
for pair, error in blacklist['errors'].items(): for pair, error in blacklist['errors'].items():
errmsgs.append(f"Error adding `{pair}` to blacklist: `{error['error_msg']}`") errmsgs.append(f"Error adding `{pair}` to blacklist: `{error['error_msg']}`")
@ -792,7 +795,7 @@ class Telegram(RPC):
Shows information related to Edge Shows information related to Edge
""" """
try: try:
edge_pairs = self._rpc_edge() edge_pairs = self._rpc._rpc_edge()
edge_pairs_tab = tabulate(edge_pairs, headers='keys', tablefmt='simple') edge_pairs_tab = tabulate(edge_pairs, headers='keys', tablefmt='simple')
message = f'<b>Edge only validated following pairs:</b>\n<pre>{edge_pairs_tab}</pre>' message = f'<b>Edge only validated following pairs:</b>\n<pre>{edge_pairs_tab}</pre>'
self._send_msg(message, parse_mode=ParseMode.HTML) self._send_msg(message, parse_mode=ParseMode.HTML)
@ -862,7 +865,7 @@ class Telegram(RPC):
:param update: message update :param update: message update
:return: None :return: None
""" """
val = RPC._rpc_show_config(self._freqtrade.config, self._freqtrade.state) val = RPC._rpc_show_config(self._config, self._rpc._freqtrade.state)
if val['trailing_stop']: if val['trailing_stop']:
sl_info = ( sl_info = (

View File

@ -6,7 +6,7 @@ from typing import Any, Dict
from requests import RequestException, post from requests import RequestException, post
from freqtrade.rpc import RPC, RPCMessageType from freqtrade.rpc import RPC, RPCHandler, RPCMessageType
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -14,16 +14,17 @@ logger = logging.getLogger(__name__)
logger.debug('Included module rpc.webhook ...') logger.debug('Included module rpc.webhook ...')
class Webhook(RPC): class Webhook(RPCHandler):
""" This class handles all webhook communication """ """ This class handles all webhook communication """
def __init__(self, freqtrade) -> None: def __init__(self, rpc: RPC, config: Dict[str, Any]) -> None:
""" """
Init the Webhook class, and init the super class RPC Init the Webhook class, and init the super class RPCHandler
:param freqtrade: Instance of a freqtrade bot :param rpc: instance of RPC Helper class
:param config: Configuration object
:return: None :return: None
""" """
super().__init__(freqtrade) super().__init__(rpc, config)
self._url = self._config['webhook']['url'] self._url = self._config['webhook']['url']

View File

@ -13,6 +13,7 @@ from requests.auth import _basic_auth_str
from freqtrade.__init__ import __version__ from freqtrade.__init__ import __version__
from freqtrade.loggers import setup_logging, setup_logging_pre from freqtrade.loggers import setup_logging, setup_logging_pre
from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence import PairLocks, Trade
from freqtrade.rpc import RPC
from freqtrade.rpc.api_server import BASE_URI, ApiServer from freqtrade.rpc.api_server import BASE_URI, ApiServer
from freqtrade.state import RunMode, State from freqtrade.state import RunMode, State
from tests.conftest import create_mock_trades, get_patched_freqtradebot, log_has, patch_get_signal from tests.conftest import create_mock_trades, get_patched_freqtradebot, log_has, patch_get_signal
@ -36,8 +37,9 @@ def botclient(default_conf, mocker):
}}) }})
ftbot = get_patched_freqtradebot(mocker, default_conf) ftbot = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(ftbot)
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock()) mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock())
apiserver = ApiServer(ftbot) apiserver = ApiServer(rpc, default_conf)
yield ftbot, apiserver.app.test_client() yield ftbot, apiserver.app.test_client()
# Cleanup ... ? # Cleanup ... ?
@ -179,8 +181,7 @@ def test_api__init__(default_conf, mocker):
}}) }})
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock()) mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock())
apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf))
assert apiserver._config == default_conf assert apiserver._config == default_conf
@ -197,7 +198,7 @@ def test_api_run(default_conf, mocker, caplog):
server_mock = MagicMock() server_mock = MagicMock()
mocker.patch('freqtrade.rpc.api_server.make_server', server_mock) mocker.patch('freqtrade.rpc.api_server.make_server', server_mock)
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf)) apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
assert apiserver._config == default_conf assert apiserver._config == default_conf
apiserver.run() apiserver.run()
@ -251,7 +252,7 @@ def test_api_cleanup(default_conf, mocker, caplog):
mocker.patch('freqtrade.rpc.api_server.threading.Thread', MagicMock()) mocker.patch('freqtrade.rpc.api_server.threading.Thread', MagicMock())
mocker.patch('freqtrade.rpc.api_server.make_server', MagicMock()) mocker.patch('freqtrade.rpc.api_server.make_server', MagicMock())
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf)) apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
apiserver.run() apiserver.run()
stop_mock = MagicMock() stop_mock = MagicMock()
stop_mock.shutdown = MagicMock() stop_mock.shutdown = MagicMock()

View File

@ -20,7 +20,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.loggers import setup_logging from freqtrade.loggers import setup_logging
from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence import PairLocks, Trade
from freqtrade.rpc import RPCMessageType from freqtrade.rpc import RPC, RPCMessageType
from freqtrade.rpc.telegram import Telegram, authorized_only from freqtrade.rpc.telegram import Telegram, authorized_only
from freqtrade.state import RunMode, State from freqtrade.state import RunMode, State
from freqtrade.strategy.interface import SellType from freqtrade.strategy.interface import SellType
@ -32,8 +32,8 @@ class DummyCls(Telegram):
""" """
Dummy class for testing the Telegram @authorized_only decorator Dummy class for testing the Telegram @authorized_only decorator
""" """
def __init__(self, freqtrade) -> None: def __init__(self, rpc: RPC, config) -> None:
super().__init__(freqtrade) super().__init__(rpc, config)
self.state = {'called': False} self.state = {'called': False}
def _init(self): def _init(self):
@ -54,7 +54,7 @@ class DummyCls(Telegram):
raise Exception('test') raise Exception('test')
def get_telegram_testobject(mocker, default_conf, mock=True): def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None):
msg_mock = MagicMock() msg_mock = MagicMock()
if mock: if mock:
mocker.patch.multiple( mocker.patch.multiple(
@ -62,8 +62,10 @@ def get_telegram_testobject(mocker, default_conf, mock=True):
_init=MagicMock(), _init=MagicMock(),
_send_msg=msg_mock _send_msg=msg_mock
) )
if not ftbot:
ftbot = get_patched_freqtradebot(mocker, default_conf) ftbot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(ftbot) rpc = RPC(ftbot)
telegram = Telegram(rpc, default_conf)
return telegram, ftbot, msg_mock return telegram, ftbot, msg_mock
@ -112,8 +114,10 @@ def test_authorized_only(default_conf, mocker, caplog, update) -> None:
default_conf['telegram']['enabled'] = False default_conf['telegram']['enabled'] = False
bot = FreqtradeBot(default_conf) bot = FreqtradeBot(default_conf)
rpc = RPC(bot)
dummy = DummyCls(rpc, default_conf)
patch_get_signal(bot, (True, False)) patch_get_signal(bot, (True, False))
dummy = DummyCls(bot)
dummy.dummy_handler(update=update, context=MagicMock()) dummy.dummy_handler(update=update, context=MagicMock())
assert dummy.state['called'] is True assert dummy.state['called'] is True
assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog) assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog)
@ -129,8 +133,10 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
default_conf['telegram']['enabled'] = False default_conf['telegram']['enabled'] = False
bot = FreqtradeBot(default_conf) bot = FreqtradeBot(default_conf)
rpc = RPC(bot)
dummy = DummyCls(rpc, default_conf)
patch_get_signal(bot, (True, False)) patch_get_signal(bot, (True, False))
dummy = DummyCls(bot)
dummy.dummy_handler(update=update, context=MagicMock()) dummy.dummy_handler(update=update, context=MagicMock())
assert dummy.state['called'] is False assert dummy.state['called'] is False
assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog) assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog)
@ -144,8 +150,9 @@ def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None
default_conf['telegram']['enabled'] = False default_conf['telegram']['enabled'] = False
bot = FreqtradeBot(default_conf) bot = FreqtradeBot(default_conf)
rpc = RPC(bot)
dummy = DummyCls(rpc, default_conf)
patch_get_signal(bot, (True, False)) patch_get_signal(bot, (True, False))
dummy = DummyCls(bot)
dummy.dummy_exception(update=update, context=MagicMock()) dummy.dummy_exception(update=update, context=MagicMock())
assert dummy.state['called'] is False assert dummy.state['called'] is False
@ -160,8 +167,10 @@ def test_telegram_status(default_conf, update, mocker) -> None:
default_conf['telegram']['chat_id'] = "123" default_conf['telegram']['chat_id'] = "123"
status_table = MagicMock() status_table = MagicMock()
mocker.patch('freqtrade.rpc.telegram.Telegram._status_table', status_table)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram', 'freqtrade.rpc.rpc.RPC',
_rpc_trade_status=MagicMock(return_value=[{ _rpc_trade_status=MagicMock(return_value=[{
'trade_id': 1, 'trade_id': 1,
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
@ -188,7 +197,6 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'open_order': '(limit buy rem=0.00000000)', 'open_order': '(limit buy rem=0.00000000)',
'is_open': True 'is_open': True
}]), }]),
_status_table=status_table,
) )
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@ -642,8 +650,9 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
) )
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot)
telegram = Telegram(rpc, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False))
telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
freqtradebot.enter_positions() freqtradebot.enter_positions()
@ -698,8 +707,9 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
) )
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot)
telegram = Telegram(rpc, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False))
telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
freqtradebot.enter_positions() freqtradebot.enter_positions()
@ -756,8 +766,9 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
) )
default_conf['max_open_trades'] = 4 default_conf['max_open_trades'] = 4
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot)
telegram = Telegram(rpc, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False))
telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
freqtradebot.enter_positions() freqtradebot.enter_positions()
@ -1216,8 +1227,8 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
old_convamount = telegram._fiat_converter.convert_amount old_convamount = telegram._rpc._fiat_converter.convert_amount
telegram._fiat_converter.convert_amount = lambda a, b, c: -24.812 telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
telegram.send_msg({ telegram.send_msg({
'type': RPCMessageType.SELL_NOTIFICATION, 'type': RPCMessageType.SELL_NOTIFICATION,
'exchange': 'Binance', 'exchange': 'Binance',
@ -1274,15 +1285,15 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n' '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
'*Profit:* `-57.41%`') '*Profit:* `-57.41%`')
# Reset singleton function to avoid random breaks # Reset singleton function to avoid random breaks
telegram._fiat_converter.convert_amount = old_convamount telegram._rpc._fiat_converter.convert_amount = old_convamount
def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None: def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
old_convamount = telegram._fiat_converter.convert_amount old_convamount = telegram._rpc._fiat_converter.convert_amount
telegram._fiat_converter.convert_amount = lambda a, b, c: -24.812 telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
telegram.send_msg({ telegram.send_msg({
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION, 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
'exchange': 'Binance', 'exchange': 'Binance',
@ -1303,7 +1314,7 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: timeout') == ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: timeout')
# Reset singleton function to avoid random breaks # Reset singleton function to avoid random breaks
telegram._fiat_converter.convert_amount = old_convamount telegram._rpc._fiat_converter.convert_amount = old_convamount
def test_send_msg_status_notification(default_conf, mocker) -> None: def test_send_msg_status_notification(default_conf, mocker) -> None:
@ -1449,6 +1460,7 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
bot = MagicMock() bot = MagicMock()
bot.send_message = MagicMock() bot.send_message = MagicMock()
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(freqtradebot)
invalid_keys_list = [['/not_valid', '/profit'], ['/daily'], ['/alsoinvalid']] invalid_keys_list = [['/not_valid', '/profit'], ['/daily'], ['/alsoinvalid']]
default_keys_list = [['/daily', '/profit', '/balance'], default_keys_list = [['/daily', '/profit', '/balance'],
@ -1461,7 +1473,7 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
custom_keyboard = ReplyKeyboardMarkup(custom_keys_list) custom_keyboard = ReplyKeyboardMarkup(custom_keys_list)
def init_telegram(freqtradebot): def init_telegram(freqtradebot):
telegram = Telegram(freqtradebot) telegram = Telegram(rpc, default_conf)
telegram._updater = MagicMock() telegram._updater = MagicMock()
telegram._updater.bot = bot telegram._updater.bot = bot
return telegram return telegram

View File

@ -5,7 +5,7 @@ from unittest.mock import MagicMock
import pytest import pytest
from requests import RequestException from requests import RequestException
from freqtrade.rpc import RPCMessageType from freqtrade.rpc import RPC, RPCMessageType
from freqtrade.rpc.webhook import Webhook from freqtrade.rpc.webhook import Webhook
from freqtrade.strategy.interface import SellType from freqtrade.strategy.interface import SellType
from tests.conftest import get_patched_freqtradebot, log_has from tests.conftest import get_patched_freqtradebot, log_has
@ -45,7 +45,7 @@ def get_webhook_dict() -> dict:
def test__init__(mocker, default_conf): def test__init__(mocker, default_conf):
default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"} default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
assert webhook._config == default_conf assert webhook._config == default_conf
@ -53,7 +53,7 @@ def test_send_msg(default_conf, mocker):
default_conf["webhook"] = get_webhook_dict() default_conf["webhook"] = get_webhook_dict()
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
# Test buy # Test buy
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
@ -172,7 +172,7 @@ def test_exception_send_msg(default_conf, mocker, caplog):
default_conf["webhook"] = get_webhook_dict() default_conf["webhook"] = get_webhook_dict()
del default_conf["webhook"]["webhookbuy"] del default_conf["webhook"]["webhookbuy"]
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
webhook.send_msg({'type': RPCMessageType.BUY_NOTIFICATION}) webhook.send_msg({'type': RPCMessageType.BUY_NOTIFICATION})
assert log_has(f"Message type '{RPCMessageType.BUY_NOTIFICATION}' not configured for webhooks", assert log_has(f"Message type '{RPCMessageType.BUY_NOTIFICATION}' not configured for webhooks",
caplog) caplog)
@ -181,7 +181,7 @@ def test_exception_send_msg(default_conf, mocker, caplog):
default_conf["webhook"]["webhookbuy"]["value1"] = "{DEADBEEF:8f}" default_conf["webhook"]["webhookbuy"]["value1"] = "{DEADBEEF:8f}"
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
msg = { msg = {
'type': RPCMessageType.BUY_NOTIFICATION, 'type': RPCMessageType.BUY_NOTIFICATION,
'exchange': 'Bittrex', 'exchange': 'Bittrex',
@ -209,7 +209,7 @@ def test_exception_send_msg(default_conf, mocker, caplog):
def test__send_msg(default_conf, mocker, caplog): def test__send_msg(default_conf, mocker, caplog):
default_conf["webhook"] = get_webhook_dict() default_conf["webhook"] = get_webhook_dict()
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
msg = {'value1': 'DEADBEEF', msg = {'value1': 'DEADBEEF',
'value2': 'ALIVEBEEF', 'value2': 'ALIVEBEEF',
'value3': 'FREQTRADE'} 'value3': 'FREQTRADE'}