Merge pull request #4113 from freqtrade/ref/rpc

Refactor RPC dependency tree
This commit is contained in:
Matthias 2020-12-25 10:10:50 +01:00 committed by GitHub
commit 4b910426ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 161 additions and 134 deletions

View File

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

View File

@ -20,8 +20,7 @@ from freqtrade.__init__ import __version__
from freqtrade.constants import DATETIME_PRINT_FORMAT, USERPATH_STRATEGIES
from freqtrade.exceptions import OperationalException
from freqtrade.persistence import Trade
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.rpc.rpc import RPC, RPCException
from freqtrade.rpc.rpc import RPC, RPCException, RPCHandler
logger = logging.getLogger(__name__)
@ -79,7 +78,7 @@ def shutdown_session(exception=None):
Trade.session.remove()
class ApiServer(RPC):
class ApiServer(RPCHandler):
"""
This class runs api server and provides rpc.rpc functionality to it
@ -90,13 +89,14 @@ class ApiServer(RPC):
return (safe_str_cmp(username, self._config['api_server'].get('username')) and
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
:param freqtrade: Instance of a freqtrade bot
Init the api server, and init the super class RPCHandler
:param rpc: instance of RPC Helper class
:param config: Configuration object
:return: None
"""
super().__init__(freqtrade)
super().__init__(rpc, config)
self.app = Flask(__name__)
self._cors = CORS(self.app,
@ -117,9 +117,6 @@ class ApiServer(RPC):
# Register application handling
self.register_rest_rpc_urls()
if self._config.get('fiat_display_currency', None):
self._fiat_converter = CryptoToFiatConverter()
thread = threading.Thread(target=self.run, daemon=True)
thread.start()
@ -286,7 +283,7 @@ class ApiServer(RPC):
Handler for /start.
Starts TradeThread in bot if stopped.
"""
msg = self._rpc_start()
msg = self._rpc._rpc_start()
return jsonify(msg)
@require_login
@ -296,7 +293,7 @@ class ApiServer(RPC):
Handler for /stop.
Stops TradeThread in bot if running
"""
msg = self._rpc_stop()
msg = self._rpc._rpc_stop()
return jsonify(msg)
@require_login
@ -306,7 +303,7 @@ class ApiServer(RPC):
Handler for /stopbuy.
Sets max_open_trades to 0 and gracefully sells all open trades
"""
msg = self._rpc_stopbuy()
msg = self._rpc._rpc_stopbuy()
return jsonify(msg)
@rpc_catch_errors
@ -330,7 +327,7 @@ class ApiServer(RPC):
"""
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
@rpc_catch_errors
@ -339,7 +336,7 @@ class ApiServer(RPC):
Handler for /reload_config.
Triggers a config file reload
"""
msg = self._rpc_reload_config()
msg = self._rpc._rpc_reload_config()
return jsonify(msg)
@require_login
@ -349,7 +346,7 @@ class ApiServer(RPC):
Handler for /count.
Returns the number of trades running
"""
msg = self._rpc_count()
msg = self._rpc._rpc_count()
return jsonify(msg)
@require_login
@ -359,7 +356,7 @@ class ApiServer(RPC):
Handler for /locks.
Returns the currently active locks.
"""
return jsonify(self._rpc_locks())
return jsonify(self._rpc._rpc_locks())
@require_login
@rpc_catch_errors
@ -372,10 +369,10 @@ class ApiServer(RPC):
timescale = request.args.get('timescale', 7)
timescale = int(timescale)
stats = self._rpc_daily_profit(timescale,
self._config['stake_currency'],
self._config.get('fiat_display_currency', '')
)
stats = self._rpc._rpc_daily_profit(timescale,
self._config['stake_currency'],
self._config.get('fiat_display_currency', '')
)
return jsonify(stats)
@ -398,7 +395,7 @@ class ApiServer(RPC):
Returns information related to Edge.
:return: edge stats
"""
stats = self._rpc_edge()
stats = self._rpc._rpc_edge()
return jsonify(stats)
@ -412,9 +409,9 @@ class ApiServer(RPC):
:return: stats
"""
stats = self._rpc_trade_statistics(self._config['stake_currency'],
self._config.get('fiat_display_currency')
)
stats = self._rpc._rpc_trade_statistics(self._config['stake_currency'],
self._config.get('fiat_display_currency')
)
return jsonify(stats)
@ -426,7 +423,7 @@ class ApiServer(RPC):
Returns a Object with "durations" and "sell_reasons" as keys.
"""
stats = self._rpc_stats()
stats = self._rpc._rpc_stats()
return jsonify(stats)
@ -439,7 +436,7 @@ class ApiServer(RPC):
Returns a cumulative performance statistics
:return: stats
"""
stats = self._rpc_performance()
stats = self._rpc._rpc_performance()
return jsonify(stats)
@ -452,7 +449,7 @@ class ApiServer(RPC):
Returns the current status of the trades in json format
"""
try:
results = self._rpc_trade_status()
results = self._rpc._rpc_trade_status()
return jsonify(results)
except RPCException:
return jsonify([])
@ -465,8 +462,8 @@ class ApiServer(RPC):
Returns the current status of the trades in json format
"""
results = self._rpc_balance(self._config['stake_currency'],
self._config.get('fiat_display_currency', ''))
results = self._rpc._rpc_balance(self._config['stake_currency'],
self._config.get('fiat_display_currency', ''))
return jsonify(results)
@require_login
@ -478,7 +475,7 @@ class ApiServer(RPC):
Returns the X last trades in json format
"""
limit = int(request.args.get('limit', 0))
results = self._rpc_trade_history(limit)
results = self._rpc._rpc_trade_history(limit)
return jsonify(results)
@require_login
@ -491,7 +488,7 @@ class ApiServer(RPC):
param:
tradeid: Numeric trade-id assigned to the trade.
"""
result = self._rpc_delete(tradeid)
result = self._rpc._rpc_delete(tradeid)
return jsonify(result)
@require_login
@ -500,7 +497,7 @@ class ApiServer(RPC):
"""
Handler for /whitelist.
"""
results = self._rpc_whitelist()
results = self._rpc._rpc_whitelist()
return jsonify(results)
@require_login
@ -510,7 +507,7 @@ class ApiServer(RPC):
Handler for /blacklist.
"""
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)
@require_login
@ -523,7 +520,7 @@ class ApiServer(RPC):
price = request.json.get("price", None)
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:
return jsonify(trade.to_json())
else:
@ -536,7 +533,7 @@ class ApiServer(RPC):
Handler for /forcesell.
"""
tradeid = request.json.get("tradeid")
results = self._rpc_forcesell(tradeid)
results = self._rpc._rpc_forcesell(tradeid)
return jsonify(results)
@require_login
@ -558,7 +555,7 @@ class ApiServer(RPC):
if not pair or not timeframe:
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)
@require_login
@ -597,7 +594,7 @@ class ApiServer(RPC):
"""
Handler for /plot_config.
"""
return jsonify(self._rpc_plot_config())
return jsonify(self._rpc._rpc_plot_config())
@require_login
@rpc_catch_errors

View File

@ -65,21 +65,17 @@ class RPCException(Exception):
}
class RPC:
"""
RPC class can be used to have extra feature, like bot data, and access to DB data
"""
# Bind _fiat_converter if needed in each RPC handler
_fiat_converter: Optional[CryptoToFiatConverter] = None
class RPCHandler:
def __init__(self, freqtrade) -> None:
def __init__(self, rpc: 'RPC', config: Dict[str, Any]) -> None:
"""
Initializes all enabled rpc modules
:param freqtrade: Instance of a freqtrade bot
Initializes RPCHandlers
:param rpc: instance of RPC Helper class
:param config: Configuration object
:return: None
"""
self._freqtrade = freqtrade
self._config: Dict[str, Any] = freqtrade.config
self._rpc = rpc
self._config: Dict[str, Any] = config
@property
def name(self) -> str:
@ -94,6 +90,25 @@ class RPC:
def send_msg(self, msg: Dict[str, str]) -> None:
""" Sends a message to all registered rpc modules """
class RPC:
"""
RPC class can be used to have extra feature, like bot data, and access to DB data
"""
# Bind _fiat_converter if needed
_fiat_converter: Optional[CryptoToFiatConverter] = None
def __init__(self, freqtrade) -> None:
"""
Initializes all enabled rpc modules
:param freqtrade: Instance of a freqtrade bot
:return: None
"""
self._freqtrade = freqtrade
self._config: Dict[str, Any] = freqtrade.config
if self._config.get('fiat_display_currency', None):
self._fiat_converter = CryptoToFiatConverter()
@staticmethod
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
from typing import Any, Dict, List
from freqtrade.rpc import RPC, RPCMessageType
from freqtrade.rpc import RPC, RPCHandler, RPCMessageType
logger = logging.getLogger(__name__)
@ -16,25 +16,26 @@ class RPCManager:
"""
def __init__(self, freqtrade) -> None:
""" Initializes all enabled rpc modules """
self.registered_modules: List[RPC] = []
self.registered_modules: List[RPCHandler] = []
self._rpc = RPC(freqtrade)
config = freqtrade.config
# Enable telegram
if freqtrade.config.get('telegram', {}).get('enabled', False):
if config.get('telegram', {}).get('enabled', False):
logger.info('Enabling rpc.telegram ...')
from freqtrade.rpc.telegram import Telegram
self.registered_modules.append(Telegram(freqtrade))
self.registered_modules.append(Telegram(self._rpc, config))
# Enable Webhook
if freqtrade.config.get('webhook', {}).get('enabled', False):
if config.get('webhook', {}).get('enabled', False):
logger.info('Enabling rpc.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
if freqtrade.config.get('api_server', {}).get('enabled', False):
if config.get('api_server', {}).get('enabled', False):
logger.info('Enabling rpc.api_server')
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:
""" Stops all enabled rpc modules """

View File

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

View File

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

View File

@ -185,7 +185,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
fetch_ticker=ticker,
get_fee=fee,
)
del default_conf['fiat_display_currency']
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot)

