From 99a4a6405280f085d13d9edaf6a293a13f373497 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Apr 2023 08:23:55 +0000 Subject: [PATCH 01/27] Bump python-telegram-bot from 13.15 to 20.2 Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 13.15 to 20.2. - [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases) - [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst) - [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v13.15...v20.2) --- updated-dependencies: - dependency-name: python-telegram-bot dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9d51852fc..a07ef8897 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ ccxt==3.0.75 cryptography==40.0.2 aiohttp==3.8.4 SQLAlchemy==2.0.10 -python-telegram-bot==13.15 +python-telegram-bot==20.2 arrow==1.2.3 cachetools==4.2.2 requests==2.28.2 From da261003df117f5d221b8f32ef9bb4a99dfe71ce Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Jan 2023 10:52:51 +0100 Subject: [PATCH 02/27] Fix telegram imports to match v20.0 --- freqtrade/rpc/telegram.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e99501cc0..9fb394b1e 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -17,11 +17,12 @@ from typing import Any, Callable, Dict, List, Optional, Union import arrow from tabulate import tabulate -from telegram import (MAX_MESSAGE_LENGTH, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, - KeyboardButton, ParseMode, ReplyKeyboardMarkup, Update) +from telegram import (CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, + ReplyKeyboardMarkup, Update) +from telegram.constants import MessageLimit, ParseMode from telegram.error import BadRequest, NetworkError, TelegramError from telegram.ext import CallbackContext, CallbackQueryHandler, CommandHandler, Updater -from telegram.utils.helpers import escape_markdown +from telegram.helpers import escape_markdown from freqtrade.__init__ import __version__ from freqtrade.constants import DUST_PER_COIN, Config @@ -33,6 +34,9 @@ from freqtrade.rpc import RPC, RPCException, RPCHandler from freqtrade.rpc.rpc_types import RPCSendMsg +MAX_MESSAGE_LENGTH = MessageLimit.MAX_TEXT_LENGTH + + logger = logging.getLogger(__name__) logger.debug('Included module rpc.telegram ...') From c37b7b77e42fdc574a6a5a9d8486a55b4bab0af4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Jan 2023 10:55:55 +0100 Subject: [PATCH 03/27] move telegram fixture to telegram file --- tests/conftest.py | 10 +--------- tests/rpc/test_rpc_telegram.py | 8 ++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2f2345d54..1c737b3aa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ import json import logging import re from copy import deepcopy -from datetime import datetime, timedelta +from datetime import timedelta from pathlib import Path from typing import Optional from unittest.mock import MagicMock, Mock, PropertyMock @@ -12,7 +12,6 @@ import arrow import numpy as np import pandas as pd import pytest -from telegram import Chat, Message, Update from freqtrade import constants from freqtrade.commands import Arguments @@ -550,13 +549,6 @@ def get_default_conf_usdt(testdatadir): return configuration -@pytest.fixture -def update(): - _update = Update(0) - _update.message = Message(0, datetime.utcnow(), Chat(0, 0)) - return _update - - @pytest.fixture def fee(): return MagicMock(return_value=0.0025) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 9b22b73c0..0da24dc4a 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -43,6 +43,14 @@ def default_conf(default_conf) -> dict: return default_conf +@pytest.fixture +def update(): + message = Message(0, datetime.utcnow(), Chat(0, 0)) + _update = Update(0, message=message) + + return _update + + class DummyCls(Telegram): """ Dummy class for testing the Telegram @authorized_only decorator From 57eed50acb5fde4343705176d881c3bff9bff18f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Jan 2023 11:05:12 +0100 Subject: [PATCH 04/27] Fix some test failures caused by v20 update --- tests/rpc/test_rpc_telegram.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 0da24dc4a..e70924a1d 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -159,8 +159,8 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: patch_exchange(mocker) caplog.set_level(logging.DEBUG) chat = Chat(0xdeadbeef, 0) - update = Update(randint(1, 100)) - update.message = Message(randint(1, 100), datetime.utcnow(), chat) + message = Message(randint(1, 100), datetime.utcnow(), chat) + update = Update(randint(1, 100), message=message) default_conf['telegram']['enabled'] = False bot = FreqtradeBot(default_conf) @@ -193,9 +193,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None def test_telegram_status(default_conf, update, mocker) -> None: - update.message.chat.id = "123" default_conf['telegram']['enabled'] = False - default_conf['telegram']['chat_id'] = "123" status_table = MagicMock() mocker.patch('freqtrade.rpc.telegram.Telegram._status_table', status_table) @@ -252,9 +250,7 @@ def test_telegram_status(default_conf, update, mocker) -> None: @pytest.mark.usefixtures("init_persistence") def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: - update.message.chat.id = "123" default_conf['telegram']['enabled'] = False - default_conf['telegram']['chat_id'] = "123" default_conf['position_adjustment_enable'] = True mocker.patch.multiple( EXMS, @@ -305,9 +301,7 @@ def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: @pytest.mark.usefixtures("init_persistence") def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None: - update.message.chat.id = "123" default_conf['telegram']['enabled'] = False - default_conf['telegram']['chat_id'] = "123" default_conf['position_adjustment_enable'] = True mocker.patch.multiple( EXMS, @@ -1277,7 +1271,6 @@ def test_force_enter_handle_exception(default_conf, update, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) - update.message.text = '/forcebuy ETH/Nonepair' telegram._force_enter(update=update, context=MagicMock(), order_side=SignalDirection.LONG) assert msg_mock.call_count == 1 From 68ac934929a48033e58c9ffaa36a7394ea8d6b8f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Jan 2023 11:29:29 +0100 Subject: [PATCH 05/27] Update command list to handle frozenSets --- freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc_telegram.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 9fb394b1e..25a75c433 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -235,7 +235,7 @@ class Telegram(RPCHandler): ) logger.info( 'rpc.telegram is listening for following commands: %s', - [h.command for h in handles] + [[x for x in sorted(h.commands)] for h in handles] ) def cleanup(self) -> None: diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index e70924a1d..7510255e9 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -115,14 +115,14 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " "['balance'], ['start'], ['stop'], " - "['forcesell', 'forceexit', 'fx'], ['forcebuy', 'forcelong'], ['forceshort'], " - "['trades'], ['delete'], ['coo', 'cancel_open_order'], ['performance'], " - "['buys', 'entries'], ['sells', 'exits'], ['mix_tags'], " + "['forceexit', 'forcesell', 'fx'], ['forcebuy', 'forcelong'], ['forceshort'], " + "['trades'], ['delete'], ['cancel_open_order', 'coo'], ['performance'], " + "['buys', 'entries'], ['exits', 'sells'], ['mix_tags'], " "['stats'], ['daily'], ['weekly'], ['monthly'], " - "['count'], ['locks'], ['unlock', 'delete_locks'], " - "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " + "['count'], ['locks'], ['delete_locks', 'unlock'], " + "['reload_conf', 'reload_config'], ['show_conf', 'show_config'], " "['stopbuy', 'stopentry'], ['whitelist'], ['blacklist'], " - "['blacklist_delete', 'bl_delete'], " + "['bl_delete', 'blacklist_delete'], " "['logs'], ['edge'], ['health'], ['help'], ['version'], ['marketdir']" "]") From 14b501a4f74a38d07e8fb88e677b6b55e4fce37a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 Jan 2023 13:49:25 +0100 Subject: [PATCH 06/27] Initial changes for telegram migration --- freqtrade/rpc/telegram.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 25a75c433..5a070bb2e 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -21,7 +21,7 @@ from telegram import (CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, Update) from telegram.constants import MessageLimit, ParseMode from telegram.error import BadRequest, NetworkError, TelegramError -from telegram.ext import CallbackContext, CallbackQueryHandler, CommandHandler, Updater +from telegram.ext import Application, CallbackContext, CallbackQueryHandler, CommandHandler, Updater from telegram.helpers import escape_markdown from freqtrade.__init__ import __version__ @@ -104,6 +104,7 @@ class Telegram(RPCHandler): super().__init__(rpc, config) self._updater: Updater + self._app: Application self._init_keyboard() self._init() @@ -162,8 +163,9 @@ class Telegram(RPCHandler): registers all known command handlers and starts polling for message updates """ - self._updater = Updater(token=self._config['telegram']['token'], workers=0, - use_context=True) + self._app = Application.builder().token(self._config['telegram']['token']).build + # self._updater = Updater(token=, workers=0, + # use_context=True) # Register command handler and start telegram message polling handles = [ @@ -222,12 +224,12 @@ class Telegram(RPCHandler): CallbackQueryHandler(self._force_enter_inline, pattern=r"\S+\/\S+"), ] for handle in handles: - self._updater.dispatcher.add_handler(handle) + self._app.add_handler(handle) for callback in callbacks: - self._updater.dispatcher.add_handler(callback) + self._app.add_handler(callback) - self._updater.start_polling( + self._app.run_polling( bootstrap_retries=-1, timeout=20, read_latency=60, # Assumed transmission latency @@ -244,7 +246,7 @@ class Telegram(RPCHandler): :return: None """ # This can take up to `timeout` from the call to `start_polling`. - self._updater.stop() + self._app.stop() def _exchange_from_msg(self, msg: Dict[str, Any]) -> str: """ From cb45689c1da939f7b28ea67edf41be2c28fb6e7e Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 14 Apr 2023 18:03:35 +0200 Subject: [PATCH 07/27] Small fixes to new telegram implementation --- freqtrade/rpc/telegram.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 5a070bb2e..d5edd0871 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -163,7 +163,7 @@ class Telegram(RPCHandler): registers all known command handlers and starts polling for message updates """ - self._app = Application.builder().token(self._config['telegram']['token']).build + self._app = Application.builder().token(self._config['telegram']['token']).build() # self._updater = Updater(token=, workers=0, # use_context=True) @@ -229,16 +229,16 @@ class Telegram(RPCHandler): for callback in callbacks: self._app.add_handler(callback) - self._app.run_polling( - bootstrap_retries=-1, - timeout=20, - read_latency=60, # Assumed transmission latency - drop_pending_updates=True, - ) logger.info( 'rpc.telegram is listening for following commands: %s', [[x for x in sorted(h.commands)] for h in handles] ) + self._app.run_polling( + bootstrap_retries=-1, + timeout=20, + # read_latency=60, # Assumed transmission latency + drop_pending_updates=True, + ) def cleanup(self) -> None: """ From 5134bf8ec382240480214eea6b29c12a1f8c612c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Apr 2023 09:46:51 +0200 Subject: [PATCH 08/27] Authorized-only and /version to async --- freqtrade/rpc/telegram.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index d5edd0871..99e6a9ba7 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -58,7 +58,7 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]: :return: decorated function """ - def wrapper(self, *args, **kwargs): + async def wrapper(self, *args, **kwargs): """ Decorator logic """ update = kwargs.get('update') or args[0] @@ -80,9 +80,9 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]: chat_id ) try: - return command_handler(self, *args, **kwargs) + return await command_handler(self, *args, **kwargs) except RPCException as e: - self._send_msg(str(e)) + await self._send_msg(str(e)) except BaseException: logger.exception('Exception occurred within Telegram module') finally: @@ -1589,7 +1589,7 @@ class Telegram(RPCHandler): self._send_msg(message) @authorized_only - def _version(self, update: Update, context: CallbackContext) -> None: + async def _version(self, update: Update, context: CallbackContext) -> None: """ Handler for /version. Show version information @@ -1602,7 +1602,7 @@ class Telegram(RPCHandler): if strategy_version is not None: version_string += f', *Strategy version: * `{strategy_version}`' - self._send_msg(version_string) + await self._send_msg(version_string) @authorized_only def _show_config(self, update: Update, context: CallbackContext) -> None: @@ -1680,12 +1680,12 @@ class Telegram(RPCHandler): except TelegramError as telegram_err: logger.warning('TelegramError: %s! Giving up on that message.', telegram_err.message) - def _send_msg(self, msg: str, parse_mode: str = ParseMode.MARKDOWN, - disable_notification: bool = False, - keyboard: Optional[List[List[InlineKeyboardButton]]] = None, - callback_path: str = "", - reload_able: bool = False, - query: Optional[CallbackQuery] = None) -> None: + async def _send_msg(self, msg: str, parse_mode: str = ParseMode.MARKDOWN, + disable_notification: bool = False, + keyboard: Optional[List[List[InlineKeyboardButton]]] = None, + callback_path: str = "", + reload_able: bool = False, + query: Optional[CallbackQuery] = None) -> None: """ Send given markdown message :param msg: message @@ -1708,7 +1708,7 @@ class Telegram(RPCHandler): reply_markup = ReplyKeyboardMarkup(self._keyboard, resize_keyboard=True) try: try: - self._updater.bot.send_message( + await self._app.bot.send_message( self._config['telegram']['chat_id'], text=msg, parse_mode=parse_mode, @@ -1722,7 +1722,7 @@ class Telegram(RPCHandler): 'Telegram NetworkError: %s! Trying one more time.', network_err.message ) - self._updater.bot.send_message( + await self._app.bot.send_message( self._config['telegram']['chat_id'], text=msg, parse_mode=parse_mode, From e7e6f719e4a769cef61e47b64dbdb8acbfce082f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Apr 2023 09:52:14 +0200 Subject: [PATCH 09/27] _update_msg to async --- freqtrade/rpc/telegram.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 99e6a9ba7..f85fd5e08 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -103,7 +103,6 @@ class Telegram(RPCHandler): """ super().__init__(rpc, config) - self._updater: Updater self._app: Application self._init_keyboard() self._init() @@ -164,8 +163,6 @@ class Telegram(RPCHandler): and starts polling for message updates """ self._app = Application.builder().token(self._config['telegram']['token']).build() - # self._updater = Updater(token=, workers=0, - # use_context=True) # Register command handler and start telegram message polling handles = [ @@ -1650,8 +1647,8 @@ class Telegram(RPCHandler): f"*Current state:* `{val['state']}`" ) - def _update_msg(self, query: CallbackQuery, msg: str, callback_path: str = "", - reload_able: bool = False, parse_mode: str = ParseMode.MARKDOWN) -> None: + async def _update_msg(self, query: CallbackQuery, msg: str, callback_path: str = "", + reload_able: bool = False, parse_mode: str = ParseMode.MARKDOWN) -> None: if reload_able: reply_markup = InlineKeyboardMarkup([ [InlineKeyboardButton("Refresh", callback_data=callback_path)], @@ -1665,7 +1662,7 @@ class Telegram(RPCHandler): message_id = query.message.message_id try: - self._updater.bot.edit_message_text( + await self._app.bot.edit_message_text( chat_id=chat_id, message_id=message_id, text=msg, @@ -1695,8 +1692,8 @@ class Telegram(RPCHandler): """ reply_markup: Union[InlineKeyboardMarkup, ReplyKeyboardMarkup] if query: - self._update_msg(query=query, msg=msg, parse_mode=parse_mode, - callback_path=callback_path, reload_able=reload_able) + await self._update_msg(query=query, msg=msg, parse_mode=parse_mode, + callback_path=callback_path, reload_able=reload_able) return if reload_able and self._config['telegram'].get('reload', True): reply_markup = InlineKeyboardMarkup([ From 54732b72fd99dcf0174db1ff0292b018ae7ababb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Apr 2023 13:10:14 +0200 Subject: [PATCH 10/27] Manage startup/teardown of telegram manually --- freqtrade/rpc/telegram.py | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index f85fd5e08..8202a03c2 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -3,6 +3,7 @@ """ This module manage Telegram communication """ +import asyncio import json import logging import re @@ -13,6 +14,7 @@ from functools import partial from html import escape from itertools import chain from math import isnan +from threading import Thread from typing import Any, Callable, Dict, List, Optional, Union import arrow @@ -104,8 +106,16 @@ class Telegram(RPCHandler): super().__init__(rpc, config) self._app: Application + self._loop: asyncio.AbstractEventLoop self._init_keyboard() - self._init() + self._start_thread() + + def _start_thread(self): + """ + Creates and starts the polling thread + """ + self._thread = Thread(target=self._init, name='FTTelegram') + self._thread.start() def _init_keyboard(self) -> None: """ @@ -161,7 +171,14 @@ class Telegram(RPCHandler): Initializes this module with the given config, registers all known command handlers and starts polling for message updates + Runs in a separate thread. """ + try: + self._loop = asyncio.get_running_loop() + except RuntimeError: + self._loop = asyncio.new_event_loop() + asyncio.set_event_loop(self._loop) + self._app = Application.builder().token(self._config['telegram']['token']).build() # Register command handler and start telegram message polling @@ -230,12 +247,27 @@ class Telegram(RPCHandler): 'rpc.telegram is listening for following commands: %s', [[x for x in sorted(h.commands)] for h in handles] ) - self._app.run_polling( + self._loop.run_until_complete(self._startup_telegram()) + + async def _startup_telegram(self) -> None: + await self._app.initialize() + await self._app.start() + await self._app.updater.start_polling( bootstrap_retries=-1, timeout=20, # read_latency=60, # Assumed transmission latency drop_pending_updates=True, + # stop_signals=[], # Necessary as we don't run on the main thread ) + while True: + await asyncio.sleep(10) + if not self._app.updater.running: + break + + async def _cleanup_telegram(self) -> None: + await self._app.updater.stop() + await self._app.stop() + await self._app.shutdown() def cleanup(self) -> None: """ @@ -243,7 +275,8 @@ class Telegram(RPCHandler): :return: None """ # This can take up to `timeout` from the call to `start_polling`. - self._app.stop() + asyncio.run_coroutine_threadsafe(self._cleanup_telegram(), self._loop) + self._thread.join() def _exchange_from_msg(self, msg: Dict[str, Any]) -> str: """ From 3d0e1d142f4cd03e9305b14f0d67f2d06f9e68b2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Apr 2023 13:24:12 +0200 Subject: [PATCH 11/27] Convert endpoints to async --- freqtrade/rpc/telegram.py | 241 +++++++++++++++++++------------------- 1 file changed, 121 insertions(+), 120 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 8202a03c2..c61e36116 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -23,7 +23,7 @@ from telegram import (CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, Update) from telegram.constants import MessageLimit, ParseMode from telegram.error import BadRequest, NetworkError, TelegramError -from telegram.ext import Application, CallbackContext, CallbackQueryHandler, CommandHandler, Updater +from telegram.ext import Application, CallbackContext, CallbackQueryHandler, CommandHandler from telegram.helpers import escape_markdown from freqtrade.__init__ import __version__ @@ -489,7 +489,9 @@ class Telegram(RPCHandler): message = self.compose_message(deepcopy(msg), msg_type) # type: ignore if message: - self._send_msg(message, disable_notification=(noti == 'silent')) + asyncio.run_coroutine_threadsafe( + self._send_msg(message, disable_notification=(noti == 'silent')), + self._loop) def _get_sell_emoji(self, msg): """ @@ -572,7 +574,7 @@ class Telegram(RPCHandler): return lines_detail @authorized_only - def _status(self, update: Update, context: CallbackContext) -> None: + async def _status(self, update: Update, context: CallbackContext) -> None: """ Handler for /status. Returns the current TradeThread status @@ -582,12 +584,12 @@ class Telegram(RPCHandler): """ if context.args and 'table' in context.args: - self._status_table(update, context) + await self._status_table(update, context) return else: - self._status_msg(update, context) + await self._status_msg(update, context) - def _status_msg(self, update: Update, context: CallbackContext) -> None: + async def _status_msg(self, update: Update, context: CallbackContext) -> None: """ handler for `/status` and `/status `. @@ -671,9 +673,9 @@ class Telegram(RPCHandler): lines_detail = self._prepare_order_details( r['orders'], r['quote_currency'], r['is_open']) lines.extend(lines_detail if lines_detail else "") - self.__send_status_msg(lines, r) + await self.__send_status_msg(lines, r) - def __send_status_msg(self, lines: List[str], r: Dict[str, Any]) -> None: + async def __send_status_msg(self, lines: List[str], r: Dict[str, Any]) -> None: """ Send status message. """ @@ -684,13 +686,13 @@ class Telegram(RPCHandler): if (len(msg) + len(line) + 1) < MAX_MESSAGE_LENGTH: msg += line + '\n' else: - self._send_msg(msg.format(**r)) + await self._send_msg(msg.format(**r)) msg = "*Trade ID:* `{trade_id}` - continued\n" + line + '\n' - self._send_msg(msg.format(**r)) + await self._send_msg(msg.format(**r)) @authorized_only - def _status_table(self, update: Update, context: CallbackContext) -> None: + async def _status_table(self, update: Update, context: CallbackContext) -> None: """ Handler for /status table. Returns the current TradeThread status in table format @@ -723,12 +725,11 @@ class Telegram(RPCHandler): # insert separators line between Total lines = message.split("\n") message = "\n".join(lines[:-1] + [lines[1]] + [lines[-1]]) - self._send_msg(f"
{message}
", parse_mode=ParseMode.HTML, - reload_able=True, callback_path="update_status_table", - query=update.callback_query) + await self._send_msg(f"
{message}
", parse_mode=ParseMode.HTML, + reload_able=True, callback_path="update_status_table", + query=update.callback_query) - @authorized_only - def _timeunit_stats(self, update: Update, context: CallbackContext, unit: str) -> None: + async def _timeunit_stats(self, update: Update, context: CallbackContext, unit: str) -> None: """ Handler for /daily Returns a daily profit (in BTC) over the last n days. @@ -775,11 +776,11 @@ class Telegram(RPCHandler): f'{val.message} Profit over the last {timescale} {val.message2}:\n' f'
{stats_tab}
' ) - self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True, - callback_path=val.callback, query=update.callback_query) + await self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True, + callback_path=val.callback, query=update.callback_query) @authorized_only - def _daily(self, update: Update, context: CallbackContext) -> None: + async def _daily(self, update: Update, context: CallbackContext) -> None: """ Handler for /daily Returns a daily profit (in BTC) over the last n days. @@ -787,10 +788,10 @@ class Telegram(RPCHandler): :param update: message update :return: None """ - self._timeunit_stats(update, context, 'days') + await self._timeunit_stats(update, context, 'days') @authorized_only - def _weekly(self, update: Update, context: CallbackContext) -> None: + async def _weekly(self, update: Update, context: CallbackContext) -> None: """ Handler for /weekly Returns a weekly profit (in BTC) over the last n weeks. @@ -798,10 +799,10 @@ class Telegram(RPCHandler): :param update: message update :return: None """ - self._timeunit_stats(update, context, 'weeks') + await self._timeunit_stats(update, context, 'weeks') @authorized_only - def _monthly(self, update: Update, context: CallbackContext) -> None: + async def _monthly(self, update: Update, context: CallbackContext) -> None: """ Handler for /monthly Returns a monthly profit (in BTC) over the last n months. @@ -809,10 +810,10 @@ class Telegram(RPCHandler): :param update: message update :return: None """ - self._timeunit_stats(update, context, 'months') + await self._timeunit_stats(update, context, 'months') @authorized_only - def _profit(self, update: Update, context: CallbackContext) -> None: + async def _profit(self, update: Update, context: CallbackContext) -> None: """ Handler for /profit. Returns a cumulative profit statistics. @@ -886,11 +887,11 @@ class Telegram(RPCHandler): f"*Max Drawdown:* `{stats['max_drawdown']:.2%} " f"({round_coin_value(stats['max_drawdown_abs'], stake_cur)})`" ) - self._send_msg(markdown_msg, reload_able=True, callback_path="update_profit", - query=update.callback_query) + await self._send_msg(markdown_msg, reload_able=True, callback_path="update_profit", + query=update.callback_query) @authorized_only - def _stats(self, update: Update, context: CallbackContext) -> None: + async def _stats(self, update: Update, context: CallbackContext) -> None: """ Handler for /stats Show stats of recent trades @@ -921,7 +922,7 @@ class Telegram(RPCHandler): headers=['Exit Reason', 'Exits', 'Wins', 'Losses'] ) if len(exit_reasons_tabulate) > 25: - self._send_msg(f"```\n{exit_reasons_msg}```", ParseMode.MARKDOWN) + await self._send_msg(f"```\n{exit_reasons_msg}```", ParseMode.MARKDOWN) exit_reasons_msg = '' durations = stats['durations'] @@ -936,10 +937,10 @@ class Telegram(RPCHandler): ) msg = (f"""```\n{exit_reasons_msg}```\n```\n{duration_msg}```""") - self._send_msg(msg, ParseMode.MARKDOWN) + await self._send_msg(msg, ParseMode.MARKDOWN) @authorized_only - def _balance(self, update: Update, context: CallbackContext) -> None: + async def _balance(self, update: Update, context: CallbackContext) -> None: """ Handler for /balance """ full_result = context.args and 'full' in context.args result = self._rpc._rpc_balance(self._config['stake_currency'], @@ -1017,11 +1018,11 @@ class Telegram(RPCHandler): f"\t`{result['stake']}: {total_stake}`{stake_improve}\n" f"\t`{result['symbol']}: {value}`{fiat_val}\n" ) - self._send_msg(output, reload_able=True, callback_path="update_balance", - query=update.callback_query) + await self._send_msg(output, reload_able=True, callback_path="update_balance", + query=update.callback_query) @authorized_only - def _start(self, update: Update, context: CallbackContext) -> None: + async def _start(self, update: Update, context: CallbackContext) -> None: """ Handler for /start. Starts TradeThread @@ -1030,10 +1031,10 @@ class Telegram(RPCHandler): :return: None """ msg = self._rpc._rpc_start() - self._send_msg(f"Status: `{msg['status']}`") + await self._send_msg(f"Status: `{msg['status']}`") @authorized_only - def _stop(self, update: Update, context: CallbackContext) -> None: + async def _stop(self, update: Update, context: CallbackContext) -> None: """ Handler for /stop. Stops TradeThread @@ -1042,10 +1043,10 @@ class Telegram(RPCHandler): :return: None """ msg = self._rpc._rpc_stop() - self._send_msg(f"Status: `{msg['status']}`") + await self._send_msg(f"Status: `{msg['status']}`") @authorized_only - def _reload_config(self, update: Update, context: CallbackContext) -> None: + async def _reload_config(self, update: Update, context: CallbackContext) -> None: """ Handler for /reload_config. Triggers a config file reload @@ -1054,10 +1055,10 @@ class Telegram(RPCHandler): :return: None """ msg = self._rpc._rpc_reload_config() - self._send_msg(f"Status: `{msg['status']}`") + await self._send_msg(f"Status: `{msg['status']}`") @authorized_only - def _stopentry(self, update: Update, context: CallbackContext) -> None: + async def _stopentry(self, update: Update, context: CallbackContext) -> None: """ Handler for /stop_buy. Sets max_open_trades to 0 and gracefully sells all open trades @@ -1066,10 +1067,10 @@ class Telegram(RPCHandler): :return: None """ msg = self._rpc._rpc_stopentry() - self._send_msg(f"Status: `{msg['status']}`") + await self._send_msg(f"Status: `{msg['status']}`") @authorized_only - def _force_exit(self, update: Update, context: CallbackContext) -> None: + async def _force_exit(self, update: Update, context: CallbackContext) -> None: """ Handler for /forceexit . Sells the given trade at current price @@ -1100,51 +1101,51 @@ class Telegram(RPCHandler): buttons_aligned.append([InlineKeyboardButton( text='Cancel', callback_data='force_exit__cancel')]) - self._send_msg(msg="Which trade?", keyboard=buttons_aligned) + await self._send_msg(msg="Which trade?", keyboard=buttons_aligned) - def _force_exit_action(self, trade_id): + async def _force_exit_action(self, trade_id): if trade_id != 'cancel': try: self._rpc._rpc_force_exit(trade_id) except RPCException as e: - self._send_msg(str(e)) + await self._send_msg(str(e)) - def _force_exit_inline(self, update: Update, _: CallbackContext) -> None: + async def _force_exit_inline(self, update: Update, _: CallbackContext) -> None: if update.callback_query: query = update.callback_query if query.data and '__' in query.data: # Input data is "force_exit__" trade_id = query.data.split("__")[1].split(' ')[0] if trade_id == 'cancel': - query.answer() - query.edit_message_text(text="Force exit canceled.") + await query.answer() + await query.edit_message_text(text="Force exit canceled.") return trade: Optional[Trade] = Trade.get_trades(trade_filter=Trade.id == trade_id).first() query.answer() if trade: - query.edit_message_text( + await query.edit_message_text( text=f"Manually exiting Trade #{trade_id}, {trade.pair}") - self._force_exit_action(trade_id) + await self._force_exit_action(trade_id) else: - query.edit_message_text(text=f"Trade {trade_id} not found.") + await query.edit_message_text(text=f"Trade {trade_id} not found.") - def _force_enter_action(self, pair, price: Optional[float], order_side: SignalDirection): + async def _force_enter_action(self, pair, price: Optional[float], order_side: SignalDirection): if pair != 'cancel': try: - self._rpc._rpc_force_entry(pair, price, order_side=order_side) + await self._rpc._rpc_force_entry(pair, price, order_side=order_side) except RPCException as e: logger.exception("Forcebuy error!") - self._send_msg(str(e), ParseMode.HTML) + await self._send_msg(str(e), ParseMode.HTML) - def _force_enter_inline(self, update: Update, _: CallbackContext) -> None: + async def _force_enter_inline(self, update: Update, _: CallbackContext) -> None: if update.callback_query: query = update.callback_query if query.data and '_||_' in query.data: pair, side = query.data.split('_||_') order_side = SignalDirection(side) - query.answer() - query.edit_message_text(text=f"Manually entering {order_side} for {pair}") - self._force_enter_action(pair, None, order_side) + await query.answer() + await query.edit_message_text(text=f"Manually entering {order_side} for {pair}") + await self._force_enter_action(pair, None, order_side) @staticmethod def _layout_inline_keyboard( @@ -1157,7 +1158,7 @@ class Telegram(RPCHandler): return [buttons[i:i + cols] for i in range(0, len(buttons), cols)] @authorized_only - def _force_enter( + async def _force_enter( self, update: Update, context: CallbackContext, order_side: SignalDirection) -> None: """ Handler for /forcelong and `/forceshort @@ -1169,7 +1170,7 @@ class Telegram(RPCHandler): if context.args: pair = context.args[0] price = float(context.args[1]) if len(context.args) > 1 else None - self._force_enter_action(pair, price, order_side) + await self._force_enter_action(pair, price, order_side) else: whitelist = self._rpc._rpc_whitelist()['whitelist'] pair_buttons = [ @@ -1179,12 +1180,12 @@ class Telegram(RPCHandler): buttons_aligned = self._layout_inline_keyboard(pair_buttons) buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) - self._send_msg(msg="Which pair?", - keyboard=buttons_aligned, - query=update.callback_query) + await self._send_msg(msg="Which pair?", + keyboard=buttons_aligned, + query=update.callback_query) @authorized_only - def _trades(self, update: Update, context: CallbackContext) -> None: + async def _trades(self, update: Update, context: CallbackContext) -> None: """ Handler for /trades Returns last n recent trades. @@ -1213,10 +1214,10 @@ class Telegram(RPCHandler): tablefmt='simple') message = (f"{min(trades['trades_count'], nrecent)} recent trades:\n" + (f"
{trades_tab}
" if trades['trades_count'] > 0 else '')) - self._send_msg(message, parse_mode=ParseMode.HTML) + await self._send_msg(message, parse_mode=ParseMode.HTML) @authorized_only - def _delete_trade(self, update: Update, context: CallbackContext) -> None: + async def _delete_trade(self, update: Update, context: CallbackContext) -> None: """ Handler for /delete . Delete the given trade @@ -1228,13 +1229,13 @@ class Telegram(RPCHandler): raise RPCException("Trade-id not set.") trade_id = int(context.args[0]) msg = self._rpc._rpc_delete(trade_id) - self._send_msg( + await self._send_msg( f"`{msg['result_msg']}`\n" 'Please make sure to take care of this asset on the exchange manually.' ) @authorized_only - def _cancel_open_order(self, update: Update, context: CallbackContext) -> None: + async def _cancel_open_order(self, update: Update, context: CallbackContext) -> None: """ Handler for /cancel_open_order . Cancel open order for tradeid @@ -1246,10 +1247,10 @@ class Telegram(RPCHandler): raise RPCException("Trade-id not set.") trade_id = int(context.args[0]) self._rpc._rpc_cancel_open_order(trade_id) - self._send_msg('Open order canceled.') + await self._send_msg('Open order canceled.') @authorized_only - def _performance(self, update: Update, context: CallbackContext) -> None: + async def _performance(self, update: Update, context: CallbackContext) -> None: """ Handler for /performance. Shows a performance statistic from finished trades @@ -1272,12 +1273,12 @@ class Telegram(RPCHandler): else: output += stat_line - self._send_msg(output, parse_mode=ParseMode.HTML, - reload_able=True, callback_path="update_performance", - query=update.callback_query) + await self._send_msg(output, parse_mode=ParseMode.HTML, + reload_able=True, callback_path="update_performance", + query=update.callback_query) @authorized_only - def _enter_tag_performance(self, update: Update, context: CallbackContext) -> None: + async def _enter_tag_performance(self, update: Update, context: CallbackContext) -> None: """ Handler for /buys PAIR . Shows a performance statistic from finished trades @@ -1304,12 +1305,12 @@ class Telegram(RPCHandler): else: output += stat_line - self._send_msg(output, parse_mode=ParseMode.HTML, - reload_able=True, callback_path="update_enter_tag_performance", - query=update.callback_query) + await self._send_msg(output, parse_mode=ParseMode.HTML, + reload_able=True, callback_path="update_enter_tag_performance", + query=update.callback_query) @authorized_only - def _exit_reason_performance(self, update: Update, context: CallbackContext) -> None: + async def _exit_reason_performance(self, update: Update, context: CallbackContext) -> None: """ Handler for /sells. Shows a performance statistic from finished trades @@ -1331,17 +1332,17 @@ class Telegram(RPCHandler): f"({trade['count']})\n") if len(output + stat_line) >= MAX_MESSAGE_LENGTH: - self._send_msg(output, parse_mode=ParseMode.HTML) + await self._send_msg(output, parse_mode=ParseMode.HTML) output = stat_line else: output += stat_line - self._send_msg(output, parse_mode=ParseMode.HTML, - reload_able=True, callback_path="update_exit_reason_performance", - query=update.callback_query) + await self._send_msg(output, parse_mode=ParseMode.HTML, + reload_able=True, callback_path="update_exit_reason_performance", + query=update.callback_query) @authorized_only - def _mix_tag_performance(self, update: Update, context: CallbackContext) -> None: + async def _mix_tag_performance(self, update: Update, context: CallbackContext) -> None: """ Handler for /mix_tags. Shows a performance statistic from finished trades @@ -1363,17 +1364,17 @@ class Telegram(RPCHandler): f"({trade['count']})\n") if len(output + stat_line) >= MAX_MESSAGE_LENGTH: - self._send_msg(output, parse_mode=ParseMode.HTML) + await self._send_msg(output, parse_mode=ParseMode.HTML) output = stat_line else: output += stat_line - self._send_msg(output, parse_mode=ParseMode.HTML, - reload_able=True, callback_path="update_mix_tag_performance", - query=update.callback_query) + await self._send_msg(output, parse_mode=ParseMode.HTML, + reload_able=True, callback_path="update_mix_tag_performance", + query=update.callback_query) @authorized_only - def _count(self, update: Update, context: CallbackContext) -> None: + async def _count(self, update: Update, context: CallbackContext) -> None: """ Handler for /count. Returns the number of trades running @@ -1387,19 +1388,19 @@ class Telegram(RPCHandler): tablefmt='simple') message = f"
{message}
" logger.debug(message) - self._send_msg(message, parse_mode=ParseMode.HTML, - reload_able=True, callback_path="update_count", - query=update.callback_query) + await self._send_msg(message, parse_mode=ParseMode.HTML, + reload_able=True, callback_path="update_count", + query=update.callback_query) @authorized_only - def _locks(self, update: Update, context: CallbackContext) -> None: + async def _locks(self, update: Update, context: CallbackContext) -> None: """ Handler for /locks. Returns the currently active locks """ rpc_locks = self._rpc._rpc_locks() if not rpc_locks['locks']: - self._send_msg('No active locks.', parse_mode=ParseMode.HTML) + await self._send_msg('No active locks.', parse_mode=ParseMode.HTML) for locks in chunks(rpc_locks['locks'], 25): message = tabulate([[ @@ -1411,10 +1412,10 @@ class Telegram(RPCHandler): tablefmt='simple') message = f"
{escape(message)}
" logger.debug(message) - self._send_msg(message, parse_mode=ParseMode.HTML) + await self._send_msg(message, parse_mode=ParseMode.HTML) @authorized_only - def _delete_locks(self, update: Update, context: CallbackContext) -> None: + async def _delete_locks(self, update: Update, context: CallbackContext) -> None: """ Handler for /delete_locks. Returns the currently active locks @@ -1429,10 +1430,10 @@ class Telegram(RPCHandler): pair = arg self._rpc._rpc_delete_lock(lockid=lockid, pair=pair) - self._locks(update, context) + await self._locks(update, context) @authorized_only - def _whitelist(self, update: Update, context: CallbackContext) -> None: + async def _whitelist(self, update: Update, context: CallbackContext) -> None: """ Handler for /whitelist Shows the currently active whitelist @@ -1449,39 +1450,39 @@ class Telegram(RPCHandler): message += f"`{', '.join(whitelist['whitelist'])}`" logger.debug(message) - self._send_msg(message) + await self._send_msg(message) @authorized_only - def _blacklist(self, update: Update, context: CallbackContext) -> None: + async def _blacklist(self, update: Update, context: CallbackContext) -> None: """ Handler for /blacklist Shows the currently active blacklist """ - self.send_blacklist_msg(self._rpc._rpc_blacklist(context.args)) + await self.send_blacklist_msg(self._rpc._rpc_blacklist(context.args)) - def send_blacklist_msg(self, blacklist: Dict): + async def send_blacklist_msg(self, blacklist: Dict): errmsgs = [] for pair, error in blacklist['errors'].items(): errmsgs.append(f"Error: {error['error_msg']}") if errmsgs: - self._send_msg('\n'.join(errmsgs)) + await self._send_msg('\n'.join(errmsgs)) message = f"Blacklist contains {blacklist['length']} pairs\n" message += f"`{', '.join(blacklist['blacklist'])}`" logger.debug(message) - self._send_msg(message) + await self._send_msg(message) @authorized_only - def _blacklist_delete(self, update: Update, context: CallbackContext) -> None: + async def _blacklist_delete(self, update: Update, context: CallbackContext) -> None: """ Handler for /bl_delete Deletes pair(s) from current blacklist """ - self.send_blacklist_msg(self._rpc._rpc_blacklist_delete(context.args or [])) + await self.send_blacklist_msg(self._rpc._rpc_blacklist_delete(context.args or [])) @authorized_only - def _logs(self, update: Update, context: CallbackContext) -> None: + async def _logs(self, update: Update, context: CallbackContext) -> None: """ Handler for /logs Shows the latest logs @@ -1500,17 +1501,17 @@ class Telegram(RPCHandler): escape_markdown(logrec[4], version=2)) if len(msgs + msg) + 10 >= MAX_MESSAGE_LENGTH: # Send message immediately if it would become too long - self._send_msg(msgs, parse_mode=ParseMode.MARKDOWN_V2) + await self._send_msg(msgs, parse_mode=ParseMode.MARKDOWN_V2) msgs = msg + '\n' else: # Append message to messages to send msgs += msg + '\n' if msgs: - self._send_msg(msgs, parse_mode=ParseMode.MARKDOWN_V2) + await self._send_msg(msgs, parse_mode=ParseMode.MARKDOWN_V2) @authorized_only - def _edge(self, update: Update, context: CallbackContext) -> None: + async def _edge(self, update: Update, context: CallbackContext) -> None: """ Handler for /edge Shows information related to Edge @@ -1518,17 +1519,17 @@ class Telegram(RPCHandler): edge_pairs = self._rpc._rpc_edge() if not edge_pairs: message = 'Edge only validated following pairs:' - self._send_msg(message, parse_mode=ParseMode.HTML) + await self._send_msg(message, parse_mode=ParseMode.HTML) for chunk in chunks(edge_pairs, 25): edge_pairs_tab = tabulate(chunk, headers='keys', tablefmt='simple') message = (f'Edge only validated following pairs:\n' f'
{edge_pairs_tab}
') - self._send_msg(message, parse_mode=ParseMode.HTML) + await self._send_msg(message, parse_mode=ParseMode.HTML) @authorized_only - def _help(self, update: Update, context: CallbackContext) -> None: + async def _help(self, update: Update, context: CallbackContext) -> None: """ Handler for /help. Show commands of the bot @@ -1606,17 +1607,17 @@ class Telegram(RPCHandler): "*/version:* `Show version`" ) - self._send_msg(message, parse_mode=ParseMode.MARKDOWN) + await self._send_msg(message, parse_mode=ParseMode.MARKDOWN) @authorized_only - def _health(self, update: Update, context: CallbackContext) -> None: + async def _health(self, update: Update, context: CallbackContext) -> None: """ Handler for /health Shows the last process timestamp """ health = self._rpc.health() message = f"Last process: `{health['last_process_loc']}`" - self._send_msg(message) + await self._send_msg(message) @authorized_only async def _version(self, update: Update, context: CallbackContext) -> None: @@ -1635,7 +1636,7 @@ class Telegram(RPCHandler): await self._send_msg(version_string) @authorized_only - def _show_config(self, update: Update, context: CallbackContext) -> None: + async def _show_config(self, update: Update, context: CallbackContext) -> None: """ Handler for /show_config. Show config information information @@ -1664,7 +1665,7 @@ class Telegram(RPCHandler): else: pa_info = "*Position adjustment:* Off\n" - self._send_msg( + await self._send_msg( f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n" f"*Exchange:* `{val['exchange']}`\n" f"*Market: * `{val['trading_mode']}`\n" @@ -1766,7 +1767,7 @@ class Telegram(RPCHandler): ) @authorized_only - def _changemarketdir(self, update: Update, context: CallbackContext) -> None: + async def _changemarketdir(self, update: Update, context: CallbackContext) -> None: """ Handler for /marketdir. Updates the bot's market_direction @@ -1789,14 +1790,14 @@ class Telegram(RPCHandler): if new_market_dir is not None: self._rpc._update_market_direction(new_market_dir) - self._send_msg("Successfully updated market direction" + await self._send_msg("Successfully updated market direction" f" from *{old_market_dir}* to *{new_market_dir}*.") else: raise RPCException("Invalid market direction provided. \n" "Valid market directions: *long, short, even, none*") elif context.args is not None and len(context.args) == 0: old_market_dir = self._rpc._get_market_direction() - self._send_msg(f"Currently set market direction: *{old_market_dir}*") + await self._send_msg(f"Currently set market direction: *{old_market_dir}*") else: raise RPCException("Invalid usage of command /marketdir. \n" "Usage: */marketdir [short | long | even | none]*") From b1367ac46fbaddccd7d45cd0f4389f4617431146 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Apr 2023 13:29:42 +0200 Subject: [PATCH 12/27] Update decorator typehint --- freqtrade/rpc/telegram.py | 42 ++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index c61e36116..7f3c79194 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -15,7 +15,7 @@ from html import escape from itertools import chain from math import isnan from threading import Thread -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable, Coroutine, Dict, List, Optional, Union import arrow from tabulate import tabulate @@ -53,7 +53,7 @@ class TimeunitMappings: default: int -def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]: +def authorized_only(command_handler: Callable[..., Coroutine[Any, Any, None]]): """ Decorator to check if the message comes from the correct chat_id :param command_handler: Telegram CommandHandler @@ -252,20 +252,22 @@ class Telegram(RPCHandler): async def _startup_telegram(self) -> None: await self._app.initialize() await self._app.start() - await self._app.updater.start_polling( - bootstrap_retries=-1, - timeout=20, - # read_latency=60, # Assumed transmission latency - drop_pending_updates=True, - # stop_signals=[], # Necessary as we don't run on the main thread - ) - while True: - await asyncio.sleep(10) - if not self._app.updater.running: - break + if self._app.updater: + await self._app.updater.start_polling( + bootstrap_retries=-1, + timeout=20, + # read_latency=60, # Assumed transmission latency + drop_pending_updates=True, + # stop_signals=[], # Necessary as we don't run on the main thread + ) + while True: + await asyncio.sleep(10) + if not self._app.updater.running: + break async def _cleanup_telegram(self) -> None: - await self._app.updater.stop() + if self._app.updater: + await self._app.updater.stop() await self._app.stop() await self._app.shutdown() @@ -994,7 +996,7 @@ class Telegram(RPCHandler): # Handle overflowing message length if len(output + curr_output) >= MAX_MESSAGE_LENGTH: - self._send_msg(output) + await self._send_msg(output) output = curr_output else: output += curr_output @@ -1088,7 +1090,7 @@ class Telegram(RPCHandler): statlist, _, _ = self._rpc._rpc_status_table( self._config['stake_currency'], fiat_currency) except RPCException: - self._send_msg(msg='No open trade found.') + await self._send_msg(msg='No open trade found.') return trades = [] for trade in statlist: @@ -1121,7 +1123,7 @@ class Telegram(RPCHandler): await query.edit_message_text(text="Force exit canceled.") return trade: Optional[Trade] = Trade.get_trades(trade_filter=Trade.id == trade_id).first() - query.answer() + await query.answer() if trade: await query.edit_message_text( text=f"Manually exiting Trade #{trade_id}, {trade.pair}") @@ -1132,7 +1134,7 @@ class Telegram(RPCHandler): async def _force_enter_action(self, pair, price: Optional[float], order_side: SignalDirection): if pair != 'cancel': try: - await self._rpc._rpc_force_entry(pair, price, order_side=order_side) + self._rpc._rpc_force_entry(pair, price, order_side=order_side) except RPCException as e: logger.exception("Forcebuy error!") await self._send_msg(str(e), ParseMode.HTML) @@ -1268,7 +1270,7 @@ class Telegram(RPCHandler): f"({trade['count']})\n") if len(output + stat_line) >= MAX_MESSAGE_LENGTH: - self._send_msg(output, parse_mode=ParseMode.HTML) + await self._send_msg(output, parse_mode=ParseMode.HTML) output = stat_line else: output += stat_line @@ -1300,7 +1302,7 @@ class Telegram(RPCHandler): f"({trade['count']})\n") if len(output + stat_line) >= MAX_MESSAGE_LENGTH: - self._send_msg(output, parse_mode=ParseMode.HTML) + await self._send_msg(output, parse_mode=ParseMode.HTML) output = stat_line else: output += stat_line From 914d7350faca31f597fdb189d31f0336eb590ee4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Apr 2023 13:41:00 +0200 Subject: [PATCH 13/27] Update mocks in apimanager tests --- tests/rpc/test_rpc_apiserver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index e045bf487..8123e4689 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -283,7 +283,7 @@ def test_api__init__(default_conf, mocker): "username": "TestUser", "password": "testPass", }}) - mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram._init') mocker.patch('freqtrade.rpc.api_server.webserver.ApiServer.start_api', MagicMock()) apiserver = ApiServer(default_conf) apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf))) @@ -341,7 +341,7 @@ def test_api_run(default_conf, mocker, caplog): "username": "TestUser", "password": "testPass", }}) - mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram._init') server_inst_mock = MagicMock() server_inst_mock.run_in_thread = MagicMock() @@ -419,7 +419,7 @@ def test_api_cleanup(default_conf, mocker, caplog): "username": "TestUser", "password": "testPass", }}) - mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) + mocker.patch('freqtrade.rpc.telegram.Telegram._init') server_mock = MagicMock() server_mock.cleanup = MagicMock() @@ -1877,7 +1877,7 @@ def test_api_ws_send_msg(default_conf, mocker, caplog): "password": _TEST_PASS, "ws_token": _TEST_WS_TOKEN }}) - mocker.patch('freqtrade.rpc.telegram.Updater') + mocker.patch('freqtrade.rpc.telegram.Telegram._init') mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api') apiserver = ApiServer(default_conf) apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf))) From fb56889b43f50bc7bb1e8ad55afb4546f935c020 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Apr 2023 14:15:38 +0200 Subject: [PATCH 14/27] Update a few tests ... --- freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc_telegram.py | 201 ++++++++++++++++----------------- 2 files changed, 101 insertions(+), 102 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 7f3c79194..192e2b6d7 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -177,7 +177,7 @@ class Telegram(RPCHandler): self._loop = asyncio.get_running_loop() except RuntimeError: self._loop = asyncio.new_event_loop() - asyncio.set_event_loop(self._loop) + asyncio.set_event_loop(self._loop) self._app = Application.builder().token(self._config['telegram']['token']).build() diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 7510255e9..5b7950e4c 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -8,7 +8,7 @@ from datetime import datetime, timedelta, timezone from functools import reduce from random import choice, randint from string import ascii_uppercase -from unittest.mock import ANY, MagicMock +from unittest.mock import ANY, AsyncMock, MagicMock import arrow import pytest @@ -79,12 +79,13 @@ class DummyCls(Telegram): def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None): - msg_mock = MagicMock() + msg_mock = AsyncMock() if mock: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), - _send_msg=msg_mock + _send_msg=msg_mock, + _start_thread=MagicMock(), ) if not ftbot: ftbot = get_patched_freqtradebot(mocker, default_conf) @@ -95,7 +96,6 @@ def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None): def test_telegram__init__(default_conf, mocker) -> None: - mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) telegram, _, _ = get_telegram_testobject(mocker, default_conf) @@ -192,7 +192,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None assert log_has('Exception occurred within Telegram module', caplog) -def test_telegram_status(default_conf, update, mocker) -> None: +async def test_telegram_status(default_conf, update, mocker) -> None: default_conf['telegram']['enabled'] = False status_table = MagicMock() @@ -238,18 +238,18 @@ def test_telegram_status(default_conf, update, mocker) -> None: telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) - telegram._status(update=update, context=MagicMock()) + await telegram._status(update=update, context=MagicMock()) assert msg_mock.call_count == 1 context = MagicMock() # /status table context.args = ["table"] - telegram._status(update=update, context=context) + await telegram._status(update=update, context=context) assert status_table.call_count == 1 @pytest.mark.usefixtures("init_persistence") -def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: +async def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: default_conf['telegram']['enabled'] = False default_conf['position_adjustment_enable'] = True mocker.patch.multiple( @@ -288,7 +288,7 @@ def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: trade.recalc_trade_from_orders() Trade.commit() - telegram._status(update=update, context=MagicMock()) + await telegram._status(update=update, context=MagicMock()) assert msg_mock.call_count == 4 msg = msg_mock.call_args_list[0][0][0] assert re.search(r'Number of Entries.*2', msg) @@ -300,8 +300,7 @@ def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: @pytest.mark.usefixtures("init_persistence") -def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None: - default_conf['telegram']['enabled'] = False +async def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None: default_conf['position_adjustment_enable'] = True mocker.patch.multiple( EXMS, @@ -315,7 +314,7 @@ def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None trade = Trade.get_trades([Trade.is_open.is_(False)]).first() context = MagicMock() context.args = [str(trade.id)] - telegram._status(update=update, context=context) + await telegram._status(update=update, context=context) assert msg_mock.call_count == 1 msg = msg_mock.call_args_list[0][0][0] assert re.search(r'Close Date:', msg) @@ -749,7 +748,7 @@ def test_telegram_profit_handle( @pytest.mark.parametrize('is_short', [True, False]) -def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> None: +async def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch.multiple( EXMS, @@ -759,7 +758,7 @@ def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) - telegram._stats(update=update, context=MagicMock()) + await telegram._stats(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'No trades yet.' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() @@ -767,7 +766,7 @@ def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> # Create some test data create_mock_trades(fee, is_short=is_short) - telegram._stats(update=update, context=MagicMock()) + await telegram._stats(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'Exit Reason' in msg_mock.call_args_list[-1][0][0] assert 'ROI' in msg_mock.call_args_list[-1][0][0] @@ -775,7 +774,7 @@ def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> msg_mock.reset_mock() -def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tickers) -> None: +async def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tickers) -> None: default_conf['dry_run'] = False mocker.patch(f'{EXMS}.get_balances', return_value=rpc_balance) mocker.patch(f'{EXMS}.get_tickers', tickers) @@ -784,10 +783,10 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) - telegram._balance(update=update, context=MagicMock()) + await telegram._balance(update=update, context=MagicMock()) context = MagicMock() context.args = ["full"] - telegram._balance(update=update, context=context) + await telegram._balance(update=update, context=context) result = msg_mock.call_args_list[0][0][0] result_full = msg_mock.call_args_list[1][0][0] assert msg_mock.call_count == 2 @@ -809,7 +808,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick assert '*Estimated Value (Bot managed assets only)*:' in result -def test_balance_handle_empty_response(default_conf, update, mocker) -> None: +async def test_balance_handle_empty_response(default_conf, update, mocker) -> None: default_conf['dry_run'] = False mocker.patch(f'{EXMS}.get_balances', return_value={}) @@ -817,26 +816,26 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None: patch_get_signal(freqtradebot) freqtradebot.config['dry_run'] = False - telegram._balance(update=update, context=MagicMock()) + await telegram._balance(update=update, context=MagicMock()) result = msg_mock.call_args_list[0][0][0] assert msg_mock.call_count == 1 assert 'Starting capital: `0 BTC' in result -def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None: +async def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None: mocker.patch(f'{EXMS}.get_balances', return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) - telegram._balance(update=update, context=MagicMock()) + await telegram._balance(update=update, context=MagicMock()) result = msg_mock.call_args_list[0][0][0] assert msg_mock.call_count == 1 assert "*Warning:* Simulated balances in Dry Mode." in result assert "Starting capital: `1000 BTC`" in result -def test_balance_handle_too_large_response(default_conf, update, mocker) -> None: +async def test_balance_handle_too_large_response(default_conf, update, mocker) -> None: balances = [] for i in range(100): curr = choice(ascii_uppercase) + choice(ascii_uppercase) + choice(ascii_uppercase) @@ -869,7 +868,7 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) - telegram._balance(update=update, context=MagicMock()) + await telegram._balance(update=update, context=MagicMock()) assert msg_mock.call_count > 1 # Test if wrap happens around 4000 - # and each single currency-output is around 120 characters long so we need @@ -878,78 +877,78 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None assert len(msg_mock.call_args_list[0][0][0]) > (4096 - 120) -def test_start_handle(default_conf, update, mocker) -> None: +async def test_start_handle(default_conf, update, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) freqtradebot.state = State.STOPPED assert freqtradebot.state == State.STOPPED - telegram._start(update=update, context=MagicMock()) + await telegram._start(update=update, context=MagicMock()) assert freqtradebot.state == State.RUNNING assert msg_mock.call_count == 1 -def test_start_handle_already_running(default_conf, update, mocker) -> None: +async def test_start_handle_already_running(default_conf, update, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) freqtradebot.state = State.RUNNING assert freqtradebot.state == State.RUNNING - telegram._start(update=update, context=MagicMock()) + await telegram._start(update=update, context=MagicMock()) assert freqtradebot.state == State.RUNNING assert msg_mock.call_count == 1 assert 'already running' in msg_mock.call_args_list[0][0][0] -def test_stop_handle(default_conf, update, mocker) -> None: +async def test_stop_handle(default_conf, update, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) freqtradebot.state = State.RUNNING assert freqtradebot.state == State.RUNNING - telegram._stop(update=update, context=MagicMock()) + await telegram._stop(update=update, context=MagicMock()) assert freqtradebot.state == State.STOPPED assert msg_mock.call_count == 1 assert 'stopping trader' in msg_mock.call_args_list[0][0][0] -def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: +async def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) freqtradebot.state = State.STOPPED assert freqtradebot.state == State.STOPPED - telegram._stop(update=update, context=MagicMock()) + await telegram._stop(update=update, context=MagicMock()) assert freqtradebot.state == State.STOPPED assert msg_mock.call_count == 1 assert 'already stopped' in msg_mock.call_args_list[0][0][0] -def test_stopbuy_handle(default_conf, update, mocker) -> None: +async def test_stopbuy_handle(default_conf, update, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) assert freqtradebot.config['max_open_trades'] != 0 - telegram._stopentry(update=update, context=MagicMock()) + await telegram._stopentry(update=update, context=MagicMock()) assert freqtradebot.config['max_open_trades'] == 0 assert msg_mock.call_count == 1 assert 'No more entries will occur from now. Run /reload_config to reset.' \ in msg_mock.call_args_list[0][0][0] -def test_reload_config_handle(default_conf, update, mocker) -> None: +async def test_reload_config_handle(default_conf, update, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) freqtradebot.state = State.RUNNING assert freqtradebot.state == State.RUNNING - telegram._reload_config(update=update, context=MagicMock()) + await telegram._reload_config(update=update, context=MagicMock()) assert freqtradebot.state == State.RELOAD_CONFIG assert msg_mock.call_count == 1 assert 'Reloading config' in msg_mock.call_args_list[0][0][0] -def test_telegram_forceexit_handle(default_conf, update, ticker, fee, +async def test_telegram_forceexit_handle(default_conf, update, ticker, fee, ticker_sell_up, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) msg_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) @@ -980,7 +979,7 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee, # /forceexit 1 context = MagicMock() context.args = ["1"] - telegram._force_exit(update=update, context=context) + await telegram._force_exit(update=update, context=context) assert msg_mock.call_count == 4 last_msg = msg_mock.call_args_list[-2][0][0] @@ -1016,8 +1015,8 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee, } == last_msg -def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, - ticker_sell_down, mocker) -> None: +async def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, + ticker_sell_down, mocker) -> None: mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) msg_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) @@ -1052,7 +1051,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, # /forceexit 1 context = MagicMock() context.args = ["1"] - telegram._force_exit(update=update, context=context) + await telegram._force_exit(update=update, context=context) assert msg_mock.call_count == 4 @@ -1089,7 +1088,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, } == last_msg -def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None: +async def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None: patch_exchange(mocker) mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) @@ -1115,7 +1114,7 @@ def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None # /forceexit all context = MagicMock() context.args = ["all"] - telegram._force_exit(update=update, context=context) + await telegram._force_exit(update=update, context=context) # Called for each trade 2 times assert msg_mock.call_count == 8 @@ -1152,7 +1151,7 @@ def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None } == msg -def test_forceexit_handle_invalid(default_conf, update, mocker) -> None: +async def test_forceexit_handle_invalid(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) @@ -1164,7 +1163,7 @@ def test_forceexit_handle_invalid(default_conf, update, mocker) -> None: # /forceexit 1 context = MagicMock() context.args = ["1"] - telegram._force_exit(update=update, context=context) + await telegram._force_exit(update=update, context=context) assert msg_mock.call_count == 1 assert 'not running' in msg_mock.call_args_list[0][0][0] @@ -1174,12 +1173,12 @@ def test_forceexit_handle_invalid(default_conf, update, mocker) -> None: # /forceexit 123456 context = MagicMock() context.args = ["123456"] - telegram._force_exit(update=update, context=context) + await telegram._force_exit(update=update, context=context) assert msg_mock.call_count == 1 assert 'invalid argument' in msg_mock.call_args_list[0][0][0] -def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: +async def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: default_conf['max_open_trades'] = 4 mocker.patch.multiple( EXMS, @@ -1195,7 +1194,7 @@ def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: # /forceexit context = MagicMock() context.args = [] - telegram._force_exit(update=update, context=context) + await telegram._force_exit(update=update, context=context) # No pair assert msg_mock.call_args_list[0][1]['msg'] == 'No open trade found.' @@ -1204,7 +1203,7 @@ def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: msg_mock.reset_mock() # /forceexit - telegram._force_exit(update=update, context=context) + await telegram._force_exit(update=update, context=context) keyboard = msg_mock.call_args_list[0][1]['keyboard'] # 4 pairs + cancel assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5 @@ -1214,7 +1213,7 @@ def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: update = MagicMock() update.callback_query = MagicMock() update.callback_query.data = keyboard[1][0].callback_data - telegram._force_exit_inline(update, None) + await telegram._force_exit_inline(update, None) assert update.callback_query.answer.call_count == 1 assert update.callback_query.edit_message_text.call_count == 1 assert femock.call_count == 1 @@ -1222,17 +1221,17 @@ def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: # Retry exiting - but cancel instead update.callback_query.reset_mock() - telegram._force_exit(update=update, context=context) + await telegram._force_exit(update=update, context=context) # Use cancel button update.callback_query.data = keyboard[-1][0].callback_data - telegram._force_exit_inline(update, None) + await telegram._force_exit_inline(update, None) query = update.callback_query assert query.answer.call_count == 1 assert query.edit_message_text.call_count == 1 assert query.edit_message_text.call_args_list[-1][1]['text'] == "Force exit canceled." -def test_force_enter_handle(default_conf, update, mocker) -> None: +async def test_force_enter_handle(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) fbuy_mock = MagicMock(return_value=None) @@ -1244,7 +1243,7 @@ def test_force_enter_handle(default_conf, update, mocker) -> None: # /forcelong ETH/BTC context = MagicMock() context.args = ["ETH/BTC"] - telegram._force_enter(update=update, context=context, order_side=SignalDirection.LONG) + await telegram._force_enter(update=update, context=context, order_side=SignalDirection.LONG) assert fbuy_mock.call_count == 1 assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC' @@ -1257,7 +1256,7 @@ def test_force_enter_handle(default_conf, update, mocker) -> None: # /forcelong ETH/BTC 0.055 context = MagicMock() context.args = ["ETH/BTC", "0.055"] - telegram._force_enter(update=update, context=context, order_side=SignalDirection.LONG) + await telegram._force_enter(update=update, context=context, order_side=SignalDirection.LONG) assert fbuy_mock.call_count == 1 assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC' @@ -1265,19 +1264,19 @@ def test_force_enter_handle(default_conf, update, mocker) -> None: assert fbuy_mock.call_args_list[0][0][1] == 0.055 -def test_force_enter_handle_exception(default_conf, update, mocker) -> None: +async def test_force_enter_handle_exception(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) - telegram._force_enter(update=update, context=MagicMock(), order_side=SignalDirection.LONG) + await telegram._force_enter(update=update, context=MagicMock(), order_side=SignalDirection.LONG) assert msg_mock.call_count == 1 assert msg_mock.call_args_list[0][0][0] == 'Force_entry not enabled.' -def test_force_enter_no_pair(default_conf, update, mocker) -> None: +async def test_force_enter_no_pair(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) fbuy_mock = MagicMock(return_value=None) @@ -1289,7 +1288,7 @@ def test_force_enter_no_pair(default_conf, update, mocker) -> None: context = MagicMock() context.args = [] - telegram._force_enter(update=update, context=context, order_side=SignalDirection.LONG) + await telegram._force_enter(update=update, context=context, order_side=SignalDirection.LONG) assert fbuy_mock.call_count == 0 assert msg_mock.call_count == 1 @@ -1301,11 +1300,11 @@ def test_force_enter_no_pair(default_conf, update, mocker) -> None: update = MagicMock() update.callback_query = MagicMock() update.callback_query.data = 'XRP/USDT_||_long' - telegram._force_enter_inline(update, None) + await telegram._force_enter_inline(update, None) assert fbuy_mock.call_count == 1 -def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, mocker) -> None: +async def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, mocker) -> None: mocker.patch.multiple( EXMS, @@ -1317,13 +1316,13 @@ def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, moc # Create some test data create_mock_trades_usdt(fee) - telegram._performance(update=update, context=MagicMock()) + await telegram._performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'Performance' in msg_mock.call_args_list[0][0][0] assert 'XRP/USDT\t2.842 USDT (10.00%) (1)' in msg_mock.call_args_list[0][0][0] -def test_telegram_entry_tag_performance_handle( +async def test_telegram_entry_tag_performance_handle( default_conf_usdt, update, ticker, fee, mocker) -> None: mocker.patch.multiple( EXMS, @@ -1336,26 +1335,26 @@ def test_telegram_entry_tag_performance_handle( create_mock_trades_usdt(fee) context = MagicMock() - telegram._enter_tag_performance(update=update, context=context) + await telegram._enter_tag_performance(update=update, context=context) assert msg_mock.call_count == 1 assert 'Entry Tag Performance' in msg_mock.call_args_list[0][0][0] assert 'TEST1\t3.987 USDT (5.00%) (1)' in msg_mock.call_args_list[0][0][0] context.args = ['XRP/USDT'] - telegram._enter_tag_performance(update=update, context=context) + await telegram._enter_tag_performance(update=update, context=context) assert msg_mock.call_count == 2 msg_mock.reset_mock() mocker.patch('freqtrade.rpc.rpc.RPC._rpc_enter_tag_performance', side_effect=RPCException('Error')) - telegram._enter_tag_performance(update=update, context=MagicMock()) + await telegram._enter_tag_performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert "Error" in msg_mock.call_args_list[0][0][0] -def test_telegram_exit_reason_performance_handle(default_conf_usdt, update, ticker, fee, - mocker) -> None: +async def test_telegram_exit_reason_performance_handle( + default_conf_usdt, update, ticker, fee, mocker) -> None: mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -1367,26 +1366,26 @@ def test_telegram_exit_reason_performance_handle(default_conf_usdt, update, tick create_mock_trades_usdt(fee) context = MagicMock() - telegram._exit_reason_performance(update=update, context=context) + await telegram._exit_reason_performance(update=update, context=context) assert msg_mock.call_count == 1 assert 'Exit Reason Performance' in msg_mock.call_args_list[0][0][0] assert 'roi\t2.842 USDT (10.00%) (1)' in msg_mock.call_args_list[0][0][0] context.args = ['XRP/USDT'] - telegram._exit_reason_performance(update=update, context=context) + await telegram._exit_reason_performance(update=update, context=context) assert msg_mock.call_count == 2 msg_mock.reset_mock() mocker.patch('freqtrade.rpc.rpc.RPC._rpc_exit_reason_performance', side_effect=RPCException('Error')) - telegram._exit_reason_performance(update=update, context=MagicMock()) + await telegram._exit_reason_performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert "Error" in msg_mock.call_args_list[0][0][0] -def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker, fee, - mocker) -> None: +async def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker, fee, + mocker) -> None: mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -1399,26 +1398,26 @@ def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker, create_mock_trades_usdt(fee) context = MagicMock() - telegram._mix_tag_performance(update=update, context=context) + await telegram._mix_tag_performance(update=update, context=context) assert msg_mock.call_count == 1 assert 'Mix Tag Performance' in msg_mock.call_args_list[0][0][0] assert ('TEST3 roi\t2.842 USDT (10.00%) (1)' in msg_mock.call_args_list[0][0][0]) context.args = ['XRP/USDT'] - telegram._mix_tag_performance(update=update, context=context) + await telegram._mix_tag_performance(update=update, context=context) assert msg_mock.call_count == 2 msg_mock.reset_mock() mocker.patch('freqtrade.rpc.rpc.RPC._rpc_mix_tag_performance', side_effect=RPCException('Error')) - telegram._mix_tag_performance(update=update, context=MagicMock()) + await telegram._mix_tag_performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert "Error" in msg_mock.call_args_list[0][0][0] -def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: +async def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -1428,7 +1427,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: patch_get_signal(freqtradebot) freqtradebot.state = State.STOPPED - telegram._count(update=update, context=MagicMock()) + await telegram._count(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'not running' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() @@ -1437,7 +1436,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: # Create some test data freqtradebot.enter_positions() msg_mock.reset_mock() - telegram._count(update=update, context=MagicMock()) + await telegram._count(update=update, context=MagicMock()) msg = ('
  current    max    total stake\n---------  -----  -------------\n'
            '        1      {}          {}
').format( @@ -1447,7 +1446,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: assert msg in msg_mock.call_args_list[0][0][0] -def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None: +async def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -1455,7 +1454,7 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) - telegram._locks(update=update, context=MagicMock()) + await telegram._locks(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'No active locks.' in msg_mock.call_args_list[0][0][0] @@ -1464,7 +1463,7 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None PairLocks.lock_pair('ETH/BTC', arrow.utcnow().shift(minutes=4).datetime, 'randreason') PairLocks.lock_pair('XRP/BTC', arrow.utcnow().shift(minutes=20).datetime, 'deadbeef') - telegram._locks(update=update, context=MagicMock()) + await telegram._locks(update=update, context=MagicMock()) assert 'Pair' in msg_mock.call_args_list[0][0][0] assert 'Until' in msg_mock.call_args_list[0][0][0] @@ -1477,7 +1476,7 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None context = MagicMock() context.args = ['XRP/BTC'] msg_mock.reset_mock() - telegram._delete_locks(update=update, context=context) + await telegram._delete_locks(update=update, context=context) assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0] assert 'randreason' in msg_mock.call_args_list[0][0][0] @@ -1485,11 +1484,11 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None assert 'deadbeef' not in msg_mock.call_args_list[0][0][0] -def test_whitelist_static(default_conf, update, mocker) -> None: +async def test_whitelist_static(default_conf, update, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - telegram._whitelist(update=update, context=MagicMock()) + await telegram._whitelist(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert ("Using whitelist `['StaticPairList']` with 4 pairs\n" "`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`" in msg_mock.call_args_list[0][0][0]) @@ -1497,33 +1496,33 @@ def test_whitelist_static(default_conf, update, mocker) -> None: context = MagicMock() context.args = ['sorted'] msg_mock.reset_mock() - telegram._whitelist(update=update, context=context) + await telegram._whitelist(update=update, context=context) assert ("Using whitelist `['StaticPairList']` with 4 pairs\n" "`ETH/BTC, LTC/BTC, NEO/BTC, XRP/BTC`" in msg_mock.call_args_list[0][0][0]) context = MagicMock() context.args = ['baseonly'] msg_mock.reset_mock() - telegram._whitelist(update=update, context=context) + await telegram._whitelist(update=update, context=context) assert ("Using whitelist `['StaticPairList']` with 4 pairs\n" "`ETH, LTC, XRP, NEO`" in msg_mock.call_args_list[0][0][0]) context = MagicMock() context.args = ['baseonly', 'sorted'] msg_mock.reset_mock() - telegram._whitelist(update=update, context=context) + await telegram._whitelist(update=update, context=context) assert ("Using whitelist `['StaticPairList']` with 4 pairs\n" "`ETH, LTC, NEO, XRP`" in msg_mock.call_args_list[0][0][0]) -def test_whitelist_dynamic(default_conf, update, mocker) -> None: +async def test_whitelist_dynamic(default_conf, update, mocker) -> None: mocker.patch(f'{EXMS}.exchange_has', return_value=True) default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 4 }] telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) - telegram._whitelist(update=update, context=MagicMock()) + await telegram._whitelist(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert ("Using whitelist `['VolumePairList']` with 4 pairs\n" "`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`" in msg_mock.call_args_list[0][0][0]) @@ -1531,30 +1530,30 @@ def test_whitelist_dynamic(default_conf, update, mocker) -> None: context = MagicMock() context.args = ['sorted'] msg_mock.reset_mock() - telegram._whitelist(update=update, context=context) + await telegram._whitelist(update=update, context=context) assert ("Using whitelist `['VolumePairList']` with 4 pairs\n" "`ETH/BTC, LTC/BTC, NEO/BTC, XRP/BTC`" in msg_mock.call_args_list[0][0][0]) context = MagicMock() context.args = ['baseonly'] msg_mock.reset_mock() - telegram._whitelist(update=update, context=context) + await telegram._whitelist(update=update, context=context) assert ("Using whitelist `['VolumePairList']` with 4 pairs\n" "`ETH, LTC, XRP, NEO`" in msg_mock.call_args_list[0][0][0]) context = MagicMock() context.args = ['baseonly', 'sorted'] msg_mock.reset_mock() - telegram._whitelist(update=update, context=context) + await telegram._whitelist(update=update, context=context) assert ("Using whitelist `['VolumePairList']` with 4 pairs\n" "`ETH, LTC, NEO, XRP`" in msg_mock.call_args_list[0][0][0]) -def test_blacklist_static(default_conf, update, mocker) -> None: +async def test_blacklist_static(default_conf, update, mocker) -> None: telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - telegram._blacklist(update=update, context=MagicMock()) + await telegram._blacklist(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert ("Blacklist contains 2 pairs\n`DOGE/BTC, HOT/BTC`" in msg_mock.call_args_list[0][0][0]) @@ -1564,7 +1563,7 @@ def test_blacklist_static(default_conf, update, mocker) -> None: # /blacklist ETH/BTC context = MagicMock() context.args = ["ETH/BTC"] - telegram._blacklist(update=update, context=context) + await telegram._blacklist(update=update, context=context) assert msg_mock.call_count == 1 assert ("Blacklist contains 3 pairs\n`DOGE/BTC, HOT/BTC, ETH/BTC`" in msg_mock.call_args_list[0][0][0]) @@ -1573,7 +1572,7 @@ def test_blacklist_static(default_conf, update, mocker) -> None: msg_mock.reset_mock() context = MagicMock() context.args = ["XRP/.*"] - telegram._blacklist(update=update, context=context) + await telegram._blacklist(update=update, context=context) assert msg_mock.call_count == 1 assert ("Blacklist contains 4 pairs\n`DOGE/BTC, HOT/BTC, ETH/BTC, XRP/.*`" @@ -1582,7 +1581,7 @@ def test_blacklist_static(default_conf, update, mocker) -> None: msg_mock.reset_mock() context.args = ["DOGE/BTC"] - telegram._blacklist_delete(update=update, context=context) + await telegram._blacklist_delete(update=update, context=context) assert msg_mock.call_count == 1 assert ("Blacklist contains 3 pairs\n`HOT/BTC, ETH/BTC, XRP/.*`" in msg_mock.call_args_list[0][0][0]) @@ -2374,7 +2373,7 @@ def test__send_msg_network_error(default_conf, mocker, caplog) -> None: assert log_has('Telegram NetworkError: Oh snap! Trying one more time.', caplog) -def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: +async def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) bot = MagicMock() bot.send_message = MagicMock() @@ -2400,7 +2399,7 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: # no keyboard in config -> default keyboard freqtradebot.config['telegram']['enabled'] = True telegram = init_telegram(freqtradebot) - telegram._send_msg('test') + await telegram._send_msg('test') used_keyboard = bot.send_message.call_args[1]['reply_markup'] assert used_keyboard == default_keyboard @@ -2417,7 +2416,7 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None: freqtradebot.config['telegram']['enabled'] = True freqtradebot.config['telegram']['keyboard'] = custom_keys_list telegram = init_telegram(freqtradebot) - telegram._send_msg('test') + await telegram._send_msg('test') used_keyboard = bot.send_message.call_args[1]['reply_markup'] assert used_keyboard == custom_keyboard assert log_has("using custom keyboard from config.json: " From c475c818414f03a06898cc2699165a9206d24f9f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Apr 2023 15:15:19 +0200 Subject: [PATCH 15/27] Update several tests to async behavior --- tests/rpc/test_rpc_telegram.py | 143 +++++++++++++++++---------------- 1 file changed, 72 insertions(+), 71 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 5b7950e4c..6ca028c1c 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -321,7 +321,7 @@ async def test_telegram_status_closed_trade(default_conf, update, mocker, fee) - assert re.search(r'Close Profit:', msg) -def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: +async def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: default_conf['max_open_trades'] = 3 mocker.patch.multiple( EXMS, @@ -341,13 +341,13 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: freqtradebot.state = State.STOPPED # Status is also enabled when stopped - telegram._status(update=update, context=MagicMock()) + await telegram._status(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'no active trade' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() freqtradebot.state = State.RUNNING - telegram._status(update=update, context=MagicMock()) + await telegram._status(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'no active trade' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() @@ -355,7 +355,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: # Create some test data freqtradebot.enter_positions() # Trigger status while we have a fulfilled order for the open trade - telegram._status(update=update, context=MagicMock()) + await telegram._status(update=update, context=MagicMock()) # close_rate should not be included in the message as the trade is not closed # and no line should be empty @@ -372,7 +372,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: context = MagicMock() context.args = ["2", "3"] - telegram._status(update=update, context=context) + await telegram._status(update=update, context=context) lines = msg_mock.call_args_list[0][0][0].split('\n') assert '' not in lines[:-1] @@ -387,7 +387,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: msg_mock.reset_mock() context = MagicMock() context.args = ["2"] - telegram._status(update=update, context=context) + await telegram._status(update=update, context=context) assert msg_mock.call_count == 2 @@ -399,7 +399,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: assert 'Trade ID:* `2` - continued' in msg2 -def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: +async def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -414,13 +414,13 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: freqtradebot.state = State.STOPPED # Status table is also enabled when stopped - telegram._status_table(update=update, context=MagicMock()) + await telegram._status_table(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'no active trade' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() freqtradebot.state = State.RUNNING - telegram._status_table(update=update, context=MagicMock()) + await telegram._status_table(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'no active trade' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() @@ -428,7 +428,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: # Create some test data freqtradebot.enter_positions() - telegram._status_table(update=update, context=MagicMock()) + await telegram._status_table(update=update, context=MagicMock()) text = re.sub('', '', msg_mock.call_args_list[-1][0][0]) line = text.split("\n") @@ -440,7 +440,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: assert msg_mock.call_count == 1 -def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None: +async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None: mocker.patch( 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.1 @@ -462,7 +462,7 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi # /daily 2 context = MagicMock() context.args = ["2"] - telegram._daily(update=update, context=context) + await telegram._daily(update=update, context=context) assert msg_mock.call_count == 1 assert "Daily Profit over the last 2 days:" in msg_mock.call_args_list[0][0][0] assert 'Day ' in msg_mock.call_args_list[0][0][0] @@ -476,7 +476,7 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi # Reset msg_mock msg_mock.reset_mock() context.args = [] - telegram._daily(update=update, context=context) + await telegram._daily(update=update, context=context) assert msg_mock.call_count == 1 assert "Daily Profit over the last 7 days:" in msg_mock.call_args_list[0][0][0] assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0] @@ -493,13 +493,13 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi # /daily 1 context = MagicMock() context.args = ["1"] - telegram._daily(update=update, context=context) + await telegram._daily(update=update, context=context) assert ' 6.83 USDT' in msg_mock.call_args_list[0][0][0] assert ' 7.51 USD' in msg_mock.call_args_list[0][0][0] assert '(2)' in msg_mock.call_args_list[0][0][0] -def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: +async def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: mocker.patch.multiple( EXMS, fetch_ticker=ticker @@ -514,7 +514,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: # /daily -2 context = MagicMock() context.args = ["-2"] - telegram._daily(update=update, context=context) + await telegram._daily(update=update, context=context) assert msg_mock.call_count == 1 assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0] @@ -524,11 +524,11 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: # /daily today context = MagicMock() context.args = ["today"] - telegram._daily(update=update, context=context) + await telegram._daily(update=update, context=context) assert 'Daily Profit over the last 7 days:' in msg_mock.call_args_list[0][0][0] -def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None: +async def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None: default_conf_usdt['max_open_trades'] = 1 mocker.patch( 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', @@ -549,7 +549,7 @@ def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mach # /weekly 2 context = MagicMock() context.args = ["2"] - telegram._weekly(update=update, context=context) + await telegram._weekly(update=update, context=context) assert msg_mock.call_count == 1 assert "Weekly Profit over the last 2 weeks (starting from Monday):" \ in msg_mock.call_args_list[0][0][0] @@ -565,7 +565,7 @@ def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mach # Reset msg_mock msg_mock.reset_mock() context.args = [] - telegram._weekly(update=update, context=context) + await telegram._weekly(update=update, context=context) assert msg_mock.call_count == 1 assert "Weekly Profit over the last 8 weeks (starting from Monday):" \ in msg_mock.call_args_list[0][0][0] @@ -581,7 +581,7 @@ def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mach # /weekly -3 context = MagicMock() context.args = ["-3"] - telegram._weekly(update=update, context=context) + await telegram._weekly(update=update, context=context) assert msg_mock.call_count == 1 assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0] @@ -591,14 +591,14 @@ def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mach # /weekly this week context = MagicMock() context.args = ["this week"] - telegram._weekly(update=update, context=context) + await telegram._weekly(update=update, context=context) assert ( 'Weekly Profit over the last 8 weeks (starting from Monday):' in msg_mock.call_args_list[0][0][0] ) -def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None: +async def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None: default_conf_usdt['max_open_trades'] = 1 mocker.patch( 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', @@ -619,7 +619,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac # /monthly 2 context = MagicMock() context.args = ["2"] - telegram._monthly(update=update, context=context) + await telegram._monthly(update=update, context=context) assert msg_mock.call_count == 1 assert 'Monthly Profit over the last 2 months:' in msg_mock.call_args_list[0][0][0] assert 'Month ' in msg_mock.call_args_list[0][0][0] @@ -634,7 +634,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac # Reset msg_mock msg_mock.reset_mock() context.args = [] - telegram._monthly(update=update, context=context) + await telegram._monthly(update=update, context=context) assert msg_mock.call_count == 1 # Default to 6 months assert 'Monthly Profit over the last 6 months:' in msg_mock.call_args_list[0][0][0] @@ -651,7 +651,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac # /monthly 12 context = MagicMock() context.args = ["12"] - telegram._monthly(update=update, context=context) + await telegram._monthly(update=update, context=context) assert msg_mock.call_count == 1 assert 'Monthly Profit over the last 12 months:' in msg_mock.call_args_list[0][0][0] assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0] @@ -668,7 +668,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac # /monthly -3 context = MagicMock() context.args = ["-3"] - telegram._monthly(update=update, context=context) + await telegram._monthly(update=update, context=context) assert msg_mock.call_count == 1 assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0] @@ -678,11 +678,11 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac # /monthly february context = MagicMock() context.args = ["february"] - telegram._monthly(update=update, context=context) + await telegram._monthly(update=update, context=context) assert 'Monthly Profit over the last 6 months:' in msg_mock.call_args_list[0][0][0] -def test_telegram_profit_handle( +async def test_telegram_profit_handle( default_conf_usdt, update, ticker_usdt, ticker_sell_up, fee, limit_sell_order_usdt, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.1) @@ -695,7 +695,7 @@ def test_telegram_profit_handle( telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt) patch_get_signal(freqtradebot) - telegram._profit(update=update, context=MagicMock()) + await telegram._profit(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'No trades yet.' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() @@ -707,7 +707,7 @@ def test_telegram_profit_handle( context = MagicMock() # Test with invalid 2nd argument (should silently pass) context.args = ["aaa"] - telegram._profit(update=update, context=context) + await telegram._profit(update=update, context=context) assert msg_mock.call_count == 1 assert 'No closed trade' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] @@ -730,7 +730,7 @@ def test_telegram_profit_handle( Trade.commit() context.args = [3] - telegram._profit(update=update, context=context) + await telegram._profit(update=update, context=context) assert msg_mock.call_count == 1 assert '*ROI:* Closed trades' in msg_mock.call_args_list[-1][0][0] assert ('∙ `5.685 USDT (9.45%) (0.57 \N{GREEK CAPITAL LETTER SIGMA}%)`' @@ -949,7 +949,7 @@ async def test_reload_config_handle(default_conf, update, mocker) -> None: async def test_telegram_forceexit_handle(default_conf, update, ticker, fee, - ticker_sell_up, mocker) -> None: + ticker_sell_up, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) msg_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) @@ -1587,7 +1587,7 @@ async def test_blacklist_static(default_conf, update, mocker) -> None: in msg_mock.call_args_list[0][0][0]) -def test_telegram_logs(default_conf, update, mocker) -> None: +async def test_telegram_logs(default_conf, update, mocker) -> None: mocker.patch.multiple( 'freqtrade.rpc.telegram.Telegram', _init=MagicMock(), @@ -1598,13 +1598,13 @@ def test_telegram_logs(default_conf, update, mocker) -> None: context = MagicMock() context.args = [] - telegram._logs(update=update, context=context) + await telegram._logs(update=update, context=context) assert msg_mock.call_count == 1 assert "freqtrade\\.rpc\\.telegram" in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() context.args = ["1"] - telegram._logs(update=update, context=context) + await telegram._logs(update=update, context=context) assert msg_mock.call_count == 1 msg_mock.reset_mock() @@ -1612,22 +1612,22 @@ def test_telegram_logs(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.telegram.MAX_MESSAGE_LENGTH', 200) context = MagicMock() context.args = [] - telegram._logs(update=update, context=context) + await telegram._logs(update=update, context=context) # Called at least 2 times. Exact times will change with unrelated changes to setup messages # Therefore we don't test for this explicitly. assert msg_mock.call_count >= 2 -def test_edge_disabled(default_conf, update, mocker) -> None: +async def test_edge_disabled(default_conf, update, mocker) -> None: telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) - telegram._edge(update=update, context=MagicMock()) + await telegram._edge(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert "Edge is not enabled." in msg_mock.call_args_list[0][0][0] -def test_edge_enabled(edge_conf, update, mocker) -> None: +async def test_edge_enabled(edge_conf, update, mocker) -> None: mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( return_value={ 'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60), @@ -1636,7 +1636,7 @@ def test_edge_enabled(edge_conf, update, mocker) -> None: telegram, _, msg_mock = get_telegram_testobject(mocker, edge_conf) - telegram._edge(update=update, context=MagicMock()) + await telegram._edge(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'Edge only validated following pairs:\n
' in msg_mock.call_args_list[0][0][0]
     assert 'Pair      Winrate    Expectancy    Stoploss' in msg_mock.call_args_list[0][0][0]
@@ -1645,7 +1645,7 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
 
     mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
         return_value={}))
-    telegram._edge(update=update, context=MagicMock())
+    await telegram._edge(update=update, context=MagicMock())
     assert msg_mock.call_count == 1
     assert 'Edge only validated following pairs:' in msg_mock.call_args_list[0][0][0]
     assert 'Winrate' not in msg_mock.call_args_list[0][0][0]
@@ -1654,20 +1654,20 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
 @pytest.mark.parametrize('is_short,regex_pattern',
                          [(True, r"just now[ ]*XRP\/BTC \(#3\)  -1.00% \("),
                           (False, r"just now[ ]*XRP\/BTC \(#3\)  1.00% \(")])
-def test_telegram_trades(mocker, update, default_conf, fee, is_short, regex_pattern):
+async def test_telegram_trades(mocker, update, default_conf, fee, is_short, regex_pattern):
 
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
     context = MagicMock()
     context.args = []
 
-    telegram._trades(update=update, context=context)
+    await telegram._trades(update=update, context=context)
     assert "0 recent trades:" in msg_mock.call_args_list[0][0][0]
     assert "
" not in msg_mock.call_args_list[0][0][0]
     msg_mock.reset_mock()
 
     context.args = ['hello']
-    telegram._trades(update=update, context=context)
+    await telegram._trades(update=update, context=context)
     assert "0 recent trades:" in msg_mock.call_args_list[0][0][0]
     assert "
" not in msg_mock.call_args_list[0][0][0]
     msg_mock.reset_mock()
@@ -1676,7 +1676,7 @@ def test_telegram_trades(mocker, update, default_conf, fee, is_short, regex_patt
 
     context = MagicMock()
     context.args = [5]
-    telegram._trades(update=update, context=context)
+    await telegram._trades(update=update, context=context)
     msg_mock.call_count == 1
     assert "2 recent trades:" in msg_mock.call_args_list[0][0][0]
     assert "Profit (" in msg_mock.call_args_list[0][0][0]
@@ -1686,13 +1686,13 @@ def test_telegram_trades(mocker, update, default_conf, fee, is_short, regex_patt
 
 
 @pytest.mark.parametrize('is_short', [True, False])
-def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short):
+async def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short):
 
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
     context = MagicMock()
     context.args = []
 
-    telegram._delete_trade(update=update, context=context)
+    await telegram._delete_trade(update=update, context=context)
     assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
 
     msg_mock.reset_mock()
@@ -1700,14 +1700,14 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short):
 
     context = MagicMock()
     context.args = [1]
-    telegram._delete_trade(update=update, context=context)
+    await telegram._delete_trade(update=update, context=context)
     msg_mock.call_count == 1
     assert "Deleted trade 1." in msg_mock.call_args_list[0][0][0]
     assert "Please make sure to take care of this asset" in msg_mock.call_args_list[0][0][0]
 
 
 @pytest.mark.parametrize('is_short', [True, False])
-def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, ticker):
+async def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, ticker):
 
     mocker.patch.multiple(
         EXMS,
@@ -1717,7 +1717,7 @@ def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short,
     context = MagicMock()
     context.args = []
 
-    telegram._cancel_open_order(update=update, context=context)
+    await telegram._cancel_open_order(update=update, context=context)
     assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
 
     msg_mock.reset_mock()
@@ -1725,7 +1725,7 @@ def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short,
 
     context = MagicMock()
     context.args = [5]
-    telegram._cancel_open_order(update=update, context=context)
+    await telegram._cancel_open_order(update=update, context=context)
     assert "No open order for trade_id" in msg_mock.call_args_list[0][0][0]
 
     msg_mock.reset_mock()
@@ -1734,43 +1734,43 @@ def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short,
     mocker.patch(f'{EXMS}.fetch_order', return_value=trade.orders[-1].to_ccxt_object())
     context = MagicMock()
     context.args = [6]
-    telegram._cancel_open_order(update=update, context=context)
+    await telegram._cancel_open_order(update=update, context=context)
     assert msg_mock.call_count == 1
     assert "Open order canceled." in msg_mock.call_args_list[0][0][0]
 
 
-def test_help_handle(default_conf, update, mocker) -> None:
+async def test_help_handle(default_conf, update, mocker) -> None:
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
-    telegram._help(update=update, context=MagicMock())
+    await telegram._help(update=update, context=MagicMock())
     assert msg_mock.call_count == 1
     assert '*/help:* `This help message`' in msg_mock.call_args_list[0][0][0]
 
 
-def test_version_handle(default_conf, update, mocker) -> None:
+async def test_version_handle(default_conf, update, mocker) -> None:
 
     telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
 
-    telegram._version(update=update, context=MagicMock())
+    await telegram._version(update=update, context=MagicMock())
     assert msg_mock.call_count == 1
     assert f'*Version:* `{__version__}`' in msg_mock.call_args_list[0][0][0]
 
     msg_mock.reset_mock()
     freqtradebot.strategy.version = lambda: '1.1.1'
 
-    telegram._version(update=update, context=MagicMock())
+    await telegram._version(update=update, context=MagicMock())
     assert msg_mock.call_count == 1
     assert f'*Version:* `{__version__}`' in msg_mock.call_args_list[0][0][0]
     assert '*Strategy version: * `1.1.1`' in msg_mock.call_args_list[0][0][0]
 
 
-def test_show_config_handle(default_conf, update, mocker) -> None:
+async def test_show_config_handle(default_conf, update, mocker) -> None:
 
     default_conf['runmode'] = RunMode.DRY_RUN
 
     telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
 
-    telegram._show_config(update=update, context=MagicMock())
+    await telegram._show_config(update=update, context=MagicMock())
     assert msg_mock.call_count == 1
     assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
     assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
@@ -1779,7 +1779,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
 
     msg_mock.reset_mock()
     freqtradebot.config['trailing_stop'] = True
-    telegram._show_config(update=update, context=MagicMock())
+    await telegram._show_config(update=update, context=MagicMock())
     assert msg_mock.call_count == 1
     assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
     assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
@@ -2323,7 +2323,7 @@ def test__sell_emoji(default_conf, mocker, msg, expected):
     assert telegram._get_sell_emoji(msg) == expected
 
 
-def test_telegram__send_msg(default_conf, mocker, caplog) -> None:
+async def test_telegram__send_msg(default_conf, mocker, caplog) -> None:
     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
     bot = MagicMock()
     telegram, _, _ = get_telegram_testobject(mocker, default_conf, mock=False)
@@ -2331,33 +2331,33 @@ def test_telegram__send_msg(default_conf, mocker, caplog) -> None:
     telegram._updater.bot = bot
 
     telegram._config['telegram']['enabled'] = True
-    telegram._send_msg('test')
+    await telegram._send_msg('test')
     assert len(bot.method_calls) == 1
 
     # Test update
     query = MagicMock()
-    telegram._send_msg('test', callback_path="DeadBeef", query=query, reload_able=True)
+    await telegram._send_msg('test', callback_path="DeadBeef", query=query, reload_able=True)
     edit_message_text = telegram._updater.bot.edit_message_text
     assert edit_message_text.call_count == 1
     assert "Updated: " in edit_message_text.call_args_list[0][1]['text']
 
     telegram._updater.bot.edit_message_text = MagicMock(side_effect=BadRequest("not modified"))
-    telegram._send_msg('test', callback_path="DeadBeef", query=query)
+    await telegram._send_msg('test', callback_path="DeadBeef", query=query)
     assert telegram._updater.bot.edit_message_text.call_count == 1
     assert not log_has_re(r"TelegramError: .*", caplog)
 
     telegram._updater.bot.edit_message_text = MagicMock(side_effect=BadRequest(""))
-    telegram._send_msg('test2', callback_path="DeadBeef", query=query)
+    await telegram._send_msg('test2', callback_path="DeadBeef", query=query)
     assert telegram._updater.bot.edit_message_text.call_count == 1
     assert log_has_re(r"TelegramError: .*", caplog)
 
     telegram._updater.bot.edit_message_text = MagicMock(side_effect=TelegramError("DeadBEEF"))
-    telegram._send_msg('test3', callback_path="DeadBeef", query=query)
+    await telegram._send_msg('test3', callback_path="DeadBeef", query=query)
 
     assert log_has_re(r"TelegramError: DeadBEEF! Giving up.*", caplog)
 
 
-def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
+async def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
     bot = MagicMock()
     bot.send_message = MagicMock(side_effect=NetworkError('Oh snap'))
@@ -2366,7 +2366,7 @@ def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
     telegram._updater.bot = bot
 
     telegram._config['telegram']['enabled'] = True
-    telegram._send_msg('test')
+    await telegram._send_msg('test')
 
     # Bot should've tried to send it twice
     assert len(bot.method_calls) == 2
@@ -2424,13 +2424,14 @@ async def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
                    "'/start', '/reload_config', '/help']]", caplog)
 
 
-def test_change_market_direction(default_conf, mocker, update) -> None:
+async def test_change_market_direction(default_conf, mocker, update) -> None:
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
     assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.NONE
     context = MagicMock()
     context.args = ["long"]
-    telegram._changemarketdir(update, context)
+    await telegram._changemarketdir(update, context)
     assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.LONG
     context = MagicMock()
     context.args = ["invalid"]
+    await telegram._changemarketdir(update, context)
     assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.LONG

From 678c9ae67ffc2c69fc5000982ccb9ffb0ff8cf3d Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sat, 15 Apr 2023 15:18:57 +0200
Subject: [PATCH 16/27] Fix some more async telegram tests

---
 freqtrade/rpc/telegram.py      | 2 +-
 tests/rpc/test_rpc_telegram.py | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 192e2b6d7..babe7d582 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -1083,7 +1083,7 @@ class Telegram(RPCHandler):
 
         if context.args:
             trade_id = context.args[0]
-            self._force_exit_action(trade_id)
+            await self._force_exit_action(trade_id)
         else:
             fiat_currency = self._config.get('fiat_display_currency', '')
             try:
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 6ca028c1c..f17dfa190 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -1211,7 +1211,7 @@ async def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) ->
 
     assert keyboard[1][0].callback_data == 'force_exit__2 '
     update = MagicMock()
-    update.callback_query = MagicMock()
+    update.callback_query = AsyncMock()
     update.callback_query.data = keyboard[1][0].callback_data
     await telegram._force_exit_inline(update, None)
     assert update.callback_query.answer.call_count == 1
@@ -1298,7 +1298,7 @@ async def test_force_enter_no_pair(default_conf, update, mocker) -> None:
     # One additional button - cancel
     assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5
     update = MagicMock()
-    update.callback_query = MagicMock()
+    update.callback_query = AsyncMock()
     update.callback_query.data = 'XRP/USDT_||_long'
     await telegram._force_enter_inline(update, None)
     assert fbuy_mock.call_count == 1

From 4177afdf8be4f2de5043cbfc934a8e3ed45e28a3 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sat, 15 Apr 2023 15:43:53 +0200
Subject: [PATCH 17/27] More async test updates

---
 tests/rpc/test_rpc_telegram.py | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index f17dfa190..39895abaf 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -64,14 +64,14 @@ class DummyCls(Telegram):
         pass
 
     @authorized_only
-    def dummy_handler(self, *args, **kwargs) -> None:
+    async def dummy_handler(self, *args, **kwargs) -> None:
         """
         Fake method that only change the state of the object
         """
         self.state['called'] = True
 
     @authorized_only
-    def dummy_exception(self, *args, **kwargs) -> None:
+    async def dummy_exception(self, *args, **kwargs) -> None:
         """
         Fake method that throw an exception
         """
@@ -91,6 +91,7 @@ def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None):
         ftbot = get_patched_freqtradebot(mocker, default_conf)
     rpc = RPC(ftbot)
     telegram = Telegram(rpc, default_conf)
+    telegram._loop = MagicMock()
 
     return telegram, ftbot, msg_mock
 
@@ -139,7 +140,7 @@ def test_cleanup(default_conf, mocker, ) -> None:
     assert telegram._updater.stop.call_count == 1
 
 
-def test_authorized_only(default_conf, mocker, caplog, update) -> None:
+async def test_authorized_only(default_conf, mocker, caplog, update) -> None:
     patch_exchange(mocker)
     caplog.set_level(logging.DEBUG)
     default_conf['telegram']['enabled'] = False
@@ -148,14 +149,14 @@ def test_authorized_only(default_conf, mocker, caplog, update) -> None:
     dummy = DummyCls(rpc, default_conf)
 
     patch_get_signal(bot)
-    dummy.dummy_handler(update=update, context=MagicMock())
+    await 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)
     assert not log_has('Rejected unauthorized message from: 0', caplog)
     assert not log_has('Exception occurred within Telegram module', caplog)
 
 
-def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
+async def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
     patch_exchange(mocker)
     caplog.set_level(logging.DEBUG)
     chat = Chat(0xdeadbeef, 0)
@@ -168,14 +169,14 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
     dummy = DummyCls(rpc, default_conf)
 
     patch_get_signal(bot)
-    dummy.dummy_handler(update=update, context=MagicMock())
+    await 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)
     assert log_has('Rejected unauthorized message from: 3735928559', caplog)
     assert not log_has('Exception occurred within Telegram module', caplog)
 
 
-def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None:
+async def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None:
     patch_exchange(mocker)
 
     default_conf['telegram']['enabled'] = False
@@ -185,7 +186,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None
     dummy = DummyCls(rpc, default_conf)
     patch_get_signal(bot)
 
-    dummy.dummy_exception(update=update, context=MagicMock())
+    await dummy.dummy_exception(update=update, context=MagicMock())
     assert dummy.state['called'] is False
     assert not log_has('Executing handler: dummy_handler for chat_id: 0', caplog)
     assert not log_has('Rejected unauthorized message from: 0', caplog)
@@ -2086,7 +2087,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
         telegram._rpc._fiat_converter.convert_amount = old_convamount
 
 
-def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
+async def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
 
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 

From 69f61ef767f6d7fe3bb493c5dd72ff0a5d1dbf95 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sat, 15 Apr 2023 15:54:25 +0200
Subject: [PATCH 18/27] Further telegram async tests

---
 tests/rpc/test_rpc_telegram.py | 31 ++++++++++++++++---------------
 1 file changed, 16 insertions(+), 15 deletions(-)

diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 39895abaf..1b84df1ca 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -137,7 +137,7 @@ def test_cleanup(default_conf, mocker, ) -> None:
 
     telegram, _, _ = get_telegram_testobject(mocker, default_conf, mock=False)
     telegram.cleanup()
-    assert telegram._updater.stop.call_count == 1
+    assert telegram._app.stop.call_count == 1
 
 
 async def test_authorized_only(default_conf, mocker, caplog, update) -> None:
@@ -2327,32 +2327,33 @@ def test__sell_emoji(default_conf, mocker, msg, expected):
 async def test_telegram__send_msg(default_conf, mocker, caplog) -> None:
     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
     bot = MagicMock()
+    bot.send_message = AsyncMock()
+    bot.edit_message_text = AsyncMock()
     telegram, _, _ = get_telegram_testobject(mocker, default_conf, mock=False)
-    telegram._updater = MagicMock()
-    telegram._updater.bot = bot
+    telegram._app = MagicMock()
+    telegram._app.bot = bot
 
-    telegram._config['telegram']['enabled'] = True
     await telegram._send_msg('test')
     assert len(bot.method_calls) == 1
 
     # Test update
     query = MagicMock()
     await telegram._send_msg('test', callback_path="DeadBeef", query=query, reload_able=True)
-    edit_message_text = telegram._updater.bot.edit_message_text
+    edit_message_text = telegram._app.bot.edit_message_text
     assert edit_message_text.call_count == 1
     assert "Updated: " in edit_message_text.call_args_list[0][1]['text']
 
-    telegram._updater.bot.edit_message_text = MagicMock(side_effect=BadRequest("not modified"))
+    telegram._app.bot.edit_message_text = AsyncMock(side_effect=BadRequest("not modified"))
     await telegram._send_msg('test', callback_path="DeadBeef", query=query)
-    assert telegram._updater.bot.edit_message_text.call_count == 1
+    assert telegram._app.bot.edit_message_text.call_count == 1
     assert not log_has_re(r"TelegramError: .*", caplog)
 
-    telegram._updater.bot.edit_message_text = MagicMock(side_effect=BadRequest(""))
+    telegram._app.bot.edit_message_text = AsyncMock(side_effect=BadRequest(""))
     await telegram._send_msg('test2', callback_path="DeadBeef", query=query)
-    assert telegram._updater.bot.edit_message_text.call_count == 1
+    assert telegram._app.bot.edit_message_text.call_count == 1
     assert log_has_re(r"TelegramError: .*", caplog)
 
-    telegram._updater.bot.edit_message_text = MagicMock(side_effect=TelegramError("DeadBEEF"))
+    telegram._app.bot.edit_message_text = AsyncMock(side_effect=TelegramError("DeadBEEF"))
     await telegram._send_msg('test3', callback_path="DeadBeef", query=query)
 
     assert log_has_re(r"TelegramError: DeadBEEF! Giving up.*", caplog)
@@ -2363,8 +2364,8 @@ async def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
     bot = MagicMock()
     bot.send_message = MagicMock(side_effect=NetworkError('Oh snap'))
     telegram, _, _ = get_telegram_testobject(mocker, default_conf, mock=False)
-    telegram._updater = MagicMock()
-    telegram._updater.bot = bot
+    telegram._app = MagicMock()
+    telegram._app.bot = bot
 
     telegram._config['telegram']['enabled'] = True
     await telegram._send_msg('test')
@@ -2377,7 +2378,7 @@ async def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
 async def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
     bot = MagicMock()
-    bot.send_message = MagicMock()
+    bot.send_message = AsyncMock()
     freqtradebot = get_patched_freqtradebot(mocker, default_conf)
     rpc = RPC(freqtradebot)
 
@@ -2393,8 +2394,8 @@ async def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
 
     def init_telegram(freqtradebot):
         telegram = Telegram(rpc, default_conf)
-        telegram._updater = MagicMock()
-        telegram._updater.bot = bot
+        telegram._app = MagicMock()
+        telegram._app.bot = bot
         return telegram
 
     # no keyboard in config -> default keyboard

From cf0b37057cd74de028b1caefb6e57395d84a70be Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 16 Apr 2023 16:58:12 +0200
Subject: [PATCH 19/27] update telegram "cleanup" test

---
 tests/rpc/test_rpc_telegram.py | 23 +++++++++++++++++------
 1 file changed, 17 insertions(+), 6 deletions(-)

diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 1b84df1ca..b406ddc3a 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -2,6 +2,7 @@
 # pragma pylint: disable=protected-access, unused-argument, invalid-name
 # pragma pylint: disable=too-many-lines, too-many-arguments
 
+import asyncio
 import logging
 import re
 from datetime import datetime, timedelta, timezone
@@ -130,14 +131,24 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
     assert log_has(message_str, caplog)
 
 
-def test_cleanup(default_conf, mocker, ) -> None:
-    updater_mock = MagicMock()
-    updater_mock.stop = MagicMock()
-    mocker.patch('freqtrade.rpc.telegram.Updater', updater_mock)
+async def test_telegram_cleanup(default_conf, mocker, ) -> None:
+    app_mock = MagicMock()
+    app_mock.stop = AsyncMock()
+    app_mock.initialize = AsyncMock()
 
-    telegram, _, _ = get_telegram_testobject(mocker, default_conf, mock=False)
+    updater_mock = MagicMock()
+    updater_mock.stop = AsyncMock()
+    app_mock.updater = updater_mock
+    # mocker.patch('freqtrade.rpc.telegram.Application', app_mock)
+
+    telegram, _, _ = get_telegram_testobject(mocker, default_conf)
+    telegram._app = app_mock
+    telegram._loop = asyncio.get_running_loop()
+    telegram._thread = MagicMock()
     telegram.cleanup()
-    assert telegram._app.stop.call_count == 1
+    await asyncio.sleep(0.1)
+    assert app_mock.stop.call_count == 1
+    assert telegram._thread.join.call_count == 1
 
 
 async def test_authorized_only(default_conf, mocker, caplog, update) -> None:

From c9e6137ad0d472d910941ce24acbb379e2f44adc Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 16 Apr 2023 17:18:28 +0200
Subject: [PATCH 20/27] Fix test_telegram _init test

---
 freqtrade/rpc/telegram.py      |  5 ++++-
 tests/rpc/test_rpc_telegram.py | 15 +++++++++------
 2 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index babe7d582..d20d1862e 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -166,6 +166,9 @@ class Telegram(RPCHandler):
                 logger.info('using custom keyboard from '
                             f'config.json: {self._keyboard}')
 
+    def _init_telegram_app(self):
+        return Application.builder().token(self._config['telegram']['token']).build()
+
     def _init(self) -> None:
         """
         Initializes this module with the given config,
