Merge pull request #8394 from freqtrade/dependabot/pip/develop/python-telegram-bot-20.2

Bump python-telegram-bot from 13.15 to 20.2
This commit is contained in:
Matthias 2023-04-24 15:58:39 +02:00 committed by GitHub
commit 459e5e67bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 501 additions and 406 deletions

View File

@ -107,8 +107,7 @@ class Exchange:
# Lock event loop. This is necessary to avoid race-conditions when using force* commands # Lock event loop. This is necessary to avoid race-conditions when using force* commands
# Due to funding fee fetching. # Due to funding fee fetching.
self._loop_lock = Lock() self._loop_lock = Lock()
self.loop = asyncio.new_event_loop() self.loop = self._init_async_loop()
asyncio.set_event_loop(self.loop)
self._config: Config = {} self._config: Config = {}
self._config.update(config) self._config.update(config)
@ -212,6 +211,11 @@ class Exchange:
if self.loop and not self.loop.is_closed(): if self.loop and not self.loop.is_closed():
self.loop.close() self.loop.close()
def _init_async_loop(self) -> asyncio.AbstractEventLoop:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop
def validate_config(self, config): def validate_config(self, config):
# Check if timeframe is available # Check if timeframe is available
self.validate_timeframes(config.get('timeframe')) self.validate_timeframes(config.get('timeframe'))

View File

