|
|
|
@@ -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]*")
|
|
|
|
|