@@ -179,7 +182,7 @@ class Telegram(RPCHandler):
             self._loop = asyncio.new_event_loop()
             asyncio.set_event_loop(self._loop)
 
-        self._app = Application.builder().token(self._config['telegram']['token']).build()
+        self._app = self._init_telegram_app()
 
         # Register command handler and start telegram message polling
         handles = [
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index b406ddc3a..b762c999b 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -105,15 +105,18 @@ def test_telegram__init__(default_conf, mocker) -> None:
 
 
 def test_telegram_init(default_conf, mocker, caplog) -> None:
-    start_polling = MagicMock()
-    mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock(return_value=start_polling))
+    app_mock = MagicMock()
+    mocker.patch('freqtrade.rpc.telegram.Telegram._start_thread', MagicMock())
+    mocker.patch('freqtrade.rpc.telegram.Telegram._init_telegram_app', return_value=app_mock)
+    mocker.patch('freqtrade.rpc.telegram.Telegram._startup_telegram', AsyncMock())
 
-    get_telegram_testobject(mocker, default_conf, mock=False)
-    assert start_polling.call_count == 0
+    telegram, _, _ = get_telegram_testobject(mocker, default_conf, mock=False)
+    telegram._init()
+    assert app_mock.call_count == 0
 
     # number of handles registered