View File

@ -13,6 +13,7 @@ from requests.auth import _basic_auth_str
from freqtrade.__init__ import __version__
from freqtrade.loggers import setup_logging, setup_logging_pre
from freqtrade.persistence import PairLocks, Trade
from freqtrade.rpc import RPC
from freqtrade.rpc.api_server import BASE_URI, ApiServer
from freqtrade.state import RunMode, State
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)
rpc = RPC(ftbot)
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock())
apiserver = ApiServer(ftbot)
apiserver = ApiServer(rpc, default_conf)
yield ftbot, apiserver.app.test_client()
# Cleanup ... ?
@ -179,8 +181,7 @@ def test_api__init__(default_conf, mocker):
}})
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock())
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf))
apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
assert apiserver._config == default_conf
@ -197,7 +198,7 @@ def test_api_run(default_conf, mocker, caplog):
server_mock = MagicMock()
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
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.make_server', MagicMock())
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf))
apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
apiserver.run()
stop_mock = MagicMock()
stop_mock.shutdown = MagicMock()

View File

@ -20,7 +20,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.loggers import setup_logging
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.state import RunMode, State
from freqtrade.strategy.interface import SellType
@ -32,8 +32,8 @@ class DummyCls(Telegram):
"""
Dummy class for testing the Telegram @authorized_only decorator
"""
def __init__(self, freqtrade) -> None:
super().__init__(freqtrade)
def __init__(self, rpc: RPC, config) -> None:
super().__init__(rpc, config)
self.state = {'called': False}
def _init(self):
@ -54,7 +54,7 @@ class DummyCls(Telegram):
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()
if mock:
mocker.patch.multiple(
@ -62,8 +62,10 @@ def get_telegram_testobject(mocker, default_conf, mock=True):
_init=MagicMock(),
_send_msg=msg_mock
)
ftbot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(ftbot)
if not ftbot:
ftbot = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(ftbot)
telegram = Telegram(rpc, default_conf)
return telegram, ftbot, msg_mock
@ -112,8 +114,10 @@ def test_authorized_only(default_conf, mocker, caplog, update) -> None:
default_conf['telegram']['enabled'] = False
bot = FreqtradeBot(default_conf)
rpc = RPC(bot)
dummy = DummyCls(rpc, default_conf)
patch_get_signal(bot, (True, False))
dummy = DummyCls(bot)
dummy.dummy_handler(update=update, context=MagicMock())
assert dummy.state['called'] is True
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
bot = FreqtradeBot(default_conf)
rpc = RPC(bot)
dummy = DummyCls(rpc, default_conf)
patch_get_signal(bot, (True, False))
dummy = DummyCls(bot)
dummy.dummy_handler(update=update, context=MagicMock())
assert dummy.state['called'] is False
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
bot = FreqtradeBot(default_conf)
rpc = RPC(bot)
dummy = DummyCls(rpc, default_conf)
patch_get_signal(bot, (True, False))
dummy = DummyCls(bot)
dummy.dummy_exception(update=update, context=MagicMock())
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"
status_table = MagicMock()
mocker.patch('freqtrade.rpc.telegram.Telegram._status_table', status_table)
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
'freqtrade.rpc.rpc.RPC',
_rpc_trade_status=MagicMock(return_value=[{
'trade_id': 1,
'pair': 'ETH/BTC',
@ -188,7 +197,6 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'open_order': '(limit buy rem=0.00000000)',
'is_open': True
}]),
_status_table=status_table,
)
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)
rpc = RPC(freqtradebot)
telegram = Telegram(rpc, default_conf)
patch_get_signal(freqtradebot, (True, False))
telegram = Telegram(freqtradebot)
# Create some test data
freqtradebot.enter_positions()
@ -698,8 +707,9 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
)
freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot)
telegram = Telegram(rpc, default_conf)
patch_get_signal(freqtradebot, (True, False))
telegram = Telegram(freqtradebot)
# Create some test data
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
freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot)
telegram = Telegram(rpc, default_conf)
patch_get_signal(freqtradebot, (True, False))
telegram = Telegram(freqtradebot)
# Create some test data
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)
old_convamount = telegram._fiat_converter.convert_amount
telegram._fiat_converter.convert_amount = lambda a, b, c: -24.812
old_convamount = telegram._rpc._fiat_converter.convert_amount
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
telegram.send_msg({
'type': RPCMessageType.SELL_NOTIFICATION,
'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'
'*Profit:* `-57.41%`')
# 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:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
old_convamount = telegram._fiat_converter.convert_amount
telegram._fiat_converter.convert_amount = lambda a, b, c: -24.812
old_convamount = telegram._rpc._fiat_converter.convert_amount
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
telegram.send_msg({
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
'exchange': 'Binance',
@ -1303,7 +1314,7 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: timeout')
# 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:
@ -1449,6 +1460,7 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
bot = MagicMock()
bot.send_message = MagicMock()
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(freqtradebot)
invalid_keys_list = [['/not_valid', '/profit'], ['/daily'], ['/alsoinvalid']]
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)
def init_telegram(freqtradebot):
telegram = Telegram(freqtradebot)
telegram = Telegram(rpc, default_conf)
telegram._updater = MagicMock()
telegram._updater.bot = bot
return telegram

View File

@ -5,7 +5,7 @@ from unittest.mock import MagicMock
import pytest
from requests import RequestException
from freqtrade.rpc import RPCMessageType
from freqtrade.rpc import RPC, RPCMessageType
from freqtrade.rpc.webhook import Webhook
from freqtrade.strategy.interface import SellType
from tests.conftest import get_patched_freqtradebot, log_has
@ -45,7 +45,7 @@ def get_webhook_dict() -> dict:
def test__init__(mocker, default_conf):
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
@ -53,7 +53,7 @@ def test_send_msg(default_conf, mocker):
default_conf["webhook"] = get_webhook_dict()
msg_mock = MagicMock()
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
msg_mock = MagicMock()
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()
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})
assert log_has(f"Message type '{RPCMessageType.BUY_NOTIFICATION}' not configured for webhooks",
caplog)
@ -181,7 +181,7 @@ def test_exception_send_msg(default_conf, mocker, caplog):
default_conf["webhook"]["webhookbuy"]["value1"] = "{DEADBEEF:8f}"
msg_mock = MagicMock()
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 = {
'type': RPCMessageType.BUY_NOTIFICATION,
'exchange': 'Bittrex',
@ -209,7 +209,7 @@ def test_exception_send_msg(default_conf, mocker, caplog):
def test__send_msg(default_conf, mocker, caplog):
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',
'value2': 'ALIVEBEEF',
'value3': 'FREQTRADE'}