@ -3,6 +3,7 @@
""" """
This module manage Telegram communication This module manage Telegram communication
""" """
import asyncio
import json import json
import logging import logging
import re import re
@ -13,15 +14,17 @@ from functools import partial
from html import escape from html import escape
from itertools import chain from itertools import chain
from math import isnan from math import isnan
from typing import Any, Callable, Dict, List, Optional, Union from threading import Thread
from typing import Any, Callable, Coroutine, Dict, List, Optional, Union
import arrow import arrow
from tabulate import tabulate from tabulate import tabulate
from telegram import (MAX_MESSAGE_LENGTH, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, from telegram import (CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton,
KeyboardButton, ParseMode, ReplyKeyboardMarkup, Update) ReplyKeyboardMarkup, Update)
from telegram.constants import MessageLimit, ParseMode
from telegram.error import BadRequest, NetworkError, TelegramError from telegram.error import BadRequest, NetworkError, TelegramError
from telegram.ext import CallbackContext, CallbackQueryHandler, CommandHandler, Updater from telegram.ext import Application, CallbackContext, CallbackQueryHandler, CommandHandler
from telegram.utils.helpers import escape_markdown from telegram.helpers import escape_markdown
from freqtrade.__init__ import __version__ from freqtrade.__init__ import __version__
from freqtrade.constants import DUST_PER_COIN, Config from freqtrade.constants import DUST_PER_COIN, Config
@ -33,6 +36,9 @@ from freqtrade.rpc import RPC, RPCException, RPCHandler
from freqtrade.rpc.rpc_types import RPCSendMsg from freqtrade.rpc.rpc_types import RPCSendMsg
MAX_MESSAGE_LENGTH = MessageLimit.MAX_TEXT_LENGTH
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.debug('Included module rpc.telegram ...') logger.debug('Included module rpc.telegram ...')
@ -47,14 +53,14 @@ class TimeunitMappings:
default: int 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 Decorator to check if the message comes from the correct chat_id
:param command_handler: Telegram CommandHandler :param command_handler: Telegram CommandHandler
:return: decorated function :return: decorated function
""" """
def wrapper(self, *args, **kwargs): async def wrapper(self, *args, **kwargs):
""" Decorator logic """ """ Decorator logic """
update = kwargs.get('update') or args[0] update = kwargs.get('update') or args[0]
@ -76,9 +82,9 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
chat_id chat_id
) )
try: try:
return command_handler(self, *args, **kwargs) return await command_handler(self, *args, **kwargs)
except RPCException as e: except RPCException as e:
self._send_msg(str(e)) await self._send_msg(str(e))
except BaseException: except BaseException:
logger.exception('Exception occurred within Telegram module') logger.exception('Exception occurred within Telegram module')
finally: finally:
@ -99,9 +105,17 @@ class Telegram(RPCHandler):
""" """
super().__init__(rpc, config) super().__init__(rpc, config)
self._updater: Updater self._app: Application
self._loop: asyncio.AbstractEventLoop
self._init_keyboard() 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: def _init_keyboard(self) -> None:
""" """
@ -152,14 +166,23 @@ class Telegram(RPCHandler):
logger.info('using custom keyboard from ' logger.info('using custom keyboard from '
f'config.json: {self._keyboard}') f'config.json: {self._keyboard}')
def _init_telegram_app(self):
return Application.builder().token(self._config['telegram']['token']).build()
def _init(self) -> None: def _init(self) -> None:
""" """
Initializes this module with the given config, Initializes this module with the given config,
registers all known command handlers registers all known command handlers
and starts polling for message updates and starts polling for message updates
Runs in a separate thread.
""" """
self._updater = Updater(token=self._config['telegram']['token'], workers=0, try:
use_context=True) self._loop = asyncio.get_running_loop()
except RuntimeError:
self._loop = asyncio.new_event_loop()
asyncio.set_event_loop(self._loop)
self._app = self._init_telegram_app()
# Register command handler and start telegram message polling # Register command handler and start telegram message polling
handles = [ handles = [
@ -218,21 +241,38 @@ class Telegram(RPCHandler):
CallbackQueryHandler(self._force_enter_inline, pattern=r"\S+\/\S+"), CallbackQueryHandler(self._force_enter_inline, pattern=r"\S+\/\S+"),
] ]
for handle in handles: for handle in handles:
self._updater.dispatcher.add_handler(handle) self._app.add_handler(handle)
for callback in callbacks: for callback in callbacks:
self._updater.dispatcher.add_handler(callback) self._app.add_handler(callback)
self._updater.start_polling(
bootstrap_retries=-1,
timeout=20,
read_latency=60, # Assumed transmission latency
drop_pending_updates=True,
)
logger.info( logger.info(
'rpc.telegram is listening for following commands: %s', '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]
) )
self._loop.run_until_complete(self._startup_telegram())
async def _startup_telegram(self) -> None:
await self._app.initialize()
await self._app.start()
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:
if self._app.updater:
await self._app.updater.stop()
await self._app.stop()
await self._app.shutdown()
def cleanup(self) -> None: def cleanup(self) -> None:
""" """
@ -240,7 +280,8 @@ class Telegram(RPCHandler):
:return: None :return: None
""" """
# This can take up to `timeout` from the call to `start_polling`. # This can take up to `timeout` from the call to `start_polling`.
self._updater.stop() asyncio.run_coroutine_threadsafe(self._cleanup_telegram(), self._loop)
self._thread.join()
def _exchange_from_msg(self, msg: Dict[str, Any]) -> str: def _exchange_from_msg(self, msg: Dict[str, Any]) -> str:
""" """
@ -453,7 +494,9 @@ class Telegram(RPCHandler):
message = self.compose_message(deepcopy(msg), msg_type) # type: ignore message = self.compose_message(deepcopy(msg), msg_type) # type: ignore
if message: 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): def _get_sell_emoji(self, msg):
""" """
@ -536,7 +579,7 @@ class Telegram(RPCHandler):
return lines_detail return lines_detail
@authorized_only @authorized_only
def _status(self, update: Update, context: CallbackContext) -> None: async def _status(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /status. Handler for /status.
Returns the current TradeThread status Returns the current TradeThread status
@ -546,12 +589,12 @@ class Telegram(RPCHandler):
""" """
if context.args and 'table' in context.args: if context.args and 'table' in context.args:
self._status_table(update, context) await self._status_table(update, context)
return return
else: 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 <id>`. handler for `/status` and `/status <id>`.
@ -635,9 +678,9 @@ class Telegram(RPCHandler):
lines_detail = self._prepare_order_details( lines_detail = self._prepare_order_details(
r['orders'], r['quote_currency'], r['is_open']) r['orders'], r['quote_currency'], r['is_open'])
lines.extend(lines_detail if lines_detail else "") 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. Send status message.
""" """
@ -648,13 +691,13 @@ class Telegram(RPCHandler):
if (len(msg) + len(line) + 1) < MAX_MESSAGE_LENGTH: if (len(msg) + len(line) + 1) < MAX_MESSAGE_LENGTH:
msg += line + '\n' msg += line + '\n'
else: else:
self._send_msg(msg.format(**r)) await self._send_msg(msg.format(**r))
msg = "*Trade ID:* `{trade_id}` - continued\n" + line + '\n' msg = "*Trade ID:* `{trade_id}` - continued\n" + line + '\n'
self._send_msg(msg.format(**r)) await self._send_msg(msg.format(**r))
@authorized_only @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. Handler for /status table.
Returns the current TradeThread status in table format Returns the current TradeThread status in table format
@ -687,12 +730,11 @@ class Telegram(RPCHandler):
# insert separators line between Total # insert separators line between Total
lines = message.split("\n") lines = message.split("\n")
message = "\n".join(lines[:-1] + [lines[1]] + [lines[-1]]) message = "\n".join(lines[:-1] + [lines[1]] + [lines[-1]])
self._send_msg(f"<pre>{message}</pre>", parse_mode=ParseMode.HTML, await self._send_msg(f"<pre>{message}</pre>", parse_mode=ParseMode.HTML,
reload_able=True, callback_path="update_status_table", reload_able=True, callback_path="update_status_table",
query=update.callback_query) query=update.callback_query)
@authorized_only async def _timeunit_stats(self, update: Update, context: CallbackContext, unit: str) -> None:
def _timeunit_stats(self, update: Update, context: CallbackContext, unit: str) -> None:
""" """
Handler for /daily <n> Handler for /daily <n>
Returns a daily profit (in BTC) over the last n days. Returns a daily profit (in BTC) over the last n days.
@ -739,11 +781,11 @@ class Telegram(RPCHandler):
f'<b>{val.message} Profit over the last {timescale} {val.message2}</b>:\n' f'<b>{val.message} Profit over the last {timescale} {val.message2}</b>:\n'
f'<pre>{stats_tab}</pre>' f'<pre>{stats_tab}</pre>'
) )
self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True, await self._send_msg(message, parse_mode=ParseMode.HTML, reload_able=True,
callback_path=val.callback, query=update.callback_query) callback_path=val.callback, query=update.callback_query)
@authorized_only @authorized_only
def _daily(self, update: Update, context: CallbackContext) -> None: async def _daily(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /daily <n> Handler for /daily <n>
Returns a daily profit (in BTC) over the last n days. Returns a daily profit (in BTC) over the last n days.
@ -751,10 +793,10 @@ class Telegram(RPCHandler):
:param update: message update :param update: message update
:return: None :return: None
""" """
self._timeunit_stats(update, context, 'days') await self._timeunit_stats(update, context, 'days')
@authorized_only @authorized_only
def _weekly(self, update: Update, context: CallbackContext) -> None: async def _weekly(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /weekly <n> Handler for /weekly <n>
Returns a weekly profit (in BTC) over the last n weeks. Returns a weekly profit (in BTC) over the last n weeks.
@ -762,10 +804,10 @@ class Telegram(RPCHandler):
:param update: message update :param update: message update
:return: None :return: None
""" """
self._timeunit_stats(update, context, 'weeks') await self._timeunit_stats(update, context, 'weeks')
@authorized_only @authorized_only
def _monthly(self, update: Update, context: CallbackContext) -> None: async def _monthly(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /monthly <n> Handler for /monthly <n>
Returns a monthly profit (in BTC) over the last n months. Returns a monthly profit (in BTC) over the last n months.
@ -773,10 +815,10 @@ class Telegram(RPCHandler):
:param update: message update :param update: message update
:return: None :return: None
""" """
self._timeunit_stats(update, context, 'months') await self._timeunit_stats(update, context, 'months')
@authorized_only @authorized_only
def _profit(self, update: Update, context: CallbackContext) -> None: async def _profit(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /profit. Handler for /profit.
Returns a cumulative profit statistics. Returns a cumulative profit statistics.
@ -850,11 +892,11 @@ class Telegram(RPCHandler):
f"*Max Drawdown:* `{stats['max_drawdown']:.2%} " f"*Max Drawdown:* `{stats['max_drawdown']:.2%} "
f"({round_coin_value(stats['max_drawdown_abs'], stake_cur)})`" f"({round_coin_value(stats['max_drawdown_abs'], stake_cur)})`"
) )
self._send_msg(markdown_msg, reload_able=True, callback_path="update_profit", await self._send_msg(markdown_msg, reload_able=True, callback_path="update_profit",
query=update.callback_query) query=update.callback_query)
@authorized_only @authorized_only
def _stats(self, update: Update, context: CallbackContext) -> None: async def _stats(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /stats Handler for /stats
Show stats of recent trades Show stats of recent trades
@ -885,7 +927,7 @@ class Telegram(RPCHandler):
headers=['Exit Reason', 'Exits', 'Wins', 'Losses'] headers=['Exit Reason', 'Exits', 'Wins', 'Losses']
) )
if len(exit_reasons_tabulate) > 25: 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 = '' exit_reasons_msg = ''
durations = stats['durations'] durations = stats['durations']
@ -900,10 +942,10 @@ class Telegram(RPCHandler):
) )
msg = (f"""```\n{exit_reasons_msg}```\n```\n{duration_msg}```""") 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 @authorized_only
def _balance(self, update: Update, context: CallbackContext) -> None: async def _balance(self, update: Update, context: CallbackContext) -> None:
""" Handler for /balance """ """ Handler for /balance """
full_result = context.args and 'full' in context.args full_result = context.args and 'full' in context.args
result = self._rpc._rpc_balance(self._config['stake_currency'], result = self._rpc._rpc_balance(self._config['stake_currency'],
@ -957,7 +999,7 @@ class Telegram(RPCHandler):
# Handle overflowing message length # Handle overflowing message length
if len(output + curr_output) >= MAX_MESSAGE_LENGTH: if len(output + curr_output) >= MAX_MESSAGE_LENGTH:
self._send_msg(output) await self._send_msg(output)
output = curr_output output = curr_output
else: else:
output += curr_output output += curr_output
@ -981,11 +1023,11 @@ class Telegram(RPCHandler):
f"\t`{result['stake']}: {total_stake}`{stake_improve}\n" f"\t`{result['stake']}: {total_stake}`{stake_improve}\n"
f"\t`{result['symbol']}: {value}`{fiat_val}\n" f"\t`{result['symbol']}: {value}`{fiat_val}\n"
) )
self._send_msg(output, reload_able=True, callback_path="update_balance", await self._send_msg(output, reload_able=True, callback_path="update_balance",
query=update.callback_query) query=update.callback_query)
@authorized_only @authorized_only
def _start(self, update: Update, context: CallbackContext) -> None: async def _start(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /start. Handler for /start.
Starts TradeThread Starts TradeThread
@ -994,10 +1036,10 @@ class Telegram(RPCHandler):
:return: None :return: None
""" """
msg = self._rpc._rpc_start() msg = self._rpc._rpc_start()
self._send_msg(f"Status: `{msg['status']}`") await self._send_msg(f"Status: `{msg['status']}`")
@authorized_only @authorized_only
def _stop(self, update: Update, context: CallbackContext) -> None: async def _stop(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /stop. Handler for /stop.
Stops TradeThread Stops TradeThread
@ -1006,10 +1048,10 @@ class Telegram(RPCHandler):
:return: None :return: None
""" """
msg = self._rpc._rpc_stop() msg = self._rpc._rpc_stop()
self._send_msg(f"Status: `{msg['status']}`") await self._send_msg(f"Status: `{msg['status']}`")
@authorized_only @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. Handler for /reload_config.
Triggers a config file reload Triggers a config file reload
@ -1018,10 +1060,10 @@ class Telegram(RPCHandler):
:return: None :return: None
""" """
msg = self._rpc._rpc_reload_config() msg = self._rpc._rpc_reload_config()
self._send_msg(f"Status: `{msg['status']}`") await self._send_msg(f"Status: `{msg['status']}`")
@authorized_only @authorized_only
def _stopentry(self, update: Update, context: CallbackContext) -> None: async def _stopentry(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /stop_buy. Handler for /stop_buy.
Sets max_open_trades to 0 and gracefully sells all open trades Sets max_open_trades to 0 and gracefully sells all open trades
@ -1030,10 +1072,10 @@ class Telegram(RPCHandler):
:return: None :return: None
""" """
msg = self._rpc._rpc_stopentry() msg = self._rpc._rpc_stopentry()
self._send_msg(f"Status: `{msg['status']}`") await self._send_msg(f"Status: `{msg['status']}`")
@authorized_only @authorized_only
def _force_exit(self, update: Update, context: CallbackContext) -> None: async def _force_exit(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /forceexit <id>. Handler for /forceexit <id>.
Sells the given trade at current price Sells the given trade at current price
@ -1044,14 +1086,14 @@ class Telegram(RPCHandler):
if context.args: if context.args:
trade_id = context.args[0] trade_id = context.args[0]
self._force_exit_action(trade_id) await self._force_exit_action(trade_id)
else: else:
fiat_currency = self._config.get('fiat_display_currency', '') fiat_currency = self._config.get('fiat_display_currency', '')
try: try:
statlist, _, _ = self._rpc._rpc_status_table( statlist, _, _ = self._rpc._rpc_status_table(
self._config['stake_currency'], fiat_currency) self._config['stake_currency'], fiat_currency)
except RPCException: except RPCException:
self._send_msg(msg='No open trade found.') await self._send_msg(msg='No open trade found.')
return return
trades = [] trades = []
for trade in statlist: for trade in statlist:
@ -1064,51 +1106,51 @@ class Telegram(RPCHandler):
buttons_aligned.append([InlineKeyboardButton( buttons_aligned.append([InlineKeyboardButton(
text='Cancel', callback_data='force_exit__cancel')]) 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': if trade_id != 'cancel':
try: try:
self._rpc._rpc_force_exit(trade_id) self._rpc._rpc_force_exit(trade_id)
except RPCException as e: 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: if update.callback_query:
query = update.callback_query query = update.callback_query
if query.data and '__' in query.data: if query.data and '__' in query.data:
# Input data is "force_exit__<tradid|cancel>" # Input data is "force_exit__<tradid|cancel>"
trade_id = query.data.split("__")[1].split(' ')[0] trade_id = query.data.split("__")[1].split(' ')[0]
if trade_id == 'cancel': if trade_id == 'cancel':
query.answer() await query.answer()
query.edit_message_text(text="Force exit canceled.") await query.edit_message_text(text="Force exit canceled.")
return return
trade: Optional[Trade] = Trade.get_trades(trade_filter=Trade.id == trade_id).first() trade: Optional[Trade] = Trade.get_trades(trade_filter=Trade.id == trade_id).first()
query.answer() await query.answer()
if trade: if trade:
query.edit_message_text( await query.edit_message_text(
text=f"Manually exiting Trade #{trade_id}, {trade.pair}") text=f"Manually exiting Trade #{trade_id}, {trade.pair}")
self._force_exit_action(trade_id) await self._force_exit_action(trade_id)
else: 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': if pair != 'cancel':
try: try:
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: except RPCException as e:
logger.exception("Forcebuy error!") 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: if update.callback_query:
query = update.callback_query query = update.callback_query
if query.data and '_||_' in query.data: if query.data and '_||_' in query.data:
pair, side = query.data.split('_||_') pair, side = query.data.split('_||_')
order_side = SignalDirection(side) order_side = SignalDirection(side)
query.answer() await query.answer()
query.edit_message_text(text=f"Manually entering {order_side} for {pair}") await query.edit_message_text(text=f"Manually entering {order_side} for {pair}")
self._force_enter_action(pair, None, order_side) await self._force_enter_action(pair, None, order_side)
@staticmethod @staticmethod
def _layout_inline_keyboard( def _layout_inline_keyboard(
@ -1121,7 +1163,7 @@ class Telegram(RPCHandler):
return [buttons[i:i + cols] for i in range(0, len(buttons), cols)] return [buttons[i:i + cols] for i in range(0, len(buttons), cols)]
@authorized_only @authorized_only
def _force_enter( async def _force_enter(
self, update: Update, context: CallbackContext, order_side: SignalDirection) -> None: self, update: Update, context: CallbackContext, order_side: SignalDirection) -> None:
""" """
Handler for /forcelong <asset> <price> and `/forceshort <asset> <price> Handler for /forcelong <asset> <price> and `/forceshort <asset> <price>
@ -1133,7 +1175,7 @@ class Telegram(RPCHandler):
if context.args: if context.args:
pair = context.args[0] pair = context.args[0]
price = float(context.args[1]) if len(context.args) > 1 else None price = float(context.args[1]) if len(context.args) > 1 else None
self._force_enter_action(pair, price, order_side) await self._force_enter_action(pair, price, order_side)
else: else:
whitelist = self._rpc._rpc_whitelist()['whitelist'] whitelist = self._rpc._rpc_whitelist()['whitelist']
pair_buttons = [ pair_buttons = [
@ -1143,12 +1185,12 @@ class Telegram(RPCHandler):
buttons_aligned = self._layout_inline_keyboard(pair_buttons) buttons_aligned = self._layout_inline_keyboard(pair_buttons)
buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')])
self._send_msg(msg="Which pair?", await self._send_msg(msg="Which pair?",
keyboard=buttons_aligned, keyboard=buttons_aligned,
query=update.callback_query) query=update.callback_query)
@authorized_only @authorized_only
def _trades(self, update: Update, context: CallbackContext) -> None: async def _trades(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /trades <n> Handler for /trades <n>
Returns last n recent trades. Returns last n recent trades.
@ -1177,10 +1219,10 @@ class Telegram(RPCHandler):
tablefmt='simple') tablefmt='simple')
message = (f"<b>{min(trades['trades_count'], nrecent)} recent trades</b>:\n" message = (f"<b>{min(trades['trades_count'], nrecent)} recent trades</b>:\n"
+ (f"<pre>{trades_tab}</pre>" if trades['trades_count'] > 0 else '')) + (f"<pre>{trades_tab}</pre>" 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 @authorized_only
def _delete_trade(self, update: Update, context: CallbackContext) -> None: async def _delete_trade(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /delete <id>. Handler for /delete <id>.
Delete the given trade Delete the given trade
@ -1192,13 +1234,13 @@ class Telegram(RPCHandler):
raise RPCException("Trade-id not set.") raise RPCException("Trade-id not set.")
trade_id = int(context.args[0]) trade_id = int(context.args[0])
msg = self._rpc._rpc_delete(trade_id) msg = self._rpc._rpc_delete(trade_id)
self._send_msg( await self._send_msg(
f"`{msg['result_msg']}`\n" f"`{msg['result_msg']}`\n"
'Please make sure to take care of this asset on the exchange manually.' 'Please make sure to take care of this asset on the exchange manually.'
) )
@authorized_only @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 <id>. Handler for /cancel_open_order <id>.
Cancel open order for tradeid Cancel open order for tradeid
@ -1210,10 +1252,10 @@ class Telegram(RPCHandler):
raise RPCException("Trade-id not set.") raise RPCException("Trade-id not set.")
trade_id = int(context.args[0]) trade_id = int(context.args[0])
self._rpc._rpc_cancel_open_order(trade_id) self._rpc._rpc_cancel_open_order(trade_id)
self._send_msg('Open order canceled.') await self._send_msg('Open order canceled.')
@authorized_only @authorized_only
def _performance(self, update: Update, context: CallbackContext) -> None: async def _performance(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /performance. Handler for /performance.
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
@ -1231,17 +1273,17 @@ class Telegram(RPCHandler):
f"({trade['count']})</code>\n") f"({trade['count']})</code>\n")
if len(output + stat_line) >= MAX_MESSAGE_LENGTH: 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 output = stat_line
else: else:
output += stat_line output += stat_line
self._send_msg(output, parse_mode=ParseMode.HTML, await self._send_msg(output, parse_mode=ParseMode.HTML,
reload_able=True, callback_path="update_performance", reload_able=True, callback_path="update_performance",
query=update.callback_query) query=update.callback_query)
@authorized_only @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 . Handler for /buys PAIR .
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
@ -1263,17 +1305,17 @@ class Telegram(RPCHandler):
f"({trade['count']})</code>\n") f"({trade['count']})</code>\n")
if len(output + stat_line) >= MAX_MESSAGE_LENGTH: 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 output = stat_line
else: else:
output += stat_line output += stat_line
self._send_msg(output, parse_mode=ParseMode.HTML, await self._send_msg(output, parse_mode=ParseMode.HTML,
reload_able=True, callback_path="update_enter_tag_performance", reload_able=True, callback_path="update_enter_tag_performance",
query=update.callback_query) query=update.callback_query)
@authorized_only @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. Handler for /sells.
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
@ -1295,17 +1337,17 @@ class Telegram(RPCHandler):
f"({trade['count']})</code>\n") f"({trade['count']})</code>\n")
if len(output + stat_line) >= MAX_MESSAGE_LENGTH: 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 output = stat_line
else: else:
output += stat_line output += stat_line
self._send_msg(output, parse_mode=ParseMode.HTML, await self._send_msg(output, parse_mode=ParseMode.HTML,
reload_able=True, callback_path="update_exit_reason_performance", reload_able=True, callback_path="update_exit_reason_performance",
query=update.callback_query) query=update.callback_query)
@authorized_only @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. Handler for /mix_tags.
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
@ -1327,17 +1369,17 @@ class Telegram(RPCHandler):
f"({trade['count']})</code>\n") f"({trade['count']})</code>\n")
if len(output + stat_line) >= MAX_MESSAGE_LENGTH: 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 output = stat_line
else: else:
output += stat_line output += stat_line
self._send_msg(output, parse_mode=ParseMode.HTML, await self._send_msg(output, parse_mode=ParseMode.HTML,
reload_able=True, callback_path="update_mix_tag_performance", reload_able=True, callback_path="update_mix_tag_performance",
query=update.callback_query) query=update.callback_query)
@authorized_only @authorized_only
def _count(self, update: Update, context: CallbackContext) -> None: async def _count(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /count. Handler for /count.
Returns the number of trades running Returns the number of trades running
@ -1351,19 +1393,19 @@ class Telegram(RPCHandler):
tablefmt='simple') tablefmt='simple')
message = f"<pre>{message}</pre>" message = f"<pre>{message}</pre>"
logger.debug(message) logger.debug(message)
self._send_msg(message, parse_mode=ParseMode.HTML, await self._send_msg(message, parse_mode=ParseMode.HTML,
reload_able=True, callback_path="update_count", reload_able=True, callback_path="update_count",
query=update.callback_query) query=update.callback_query)
@authorized_only @authorized_only
def _locks(self, update: Update, context: CallbackContext) -> None: async def _locks(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /locks. Handler for /locks.
Returns the currently active locks Returns the currently active locks
""" """
rpc_locks = self._rpc._rpc_locks() rpc_locks = self._rpc._rpc_locks()
if not rpc_locks['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): for locks in chunks(rpc_locks['locks'], 25):
message = tabulate([[ message = tabulate([[
@ -1375,10 +1417,10 @@ class Telegram(RPCHandler):
tablefmt='simple') tablefmt='simple')
message = f"<pre>{escape(message)}</pre>" message = f"<pre>{escape(message)}</pre>"
logger.debug(message) logger.debug(message)
self._send_msg(message, parse_mode=ParseMode.HTML) await self._send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only @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. Handler for /delete_locks.
Returns the currently active locks Returns the currently active locks
@ -1393,10 +1435,10 @@ class Telegram(RPCHandler):
pair = arg pair = arg
self._rpc._rpc_delete_lock(lockid=lockid, pair=pair) self._rpc._rpc_delete_lock(lockid=lockid, pair=pair)
self._locks(update, context) await self._locks(update, context)
@authorized_only @authorized_only
def _whitelist(self, update: Update, context: CallbackContext) -> None: async def _whitelist(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /whitelist Handler for /whitelist
Shows the currently active whitelist Shows the currently active whitelist
@ -1413,39 +1455,39 @@ class Telegram(RPCHandler):
message += f"`{', '.join(whitelist['whitelist'])}`" message += f"`{', '.join(whitelist['whitelist'])}`"
logger.debug(message) logger.debug(message)
self._send_msg(message) await self._send_msg(message)
@authorized_only @authorized_only
def _blacklist(self, update: Update, context: CallbackContext) -> None: async def _blacklist(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /blacklist Handler for /blacklist
Shows the currently active 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 = [] errmsgs = []
for pair, error in blacklist['errors'].items(): for pair, error in blacklist['errors'].items():
errmsgs.append(f"Error: {error['error_msg']}") errmsgs.append(f"Error: {error['error_msg']}")
if errmsgs: 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"Blacklist contains {blacklist['length']} pairs\n"
message += f"`{', '.join(blacklist['blacklist'])}`" message += f"`{', '.join(blacklist['blacklist'])}`"
logger.debug(message) logger.debug(message)
self._send_msg(message) await self._send_msg(message)
@authorized_only @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 Handler for /bl_delete
Deletes pair(s) from current blacklist 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 @authorized_only
def _logs(self, update: Update, context: CallbackContext) -> None: async def _logs(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /logs Handler for /logs
Shows the latest logs Shows the latest logs
@ -1464,17 +1506,17 @@ class Telegram(RPCHandler):
escape_markdown(logrec[4], version=2)) escape_markdown(logrec[4], version=2))
if len(msgs + msg) + 10 >= MAX_MESSAGE_LENGTH: if len(msgs + msg) + 10 >= MAX_MESSAGE_LENGTH:
# Send message immediately if it would become too long # 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' msgs = msg + '\n'
else: else:
# Append message to messages to send # Append message to messages to send
msgs += msg + '\n' msgs += msg + '\n'
if msgs: if msgs:
self._send_msg(msgs, parse_mode=ParseMode.MARKDOWN_V2) await self._send_msg(msgs, parse_mode=ParseMode.MARKDOWN_V2)
@authorized_only @authorized_only
def _edge(self, update: Update, context: CallbackContext) -> None: async def _edge(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /edge Handler for /edge
Shows information related to Edge Shows information related to Edge
@ -1482,17 +1524,17 @@ class Telegram(RPCHandler):
edge_pairs = self._rpc._rpc_edge() edge_pairs = self._rpc._rpc_edge()
if not edge_pairs: if not edge_pairs:
message = '<b>Edge only validated following pairs:</b>' message = '<b>Edge only validated following pairs:</b>'
self._send_msg(message, parse_mode=ParseMode.HTML) await self._send_msg(message, parse_mode=ParseMode.HTML)
for chunk in chunks(edge_pairs, 25): for chunk in chunks(edge_pairs, 25):
edge_pairs_tab = tabulate(chunk, headers='keys', tablefmt='simple') edge_pairs_tab = tabulate(chunk, headers='keys', tablefmt='simple')
message = (f'<b>Edge only validated following pairs:</b>\n' message = (f'<b>Edge only validated following pairs:</b>\n'
f'<pre>{edge_pairs_tab}</pre>') f'<pre>{edge_pairs_tab}</pre>')
self._send_msg(message, parse_mode=ParseMode.HTML) await self._send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only @authorized_only
def _help(self, update: Update, context: CallbackContext) -> None: async def _help(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /help. Handler for /help.
Show commands of the bot Show commands of the bot
@ -1570,20 +1612,20 @@ class Telegram(RPCHandler):
"*/version:* `Show version`" "*/version:* `Show version`"
) )
self._send_msg(message, parse_mode=ParseMode.MARKDOWN) await self._send_msg(message, parse_mode=ParseMode.MARKDOWN)
@authorized_only @authorized_only
def _health(self, update: Update, context: CallbackContext) -> None: async def _health(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /health Handler for /health
Shows the last process timestamp Shows the last process timestamp
""" """
health = self._rpc.health() health = self._rpc.health()
message = f"Last process: `{health['last_process_loc']}`" message = f"Last process: `{health['last_process_loc']}`"
self._send_msg(message) await self._send_msg(message)
@authorized_only @authorized_only
def _version(self, update: Update, context: CallbackContext) -> None: async def _version(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /version. Handler for /version.
Show version information Show version information
@ -1596,10 +1638,10 @@ class Telegram(RPCHandler):
if strategy_version is not None: if strategy_version is not None:
version_string += f', *Strategy version: * `{strategy_version}`' version_string += f', *Strategy version: * `{strategy_version}`'
self._send_msg(version_string) await self._send_msg(version_string)
@authorized_only @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. Handler for /show_config.
Show config information information Show config information information
@ -1628,7 +1670,7 @@ class Telegram(RPCHandler):
else: else:
pa_info = "*Position adjustment:* Off\n" 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"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n"
f"*Exchange:* `{val['exchange']}`\n" f"*Exchange:* `{val['exchange']}`\n"
f"*Market: * `{val['trading_mode']}`\n" f"*Market: * `{val['trading_mode']}`\n"
@ -1644,8 +1686,8 @@ class Telegram(RPCHandler):
f"*Current state:* `{val['state']}`" f"*Current state:* `{val['state']}`"
) )
def _update_msg(self, query: CallbackQuery, msg: str, callback_path: str = "", async def _update_msg(self, query: CallbackQuery, msg: str, callback_path: str = "",
reload_able: bool = False, parse_mode: str = ParseMode.MARKDOWN) -> None: reload_able: bool = False, parse_mode: str = ParseMode.MARKDOWN) -> None:
if reload_able: if reload_able:
reply_markup = InlineKeyboardMarkup([ reply_markup = InlineKeyboardMarkup([
[InlineKeyboardButton("Refresh", callback_data=callback_path)], [InlineKeyboardButton("Refresh", callback_data=callback_path)],
@ -1659,7 +1701,7 @@ class Telegram(RPCHandler):
message_id = query.message.message_id message_id = query.message.message_id
try: try:
self._updater.bot.edit_message_text( await self._app.bot.edit_message_text(
chat_id=chat_id, chat_id=chat_id,
message_id=message_id, message_id=message_id,
text=msg, text=msg,
@ -1674,12 +1716,12 @@ class Telegram(RPCHandler):
except TelegramError as telegram_err: except TelegramError as telegram_err:
logger.warning('TelegramError: %s! Giving up on that message.', telegram_err.message) logger.warning('TelegramError: %s! Giving up on that message.', telegram_err.message)
def _send_msg(self, msg: str, parse_mode: str = ParseMode.MARKDOWN, async def _send_msg(self, msg: str, parse_mode: str = ParseMode.MARKDOWN,
disable_notification: bool = False, disable_notification: bool = False,
keyboard: Optional[List[List[InlineKeyboardButton]]] = None, keyboard: Optional[List[List[InlineKeyboardButton]]] = None,
callback_path: str = "", callback_path: str = "",
reload_able: bool = False, reload_able: bool = False,
query: Optional[CallbackQuery] = None) -> None: query: Optional[CallbackQuery] = None) -> None:
""" """
Send given markdown message Send given markdown message
:param msg: message :param msg: message
@ -1689,20 +1731,20 @@ class Telegram(RPCHandler):
""" """
reply_markup: Union[InlineKeyboardMarkup, ReplyKeyboardMarkup] reply_markup: Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]
if query: if query:
self._update_msg(query=query, msg=msg, parse_mode=parse_mode, await self._update_msg(query=query, msg=msg, parse_mode=parse_mode,
callback_path=callback_path, reload_able=reload_able) callback_path=callback_path, reload_able=reload_able)
return return
if reload_able and self._config['telegram'].get('reload', True): if reload_able and self._config['telegram'].get('reload', True):
reply_markup = InlineKeyboardMarkup([ reply_markup = InlineKeyboardMarkup([
[InlineKeyboardButton("Refresh", callback_data=callback_path)]]) [InlineKeyboardButton("Refresh", callback_data=callback_path)]])
else: else:
if keyboard is not None: if keyboard is not None:
reply_markup = InlineKeyboardMarkup(keyboard, resize_keyboard=True) reply_markup = InlineKeyboardMarkup(keyboard)
else: else:
reply_markup = ReplyKeyboardMarkup(self._keyboard, resize_keyboard=True) reply_markup = ReplyKeyboardMarkup(self._keyboard, resize_keyboard=True)
try: try:
try: try:
self._updater.bot.send_message( await self._app.bot.send_message(
self._config['telegram']['chat_id'], self._config['telegram']['chat_id'],
text=msg, text=msg,
parse_mode=parse_mode, parse_mode=parse_mode,
@ -1716,7 +1758,7 @@ class Telegram(RPCHandler):
'Telegram NetworkError: %s! Trying one more time.', 'Telegram NetworkError: %s! Trying one more time.',
network_err.message network_err.message
) )
self._updater.bot.send_message( await self._app.bot.send_message(
self._config['telegram']['chat_id'], self._config['telegram']['chat_id'],
text=msg, text=msg,
parse_mode=parse_mode, parse_mode=parse_mode,
@ -1730,7 +1772,7 @@ class Telegram(RPCHandler):
) )
@authorized_only @authorized_only
def _changemarketdir(self, update: Update, context: CallbackContext) -> None: async def _changemarketdir(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /marketdir. Handler for /marketdir.
Updates the bot's market_direction Updates the bot's market_direction
@ -1753,14 +1795,14 @@ class Telegram(RPCHandler):
if new_market_dir is not None: if new_market_dir is not None:
self._rpc._update_market_direction(new_market_dir) 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}*.") f" from *{old_market_dir}* to *{new_market_dir}*.")
else: else:
raise RPCException("Invalid market direction provided. \n" raise RPCException("Invalid market direction provided. \n"
"Valid market directions: *long, short, even, none*") "Valid market directions: *long, short, even, none*")
elif context.args is not None and len(context.args) == 0: elif context.args is not None and len(context.args) == 0:
old_market_dir = self._rpc._get_market_direction() 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: else:
raise RPCException("Invalid usage of command /marketdir. \n" raise RPCException("Invalid usage of command /marketdir. \n"
"Usage: */marketdir [short | long | even | none]*") "Usage: */marketdir [short | long | even | none]*")

View File

@ -18,8 +18,6 @@ pytest-random-order==1.1.0
isort==5.12.0 isort==5.12.0
# For datetime mocking # For datetime mocking
time-machine==2.9.0 time-machine==2.9.0
# fastapi testing
httpx==0.24.0
# Convert jupyter notebooks to markdown documents # Convert jupyter notebooks to markdown documents
nbconvert==7.3.1 nbconvert==7.3.1

View File

@ -6,7 +6,9 @@ ccxt==3.0.75
cryptography==40.0.2 cryptography==40.0.2
aiohttp==3.8.4 aiohttp==3.8.4
SQLAlchemy==2.0.10 SQLAlchemy==2.0.10
python-telegram-bot==13.15 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 arrow==1.2.3
cachetools==4.2.2 cachetools==4.2.2
requests==2.28.2 requests==2.28.2

View File

@ -3,7 +3,7 @@ import json
import logging import logging
import re import re
from copy import deepcopy from copy import deepcopy
from datetime import datetime, timedelta from datetime import timedelta
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
from unittest.mock import MagicMock, Mock, PropertyMock from unittest.mock import MagicMock, Mock, PropertyMock
@ -12,7 +12,6 @@ import arrow
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import pytest import pytest
from telegram import Chat, Message, Update
from freqtrade import constants from freqtrade import constants
from freqtrade.commands import Arguments from freqtrade.commands import Arguments
@ -550,13 +549,6 @@ def get_default_conf_usdt(testdatadir):
return configuration return configuration
@pytest.fixture
def update():
_update = Update(0)
_update.message = Message(0, datetime.utcnow(), Chat(0, 0))
return _update
@pytest.fixture @pytest.fixture
def fee(): def fee():
return MagicMock(return_value=0.0025) return MagicMock(return_value=0.0025)

View File

@ -283,7 +283,7 @@ def test_api__init__(default_conf, mocker):
"username": "TestUser", "username": "TestUser",
"password": "testPass", "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()) mocker.patch('freqtrade.rpc.api_server.webserver.ApiServer.start_api', MagicMock())
apiserver = ApiServer(default_conf) apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, 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", "username": "TestUser",
"password": "testPass", "password": "testPass",
}}) }})
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init')
server_inst_mock = MagicMock() server_inst_mock = MagicMock()
server_inst_mock.run_in_thread = MagicMock() server_inst_mock.run_in_thread = MagicMock()
@ -419,7 +419,7 @@ def test_api_cleanup(default_conf, mocker, caplog):
"username": "TestUser", "username": "TestUser",
"password": "testPass", "password": "testPass",
}}) }})
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init')
server_mock = MagicMock() server_mock = MagicMock()
server_mock.cleanup = MagicMock() server_mock.cleanup = MagicMock()
@ -1877,7 +1877,7 @@ def test_api_ws_send_msg(default_conf, mocker, caplog):
"password": _TEST_PASS, "password": _TEST_PASS,
"ws_token": _TEST_WS_TOKEN "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') mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api')
apiserver = ApiServer(default_conf) apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf))) apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))

File diff suppressed because it is too large Load Diff