-    assert start_polling.dispatcher.add_handler.call_count > 0
-    assert start_polling.start_polling.call_count == 1
+    assert app_mock.add_handler.call_count > 0
+    # assert start_polling.start_polling.call_count == 1
 
     message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
                    "['balance'], ['start'], ['stop'], "

From 7171fd1132a9a81c210f1426833b8f383c3ae5da Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 16 Apr 2023 17:24:11 +0200
Subject: [PATCH 21/27] Test telegram startup

---
 tests/rpc/test_rpc_telegram.py | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index b762c999b..5fbd4f98e 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -134,6 +134,23 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
     assert log_has(message_str, caplog)
 
 
+async def test_telegram_startup(default_conf, mocker) -> None:
+    app_mock = MagicMock()
+    app_mock.initialize = AsyncMock()
+    app_mock.start = AsyncMock()
+    app_mock.updater.start_polling = AsyncMock()
+    app_mock.updater.running = False
+    sleep_mock = mocker.patch('freqtrade.rpc.telegram.asyncio.sleep',AsyncMock())
+
+    telegram, _, _ = get_telegram_testobject(mocker, default_conf)
+    telegram._app = app_mock
+    await telegram._startup_telegram()
+    assert app_mock.initialize.call_count == 1
+    assert app_mock.start.call_count == 1
+    assert app_mock.updater.start_polling.call_count == 1
+    assert sleep_mock.call_count == 1
+
+
 async def test_telegram_cleanup(default_conf, mocker, ) -> None:
     app_mock = MagicMock()
     app_mock.stop = AsyncMock()

