From c1600188b608a502788e13d8d2b9f85874912eb9 Mon Sep 17 00:00:00 2001
From: ikocheev <ivan.kocheev@gmail.com>
Date: Mon, 27 Nov 2017 23:15:13 +0300
Subject: [PATCH] Force sell telergam inline keyboard selection

---
 freqtrade/rpc/telegram.py | 115 ++++++++++++++++++++++++++++++--------
 1 file changed, 92 insertions(+), 23 deletions(-)

diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index e2b472119..dce89acc3 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -7,9 +7,9 @@ import arrow
 from pandas import DataFrame
 from sqlalchemy import and_, func, text
 from tabulate import tabulate
-from telegram import ParseMode, Bot, Update, ReplyKeyboardMarkup
+from telegram import ParseMode, Bot, Update, ReplyKeyboardMarkup, InlineKeyboardButton, InlineKeyboardMarkup
 from telegram.error import NetworkError, TelegramError
-from telegram.ext import CommandHandler, Updater
+from telegram.ext import CommandHandler, Updater, CallbackQueryHandler
 
 from freqtrade import exchange, __version__
 from freqtrade.misc import get_state, State, update_state
@@ -51,10 +51,14 @@ def init(config: dict) -> None:
         CommandHandler('performance', _performance),
         CommandHandler('count', _count),
         CommandHandler('help', _help),
-        CommandHandler('version', _version),
+        CommandHandler('version', _version)
     ]
+
     for handle in handles:
         _UPDATER.dispatcher.add_handler(handle)
+
+    _UPDATER.dispatcher.add_handler(CallbackQueryHandler(_sell_callback))
+
     _UPDATER.start_polling(
         clean=True,
         bootstrap_retries=-1,
@@ -322,6 +326,47 @@ def _stop(bot: Bot, update: Update) -> None:
         send_msg('*Status:* `already stopped`', bot=bot)
 
 
+@authorized_only
+def _sell(bot: Bot, update: Update) -> None:
+    """
+    Handler for /sell <id>.
+    Sells the given trade at current price
+    :param bot: telegram bot
+    :param update: message update
+    :return: None
+    """
+    if get_state() != State.RUNNING:
+        send_msg('`trader is not running`', bot=bot)
+        return
+
+    trades = Trade.query.filter(Trade.is_open.is_(True)).all()
+    if not trades:
+        send_msg('no active trade', bot=bot)
+        return
+
+    button_list = []
+    for trade in trades:
+        # calculate profit and send message to user
+        current_rate = exchange.get_ticker(trade.pair)['bid']
+        current_profit = trade.calc_profit(current_rate)
+
+        message = '#{trade_id} [{pair}] ({current_profit:.2f}%)'.format(
+            trade_id=trade.id,
+            pair=trade.pair,
+            current_profit=round(current_profit * 100, 2)
+        )
+        button_list.append(InlineKeyboardButton(message, callback_data=trade.id))
+
+    reply_markup = InlineKeyboardMarkup(build_menu(button_list, n_cols=1))
+
+    send_msg('Choose order for sell:', inline_markup=reply_markup)
+
+
+def _sell_callback(bot, update):
+    query = update.callback_query
+    sell_order(query.data)
+
+
 @authorized_only
 def _forcesell(bot: Bot, update: Update) -> None:
     """
@@ -339,25 +384,10 @@ def _forcesell(bot: Bot, update: Update) -> None:
     if trade_id == 'all':
         # Execute sell for all open orders
         for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
-            # Get current rate
-            current_rate = exchange.get_ticker(trade.pair)['bid']
-            from freqtrade.main import execute_sell
-            execute_sell(trade, current_rate)
+            sell_order(trade.id)
         return
 
-    # Query for trade
-    trade = Trade.query.filter(and_(
-        Trade.id == trade_id,
-        Trade.is_open.is_(True)
-    )).first()
-    if not trade:
-        send_msg('Invalid argument. See `/help` to view usage')
-        logger.warning('/forcesell: Invalid argument received')
-        return
-    # Get current rate
-    current_rate = exchange.get_ticker(trade.pair)['bid']
-    from freqtrade.main import execute_sell
-    execute_sell(trade, current_rate)
+    sell_order(trade_id)
 
 
 @authorized_only
@@ -463,12 +493,15 @@ def shorten_date(date):
     return new_date
 
 
-def send_msg(msg: str, bot: Bot = None, parse_mode: ParseMode = ParseMode.MARKDOWN) -> None:
+def send_msg(msg: str, bot: Bot = None,
+             parse_mode: ParseMode = ParseMode.MARKDOWN,
+             inline_markup: InlineKeyboardMarkup = None) -> None:
     """
     Send given markdown message
     :param msg: message
     :param bot: alternative bot
     :param parse_mode: telegram parse mode
+    :param inline_markup: inline_markup
     :return: None
     """
     if not is_enabled():
@@ -478,9 +511,9 @@ def send_msg(msg: str, bot: Bot = None, parse_mode: ParseMode = ParseMode.MARKDO
 
     keyboard = [['/status table', '/profit', '/performance', ],
                 ['/balance', '/status', '/count'],
-                ['/start', '/stop', '/help']]
+                ['/start', '/stop', '/sell']]
 
-    reply_markup = ReplyKeyboardMarkup(keyboard)
+    reply_markup = inline_markup or ReplyKeyboardMarkup(keyboard)
 
     try:
         try:
@@ -495,3 +528,39 @@ def send_msg(msg: str, bot: Bot = None, parse_mode: ParseMode = ParseMode.MARKDO
             bot.send_message(_CONF['telegram']['chat_id'], msg, parse_mode=parse_mode, reply_markup=reply_markup)
     except TelegramError as telegram_err:
         logger.warning('Got TelegramError: %s! Giving up on that message.', telegram_err.message)
+
+
+def sell_order(trade_id) -> None:
+    """
+    Sell order by trade id
+    :param trade_id: trade id
+    :return: None
+    """
+    # Query for trade
+    trade = Trade.query.filter(and_(
+        Trade.id == trade_id,
+        Trade.is_open.is_(True)
+    )).first()
+    if not trade:
+        send_msg('Order #{} not found'.format(trade_id))
+        logger.warning('/forcesell: Invalid argument received')
+        return
+
+    # Get current rate
+    current_rate = exchange.get_ticker(trade.pair)['bid']
+    from freqtrade.main import execute_sell
+    execute_sell(trade, current_rate)
+
+
+def build_menu(buttons,
+               n_cols,
+               header_buttons=None,
+               footer_buttons=None):
+    menu = [buttons[i:i + n_cols] for i in range(0, len(buttons), n_cols)]
+    if header_buttons:
+        menu.insert(0, header_buttons)
+    if footer_buttons:
+        menu.append(footer_buttons)
+
+    return menu
+