mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-13 03:33:55 +00:00
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:
commit
459e5e67bd
|
@ -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'))
|
||||||
|
|
|
@ -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]*")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user