From 5608aaca26789645acffdf3841607abcef36e06c Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 16 Apr 2023 17:45:56 +0200
Subject: [PATCH 22/27] Simplify mocking

---
 freqtrade/exchange/exchange.py | 8 ++++++--
 tests/rpc/test_rpc_telegram.py | 3 ++-
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index 7e276d538..669a5c7d1 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -107,8 +107,7 @@ class Exchange:
         # Lock event loop. This is necessary to avoid race-conditions when using force* commands
         # Due to funding fee fetching.
         self._loop_lock = Lock()
-        self.loop = asyncio.new_event_loop()
-        asyncio.set_event_loop(self.loop)
+        self.loop = self._init_async_loop()
         self._config: Config = {}
 
         self._config.update(config)
@@ -212,6 +211,11 @@ class Exchange:
         if self.loop and not self.loop.is_closed():
             self.loop.close()
 
+    def _init_async_loop(self):
+        loop = asyncio.new_event_loop()
+        asyncio.set_event_loop(loop)
+        return loop
+
     def validate_config(self, config):
         # Check if timeframe is available
         self.validate_timeframes(config.get('timeframe'))
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 5fbd4f98e..adfbb4b2a 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -89,6 +89,7 @@ def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None):
             _start_thread=MagicMock(),
         )
     if not ftbot:
+        mocker.patch('freqtrade.exchange.exchange.Exchange._init_async_loop')
         ftbot = get_patched_freqtradebot(mocker, default_conf)
     rpc = RPC(ftbot)
     telegram = Telegram(rpc, default_conf)
@@ -140,7 +141,7 @@ async def test_telegram_startup(default_conf, mocker) -> None:
     app_mock.start = AsyncMock()
     app_mock.updater.start_polling = AsyncMock()
     app_mock.updater.running = False
-    sleep_mock = mocker.patch('freqtrade.rpc.telegram.asyncio.sleep',AsyncMock())
+    sleep_mock = mocker.patch('freqtrade.rpc.telegram.asyncio.sleep', AsyncMock())
 
     telegram, _, _ = get_telegram_testobject(mocker, default_conf)
     telegram._app = app_mock

From d25e82d095956a132f6ae5ec3b465204cf8fcda6 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 16 Apr 2023 17:56:12 +0200
Subject: [PATCH 23/27] Mock exchange loop

---
 tests/rpc/test_rpc_telegram.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index adfbb4b2a..4601e64c4 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -37,6 +37,11 @@ from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades,
                             patch_exchange, patch_get_signal, patch_whitelist)
 
 
+@pytest.fixture(autouse=True)
+def mock_exchange_loop(mocker):
+    mocker.patch('freqtrade.exchange.exchange.Exchange._init_async_loop')
+
+
 @pytest.fixture
 def default_conf(default_conf) -> dict:
     # Telegram is enabled by default

