Repository: freqtrade/freqtrade

Issue:
Optimized Telegram Output 🤖
**For all those guys who wants a better visual Telegram Output. Specially on Smartphones its very useful for a quick look into nofication-list.
Its possible to format the telegram Output in the "telegram.py" file like this...**

![image](https://user-images.githubusercontent.com/39638559/68532532-9449fb00-031e-11ea-86f9-306d8a67c581.png)
![Telegram_G3JmQx0SgZ](https://user-images.githubusercontent.com/39638559/68532396-ff92cd80-031c-11ea-82e1-3fe6042ce7ac.png)
![image](https://user-images.githubusercontent.com/39638559/68532398-09b4cc00-031d-11ea-8353-ccedabf4ca63.png)

**you only have to set your emotes from https://emojipedia.org/ in the "telegram.py"-file like this**

![image](https://user-images.githubusercontent.com/39638559/68532442-74fe9e00-031d-11ea-87be-c6c55cab537a.png)


**thats it 👍**

**_btw here is my example file_** 

[telegram.zip](https://github.com/freqtrade/freqtrade/files/3827523/telegram.zip)


ENJOY 🤖







Discussion:
Now, i personally would want to see sells in either Green or Red - depending on the outcome (positive / negative) ...
and buys in blue (or some neutral color ...).

Apart from that (which are visual details ...) - why is this an issue and not a PR, where you're proposing to change this so everyone can benefit? :smile: 
I indeed would like something mathias said. I quickly made this, there are probably better ways to do this tho, but it works :)
``` python
        elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
            msg['amount'] = round(msg['amount'], 8)
            msg['profit_percent'] = round(msg['profit_percent'] * 100, 2)

            message = ("*{exchange}:* Selling {pair}\n"
                       "*Rate:* `{limit:.8f}`\n"
                       "*Amount:* `{amount:.8f}`\n"
                       "*Open Rate:* `{open_rate:.8f}`\n"
                       "*Current Rate:* `{current_rate:.8f}`\n"
                       "*Sell Reason:* `{sell_reason}`\n").format(**msg)

            if float(msg['profit_percent']) > 0:
                message += ('💲*Profit:* `{profit_percent:.2f}%`').format(**msg)

            elif float(msg['profit_percent']) > 5:
                message += ('🚀*Profit:* `{profit_percent:.2f}%`').format(**msg)

            elif float(msg['profit_percent']) <= 0:
                message += ('❌*Profit:* `{profit_percent:.2f}%`').format(**msg)
```
Tweaked it a little since the 💲 is grey in Telegram so you don't really see it. Now it's just some nice green emoji. For buying I added a neutral blue color. I also made all the emojis at the beginning of the message so you would see it immediately and also in the notifications. And it now also shows another emoji when it hits your stop loss value. (Emojis in code don't match up with what they really look like in Telegram).

``` python
message = ("🔵 *{exchange}:* Buying {pair}\n"
                       "at rate `{limit:.8f}\n"
                       "({stake_amount:.6f} {stake_currency}").format(**msg)
```

``` python
            if float(msg['profit_percent']) >= 0:
                message = ("✳ *{exchange}:* Selling {pair}\n").format(**msg)

            elif float(msg['profit_percent']) >= 5:
                message = ("🚀 *{exchange}:* Selling {pair}\n").format(**msg)

            elif msg['sell_reason'] == "stop_loss":
                message = ("⚠ *{exchange}:* Selling {pair}\n").format(**msg)

            else:
                message = ("❌ *{exchange}:* Selling {pair}\n").format(**msg)

            message += ("*Rate:* `{limit:.8f}`\n"
                       "*Amount:* `{amount:.8f}`\n"
                       "*Open Rate:* `{open_rate:.8f}`\n"
                       "*Current Rate:* `{current_rate:.8f}`\n"
                       "*Sell Reason:* `{sell_reason}`\n"
                       "*Profit:* `{profit_percent:.2f}%`").format(**msg)
```
@Theagainmen hi, i don't understand the role of '?' in red below Current Rate. Could you explain ? Many tks.
That part I'm not using, I'm only using the code snippet I sent here, I like the default message, but just with a nice colored emoji depending on the sell percentage :). I assume Escaliert made that for the 'Sell reason', but he made a new line there so it looks kinda weird. You should see it as this probably:
```❓ Sell reason: reason```
Here's how mine look like (last message with the stoploss is kinda ruined, but you get the idea, it's fixed now):
![buy](https://user-images.githubusercontent.com/24569139/68614620-95675d80-04c1-11ea-8076-6679ab179932.png)
![sell profit](https://user-images.githubusercontent.com/24569139/68614597-87b1d800-04c1-11ea-8fc3-de2810d95125.png)
![sell loss](https://user-images.githubusercontent.com/24569139/68614596-87b1d800-04c1-11ea-95f5-1c01f553a314.png)
![stoploss](https://user-images.githubusercontent.com/24569139/68614670-b16aff00-04c1-11ea-9bfe-90a5bf11dd0b.png)


@xmatthias 
Should we add emojies into the telegram messages as proposed (or other ones for some of the cases)? For me it looks attractive and increases usability...
> That part I'm not using, I'm only using the code snippet I sent here, I like the default message, but just with a nice colored emoji depending on the sell percentage :). I assume Escaliert made that for the 'Sell reason', but he made a new line there so it looks kinda weird. You should see it as this probably:
> `❓ Sell reason: reason`

well, i made my homework ;-)

![image](https://user-images.githubusercontent.com/39638559/72492788-cf851080-381e-11ea-86fe-1ec016242f03.png)

> Should we add emojies into the telegram messages as proposed (or other ones for some of the cases)? For me it looks attractive and increases usability...

I guess so - i like the version from this comment https://github.com/freqtrade/freqtrade/issues/2502#issuecomment-552580197 - which color-codes by profit / loss (i don't like having a sell for profit in red as in the original post...).

We'll probably need to pay attention to encoding - but python offers some good ways to not use these symbols directly (they can cause problems, as we've seen with the sum symbol in hyperopt).


For anyone thinking about implementing this - it would be great to use named unicode (e.g. `'\N{WARNING SIGN}'`) to allow the code to work on all platforms.

This can be generated via the following code:

```python
'⚠'.encode('ascii', 'namereplace')
```

Resolving patch:
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -19,7 +19,6 @@
 
 logger.debug('Included module rpc.telegram ...')
 
-
 MAX_TELEGRAM_MESSAGE_LENGTH = 4096
 
 
@@ -29,6 +28,7 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
     :param command_handler: Telegram CommandHandler
     :return: decorated function
     """
+
     def wrapper(self, *args, **kwargs):
         """ Decorator logic """
         update = kwargs.get('update') or args[0]
@@ -133,7 +133,7 @@ def send_msg(self, msg: Dict[str, Any]) -> None:
             else:
                 msg['stake_amount_fiat'] = 0
 
-            message = ("*{exchange}:* Buying {pair}\n"
+            message = ("\N{LARGE BLUE CIRCLE} *{exchange}:* Buying {pair}\n"
                        "*Amount:* `{amount:.8f}`\n"
                        "*Open Rate:* `{limit:.8f}`\n"
                        "*Current Rate:* `{current_rate:.8f}`\n"
@@ -144,7 +144,8 @@ def send_msg(self, msg: Dict[str, Any]) -> None:
             message += ")`"
 
         elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
-            message = "*{exchange}:* Cancelling Open Buy Order for {pair}".format(**msg)
+            message = "\N{WARNING SIGN} *{exchange}:* " \
+                      "Cancelling Open Buy Order for {pair}".format(**msg)
 
         elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
             msg['amount'] = round(msg['amount'], 8)
@@ -153,7 +154,9 @@ def send_msg(self, msg: Dict[str, Any]) -> None:
                 microsecond=0) - msg['open_date'].replace(microsecond=0)
             msg['duration_min'] = msg['duration'].total_seconds() / 60
 
-            message = ("*{exchange}:* Selling {pair}\n"
+            msg['emoji'] = self._get_sell_emoji(msg)
+
+            message = ("{emoji} *{exchange}:* Selling {pair}\n"
                        "*Amount:* `{amount:.8f}`\n"
                        "*Open Rate:* `{open_rate:.8f}`\n"
                        "*Current Rate:* `{current_rate:.8f}`\n"
@@ -165,21 +168,21 @@ def send_msg(self, msg: Dict[str, Any]) -> None:
             # Check if all sell properties are available.
             # This might not be the case if the message origin is triggered by /forcesell
             if (all(prop in msg for prop in ['gain', 'fiat_currency', 'stake_currency'])
-               and self._fiat_converter):
+                    and self._fiat_converter):
                 msg['profit_fiat'] = self._fiat_converter.convert_amount(
                     msg['profit_amount'], msg['stake_currency'], msg['fiat_currency'])
                 message += (' `({gain}: {profit_amount:.8f} {stake_currency}'
                             ' / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
 
         elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
-            message = ("*{exchange}:* Cancelling Open Sell Order "
+            message = ("\N{WARNING SIGN} *{exchange}:* Cancelling Open Sell Order "
                        "for {pair}. Reason: {reason}").format(**msg)
 
         elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION:
             message = '*Status:* `{status}`'.format(**msg)
 
         elif msg['type'] == RPCMessageType.WARNING_NOTIFICATION:
-            message = '*Warning:* `{status}`'.format(**msg)
+            message = '\N{WARNING SIGN} *Warning:* `{status}`'.format(**msg)
 
         elif msg['type'] == RPCMessageType.CUSTOM_NOTIFICATION:
             message = '{status}'.format(**msg)
@@ -189,6 +192,20 @@ def send_msg(self, msg: Dict[str, Any]) -> None:
 
         self._send_msg(message)
 
+    def _get_sell_emoji(self, msg):
+        """
+        Get emoji for sell-side
+        """
+
+        if float(msg['profit_percent']) >= 5.0:
+            return "\N{ROCKET}"
+        elif float(msg['profit_percent']) >= 0.0:
+            return "\N{EIGHT SPOKED ASTERISK}"
+        elif msg['sell_reason'] == "stop_loss":
+            return"\N{WARNING SIGN}"
+        else:
+            return "\N{CROSS MARK}"
+
     @authorized_only
     def _status(self, update: Update, context: CallbackContext) -> None:
         """
@@ -222,8 +239,8 @@ def _status(self, update: Update, context: CallbackContext) -> None:
                     # Adding initial stoploss only if it is different from stoploss
                     "*Initial Stoploss:* `{initial_stop_loss:.8f}` " +
                     ("`({initial_stop_loss_pct:.2f}%)`") if (
-                        r['stop_loss'] != r['initial_stop_loss']
-                        and r['initial_stop_loss_pct'] is not None) else "",
+                            r['stop_loss'] != r['initial_stop_loss']
+                            and r['initial_stop_loss_pct'] is not None) else "",
 
                     # Adding stoploss and stoploss percentage only if it is not None
                     "*Stoploss:* `{stop_loss:.8f}` " +
@@ -363,14 +380,14 @@ def _balance(self, update: Update, context: CallbackContext) -> None:
                     "This mode is still experimental!\n"
                     "Starting capital: "
                     f"`{self._config['dry_run_wallet']}` {self._config['stake_currency']}.\n"
-                    )
+                )
             for currency in result['currencies']:
                 if currency['est_stake'] > 0.0001:
                     curr_output = "*{currency}:*\n" \
-                            "\t`Available: {free: .8f}`\n" \
-                            "\t`Balance: {balance: .8f}`\n" \
-                            "\t`Pending: {used: .8f}`\n" \
-                            "\t`Est. {stake}: {est_stake: .8f}`\n".format(**currency)
+                                  "\t`Available: {free: .8f}`\n" \
+                                  "\t`Balance: {balance: .8f}`\n" \
+                                  "\t`Pending: {used: .8f}`\n" \
+                                  "\t`Est. {stake}: {est_stake: .8f}`\n".format(**currency)
                 else:
                     curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency)
 
@@ -587,7 +604,7 @@ def _help(self, update: Update, context: CallbackContext) -> None:
                   "*/profit:* `Lists cumulative profit from all finished trades`\n" \
                   "*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " \
                   "regardless of profit`\n" \
-                  f"{forcebuy_text if self._config.get('forcebuy_enable', False) else '' }" \
+                  f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}" \
                   "*/performance:* `Show performance of each finished trade grouped by pair`\n" \
                   "*/daily <n>:* `Shows profit or loss per day, over the last n days`\n" \
                   "*/count:* `Show number of trades running compared to allowed number of trades`" \


Test patch:
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -1222,7 +1222,7 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None:
         'open_date': arrow.utcnow().shift(hours=-1)
     })
     assert msg_mock.call_args[0][0] \
-        == '*Bittrex:* Buying ETH/BTC\n' \
+        == '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \
            '*Amount:* `1333.33333333`\n' \
            '*Open Rate:* `0.00001099`\n' \
            '*Current Rate:* `0.00001099`\n' \
@@ -1244,7 +1244,7 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
         'pair': 'ETH/BTC',
     })
     assert msg_mock.call_args[0][0] \
-        == ('*Bittrex:* Cancelling Open Buy Order for ETH/BTC')
+        == ('\N{WARNING SIGN} *Bittrex:* Cancelling Open Buy Order for ETH/BTC')
 
 
 def test_send_msg_sell_notification(default_conf, mocker) -> None:
@@ -1277,7 +1277,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
         'close_date': arrow.utcnow(),
     })
     assert msg_mock.call_args[0][0] \
-        == ('*Binance:* Selling KEY/ETH\n'
+        == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n'
             '*Amount:* `1333.33333333`\n'
             '*Open Rate:* `0.00007500`\n'
             '*Current Rate:* `0.00003201`\n'
@@ -1305,7 +1305,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
         'close_date': arrow.utcnow(),
     })
     assert msg_mock.call_args[0][0] \
-        == ('*Binance:* Selling KEY/ETH\n'
+        == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n'
             '*Amount:* `1333.33333333`\n'
             '*Open Rate:* `0.00007500`\n'
             '*Current Rate:* `0.00003201`\n'
@@ -1335,7 +1335,8 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
         'reason': 'Cancelled on exchange'
     })
     assert msg_mock.call_args[0][0] \
-        == ('*Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: Cancelled on exchange')
+        == ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. '
+            'Reason: Cancelled on exchange')
 
     msg_mock.reset_mock()
     telegram.send_msg({
@@ -1345,7 +1346,7 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
         'reason': 'timeout'
     })
     assert msg_mock.call_args[0][0] \
-        == ('*Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: timeout')
+        == ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: timeout')
     # Reset singleton function to avoid random breaks
     telegram._fiat_converter.convert_amount = old_convamount
 
@@ -1379,7 +1380,7 @@ def test_warning_notification(default_conf, mocker) -> None:
         'type': RPCMessageType.WARNING_NOTIFICATION,
         'status': 'message'
     })
-    assert msg_mock.call_args[0][0] == '*Warning:* `message`'
+    assert msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Warning:* `message`'
 
 
 def test_custom_notification(default_conf, mocker) -> None:
@@ -1438,7 +1439,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
         'open_date': arrow.utcnow().shift(hours=-1)
     })
     assert msg_mock.call_args[0][0] \
-        == '*Bittrex:* Buying ETH/BTC\n' \
+        == '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \
            '*Amount:* `1333.33333333`\n' \
            '*Open Rate:* `0.00001099`\n' \
            '*Current Rate:* `0.00001099`\n' \
@@ -1474,7 +1475,7 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
         'close_date': arrow.utcnow(),
     })
     assert msg_mock.call_args[0][0] \
-        == '*Binance:* Selling KEY/ETH\n' \
+        == '\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n' \
            '*Amount:* `1333.33333333`\n' \
            '*Open Rate:* `0.00007500`\n' \
            '*Current Rate:* `0.00003201`\n' \
@@ -1484,6 +1485,29 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
            '*Profit:* `-57.41%`'
 
 
+@pytest.mark.parametrize('msg,expected', [
+    ({'profit_percent': 20.1, 'sell_reason': 'roi'}, "\N{ROCKET}"),
+    ({'profit_percent': 5.1, 'sell_reason': 'roi'}, "\N{ROCKET}"),
+    ({'profit_percent': 2.56, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
+    ({'profit_percent': 1.0, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
+    ({'profit_percent': 0.0, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
+    ({'profit_percent': -5.0, 'sell_reason': 'stop_loss'}, "\N{WARNING SIGN}"),
+    ({'profit_percent': -2.0, 'sell_reason': 'sell_signal'}, "\N{CROSS MARK}"),
+])
+def test__sell_emoji(default_conf, mocker, msg, expected):
+    del default_conf['fiat_display_currency']
+    msg_mock = MagicMock()
+    mocker.patch.multiple(
+        'freqtrade.rpc.telegram.Telegram',
+        _init=MagicMock(),
+        _send_msg=msg_mock
+    )
+    freqtradebot = get_patched_freqtradebot(mocker, default_conf)
+    telegram = Telegram(freqtradebot)
+
+    assert telegram._get_sell_emoji(msg) == expected
+
+
 def test__send_msg(default_conf, mocker) -> None:
     mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
     bot = MagicMock()