From 516b49ff500cacd50222fa2946eae89805cbaf9b Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sun, 16 Apr 2023 18:15:48 +0200
Subject: [PATCH 24/27] Fix bad types

---
 freqtrade/optimize/hyperopt.py | 2 +-
 freqtrade/rpc/telegram.py      | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py
index fe590f0d2..bf6bb1ea0 100644
--- a/freqtrade/optimize/hyperopt.py
+++ b/freqtrade/optimize/hyperopt.py
@@ -556,7 +556,7 @@ class Hyperopt:
         self.backtesting.exchange.close()
         self.backtesting.exchange._api = None
         self.backtesting.exchange._api_async = None
-        self.backtesting.exchange.loop = None  # type: ignore
+        self.backtesting.exchange.loop = None
         self.backtesting.exchange._loop_lock = None  # type: ignore
         self.backtesting.exchange._cache_lock = None  # type: ignore
         # self.backtesting.exchange = None  # type: ignore
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index d20d1862e..a972dc322 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -1739,7 +1739,7 @@ class Telegram(RPCHandler):
                 [InlineKeyboardButton("Refresh", callback_data=callback_path)]])
         else:
             if keyboard is not None:
-                reply_markup = InlineKeyboardMarkup(keyboard, resize_keyboard=True)
+                reply_markup = InlineKeyboardMarkup(keyboard)
             else:
                 reply_markup = ReplyKeyboardMarkup(self._keyboard, resize_keyboard=True)
         try:

From c06759223eaa7698eefe885360926f199d3caed8 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Mon, 17 Apr 2023 06:40:18 +0200
Subject: [PATCH 25/27] Improve telegram async tests

---
 freqtrade/rpc/telegram.py      |  2 +-
 tests/rpc/test_rpc_telegram.py | 19 ++++++++++++++++++-
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index a972dc322..6e509950c 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -1796,7 +1796,7 @@ class Telegram(RPCHandler):
             if new_market_dir is not None:
                 self._rpc._update_market_direction(new_market_dir)
                 await self._send_msg("Successfully updated market direction"
-                               f" from *{old_market_dir}* to *{new_market_dir}*.")
+                                     f" from *{old_market_dir}* to *{new_market_dir}*.")
             else:
                 raise RPCException("Invalid market direction provided. \n"
                                    "Valid market directions: *long, short, even, none*")
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 4601e64c4..4b4c2b028 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -5,6 +5,7 @@
 import asyncio
 import logging
 import re
+import threading
 from datetime import datetime, timedelta, timezone
 from functools import reduce
 from random import choice, randint
@@ -57,6 +58,20 @@ def update():
     return _update
 
 
+def patch_eventloop_threading(telegrambot):
+    is_init = False
+
+    def thread_fuck():
+        nonlocal is_init
+        telegrambot._loop = asyncio.new_event_loop()
+        is_init = True
+        telegrambot._loop.run_forever()
+    x = threading.Thread(target=thread_fuck, daemon=True)
+    x.start()
+    while not is_init:
+        pass
+
+
 class DummyCls(Telegram):
     """
     Dummy class for testing the Telegram @authorized_only decorator
@@ -99,6 +114,7 @@ def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None):
     rpc = RPC(ftbot)
     telegram = Telegram(rpc, default_conf)
     telegram._loop = MagicMock()
+    patch_eventloop_threading(telegram)
 
     return telegram, ftbot, msg_mock
 
@@ -2216,7 +2232,7 @@ def test_send_msg_status_notification(default_conf, mocker) -> None:
     assert msg_mock.call_args[0][0] == '*Status:* `running`'
 
 
-def test_warning_notification(default_conf, mocker) -> None:
+async def test_warning_notification(default_conf, mocker) -> None:
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
     telegram.send_msg({
         'type': RPCMessageType.WARNING,
@@ -2412,6 +2428,7 @@ async def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
     assert log_has('Telegram NetworkError: Oh snap! Trying one more time.', caplog)
 
 
+@pytest.mark.filterwarnings("ignore:.*ChatPermissions")
 async def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
     bot = MagicMock()

From 2615b0297e5fa2c578baeafbe84497dcfa480fe7 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Wed, 19 Apr 2023 18:12:52 +0200
Subject: [PATCH 26/27] Move httpx to regular dependencies, losely-pin

---
 requirements-dev.txt | 2 --
 requirements.txt     | 2 ++
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/requirements-dev.txt b/requirements-dev.txt
index cd4c96eea..ea75bb8f2 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -18,8 +18,6 @@ pytest-random-order==1.1.0
 isort==5.12.0
 # For datetime mocking
 time-machine==2.9.0
-# fastapi testing
-httpx==0.24.0
 
 # Convert jupyter notebooks to markdown documents
 nbconvert==7.3.1
diff --git a/requirements.txt b/requirements.txt
index a07ef8897..6eecfb630 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,6 +7,8 @@ cryptography==40.0.2
 aiohttp==3.8.4
 SQLAlchemy==2.0.10
 python-telegram-bot==20.2
+# can't be hard-pinned due to telegram-bot pinning httpx with ~
+httpx>=0.23.3
 arrow==1.2.3
 cachetools==4.2.2
 requests==2.28.2

From b49ff3d5bcc99aa796bed83ebaf203418617b673 Mon Sep 17 00:00:00 2001
From: Matthias 
Date: Sat, 22 Apr 2023 11:11:03 +0200
Subject: [PATCH 27/27] Improve type safety

---
 freqtrade/exchange/exchange.py | 2 +-
 freqtrade/optimize/hyperopt.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index 669a5c7d1..6706e94ab 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -211,7 +211,7 @@ class Exchange:
         if self.loop and not self.loop.is_closed():
             self.loop.close()
 
-    def _init_async_loop(self):
+    def _init_async_loop(self) -> asyncio.AbstractEventLoop:
         loop = asyncio.new_event_loop()
         asyncio.set_event_loop(loop)
         return loop
diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py
index bf6bb1ea0..fe590f0d2 100644
--- a/freqtrade/optimize/hyperopt.py
+++ b/freqtrade/optimize/hyperopt.py
@@ -556,7 +556,7 @@ class Hyperopt:
         self.backtesting.exchange.close()
         self.backtesting.exchange._api = None
         self.backtesting.exchange._api_async = None
-        self.backtesting.exchange.loop = None
+        self.backtesting.exchange.loop = None  # type: ignore
         self.backtesting.exchange._loop_lock = None  # type: ignore
         self.backtesting.exchange._cache_lock = None  # type: ignore
         # self.backtesting.exchange = None  # type: ignore