Repository: freqtrade/freqtrade

Issue:
Include more details in _FILL telegram notifications
## Describe the enhancement
When running strategies on market orders, slippage can occur, rendering all of the information in the buy/sell notifications inaccurate.

*Explain the enhancement you would like*
Please include the same information that is in the buy/sell order notifications in the buy/sell FILL notifications. This way user can see the **actual** profit %. It is not enough to only show the price the order was filled at, as user then needs to do math to calculate what the actual result is after slippage

Something along the lines of this:
🔵 Kucoin: **Filled Buy** OMG/USDT (#4)
Buy Tag: 46 
Amount: 63.89690000
Open Rate: 13.35900000
Current Rate: 14.35900000
Slippage: 1 (7.69%)
Total: (853.599 USDT, 853.599 USD)

✳ Kucoin: **Filled Sell** OMG/USDT (#4)
Profit: 4.85% (profit: 41.19867642 USDT / 41.199 USD)
Sell Reason: sell_profit_u_bear_4_1 ( 46 )
Duration: 0:09:28 (9.5 min)
Amount: 63.89690000
Open Rate: 13.28000000
Current Rate: 13.95200000
Close Rate: 13.95200000
Slippage: XX (x.x%)


Discussion:
Pull requests are welcome for this.
This shouldn't be too hard, either - and changes should be mostly limited to `telegram.py`.

Resolving patch:
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -219,26 +219,28 @@ def _format_buy_msg(self, msg: Dict[str, Any]) -> str:
                 msg['stake_amount'], msg['stake_currency'], msg['fiat_currency'])
         else:
             msg['stake_amount_fiat'] = 0
+        is_fill = msg['type'] == RPCMessageType.BUY_FILL
+        emoji = '\N{CHECK MARK}' if is_fill else '\N{LARGE BLUE CIRCLE}'
 
-        content = []
-        content.append(
-            f"\N{LARGE BLUE CIRCLE} *{msg['exchange']}:* Buying {msg['pair']}"
+        message = (
+            f"{emoji} *{msg['exchange']}:* {'Bought' if is_fill else 'Buying'} {msg['pair']}"
             f" (#{msg['trade_id']})\n"
-        )
-        if msg.get('buy_tag', None):
-            content.append(f"*Buy Tag:* `{msg['buy_tag']}`\n")
-        content.append(f"*Amount:* `{msg['amount']:.8f}`\n")
-        content.append(f"*Open Rate:* `{msg['limit']:.8f}`\n")
-        content.append(f"*Current Rate:* `{msg['current_rate']:.8f}`\n")
-        content.append(
-            f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}"
-        )
-        if msg.get('fiat_currency', None):
-            content.append(
-                f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
             )
+        message += f"*Buy Tag:* `{msg['buy_tag']}`\n" if msg.get('buy_tag', None) else ""
+        message += f"*Amount:* `{msg['amount']:.8f}`\n"
+
+        if msg['type'] == RPCMessageType.BUY_FILL:
+            message += f"*Open Rate:* `{msg['open_rate']:.8f}`\n"
+
+        elif msg['type'] == RPCMessageType.BUY:
+            message += f"*Open Rate:* `{msg['limit']:.8f}`\n"\
+                       f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
+
+        message += f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}"
+
+        if msg.get('fiat_currency', None):
+            message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
 
-        message = ''.join(content)
         message += ")`"
         return message
 
@@ -258,54 +260,57 @@ def _format_sell_msg(self, msg: Dict[str, Any]) -> str:
                 and self._rpc._fiat_converter):
             msg['profit_fiat'] = self._rpc._fiat_converter.convert_amount(
                 msg['profit_amount'], msg['stake_currency'], msg['fiat_currency'])
-            msg['profit_extra'] = (' ({gain}: {profit_amount:.8f} {stake_currency}'
-                                   ' / {profit_fiat:.3f} {fiat_currency})').format(**msg)
+            msg['profit_extra'] = (
+                f" ({msg['gain']}: {msg['profit_amount']:.8f} {msg['stake_currency']}"
+                f" / {msg['profit_fiat']:.3f} {msg['fiat_currency']})")
         else:
             msg['profit_extra'] = ''
-
-        message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n"
-                   "*Profit:* `{profit_ratio:.2%}{profit_extra}`\n"
-                   "*Buy Tag:* `{buy_tag}`\n"
-                   "*Sell Reason:* `{sell_reason}`\n"
-                   "*Duration:* `{duration} ({duration_min:.1f} min)`\n"
-                   "*Amount:* `{amount:.8f}`\n"
-                   "*Open Rate:* `{open_rate:.8f}`\n"
-                   "*Current Rate:* `{current_rate:.8f}`\n"
-                   "*Close Rate:* `{limit:.8f}`").format(**msg)
+        is_fill = msg['type'] == RPCMessageType.SELL_FILL
+        message = (
+            f"{msg['emoji']} *{msg['exchange']}:* "
+            f"{'Sold' if is_fill else 'Selling'} {msg['pair']} (#{msg['trade_id']})\n"
+            f"*{'Profit' if is_fill else 'Unrealized Profit'}:* "
+            f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n"
+            f"*Buy Tag:* `{msg['buy_tag']}`\n"
+            f"*Sell Reason:* `{msg['sell_reason']}`\n"
+            f"*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`\n"
+            f"*Amount:* `{msg['amount']:.8f}`\n")
+
+        if msg['type'] == RPCMessageType.SELL:
+            message += (f"*Open Rate:* `{msg['open_rate']:.8f}`\n"
+                        f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
+                        f"*Close Rate:* `{msg['limit']:.8f}`")
+
+        elif msg['type'] == RPCMessageType.SELL_FILL:
+            message += f"*Close Rate:* `{msg['close_rate']:.8f}`"
 
         return message
 
     def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str:
-
-        if msg_type == RPCMessageType.BUY:
+        if msg_type in [RPCMessageType.BUY, RPCMessageType.BUY_FILL]:
             message = self._format_buy_msg(msg)
 
+        elif msg_type in [RPCMessageType.SELL, RPCMessageType.SELL_FILL]:
+            message = self._format_sell_msg(msg)
+
         elif msg_type in (RPCMessageType.BUY_CANCEL, RPCMessageType.SELL_CANCEL):
             msg['message_side'] = 'buy' if msg_type == RPCMessageType.BUY_CANCEL else 'sell'
             message = ("\N{WARNING SIGN} *{exchange}:* "
                        "Cancelling open {message_side} Order for {pair} (#{trade_id}). "
                        "Reason: {reason}.".format(**msg))
 
-        elif msg_type == RPCMessageType.BUY_FILL:
-            message = ("\N{LARGE CIRCLE} *{exchange}:* "
-                       "Buy order for {pair} (#{trade_id}) filled "
-                       "for {open_rate}.".format(**msg))
-        elif msg_type == RPCMessageType.SELL_FILL:
-            message = ("\N{LARGE CIRCLE} *{exchange}:* "
-                       "Sell order for {pair} (#{trade_id}) filled "
-                       "for {close_rate}.".format(**msg))
-        elif msg_type == RPCMessageType.SELL:
-            message = self._format_sell_msg(msg)
         elif msg_type == RPCMessageType.PROTECTION_TRIGGER:
             message = (
                 "*Protection* triggered due to {reason}. "
                 "`{pair}` will be locked until `{lock_end_time}`."
             ).format(**msg)
+
         elif msg_type == RPCMessageType.PROTECTION_TRIGGER_GLOBAL:
             message = (
                 "*Protection* triggered due to {reason}. "
                 "*All pairs* will be locked until `{lock_end_time}`."
             ).format(**msg)
+
         elif msg_type == RPCMessageType.STATUS:
             message = '*Status:* `{status}`'.format(**msg)
 
@@ -357,7 +362,7 @@ def _get_sell_emoji(self, msg):
         elif float(msg['profit_percent']) >= 0.0:
             return "\N{EIGHT SPOKED ASTERISK}"
         elif msg['sell_reason'] == "stop_loss":
-            return"\N{WARNING SIGN}"
+            return "\N{WARNING SIGN}"
         else:
             return "\N{CROSS MARK}"
 


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
@@ -1686,17 +1686,25 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
 
     telegram.send_msg({
         'type': RPCMessageType.BUY_FILL,
-        'buy_tag': 'buy_signal_01',
         'trade_id': 1,
+        'buy_tag': 'buy_signal_01',
         'exchange': 'Binance',
-        'pair': 'ETH/USDT',
-        'open_rate': 200,
-        'stake_amount': 100,
-        'amount': 0.5,
-        'open_date': arrow.utcnow().datetime
+        'pair': 'ETH/BTC',
+        'stake_amount': 0.001,
+        # 'stake_amount_fiat': 0.0,
+        'stake_currency': 'BTC',
+        'fiat_currency': 'USD',
+        'open_rate': 1.099e-05,
+        'amount': 1333.3333333333335,
+        'open_date': arrow.utcnow().shift(hours=-1)
     })
-    assert (msg_mock.call_args[0][0] == '\N{LARGE CIRCLE} *Binance:* '
-            'Buy order for ETH/USDT (#1) filled for 200.')
+
+    assert msg_mock.call_args[0][0] \
+        == '\N{CHECK MARK} *Binance:* Bought ETH/BTC (#1)\n' \
+           '*Buy Tag:* `buy_signal_01`\n' \
+           '*Amount:* `1333.33333333`\n' \
+           '*Open Rate:* `0.00001099`\n' \
+           '*Total:* `(0.00100000 BTC, 12.345 USD)`'
 
 
 def test_send_msg_sell_notification(default_conf, mocker) -> None:
@@ -1727,7 +1735,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
     })
     assert msg_mock.call_args[0][0] \
         == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
-            '*Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
+            '*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
             '*Buy Tag:* `buy_signal1`\n'
             '*Sell Reason:* `stop_loss`\n'
             '*Duration:* `1:00:00 (60.0 min)`\n'
@@ -1759,7 +1767,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
     })
     assert msg_mock.call_args[0][0] \
         == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
-            '*Profit:* `-57.41%`\n'
+            '*Unrealized Profit:* `-57.41%`\n'
             '*Buy Tag:* `buy_signal1`\n'
             '*Sell Reason:* `stop_loss`\n'
             '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
@@ -1813,25 +1821,30 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
         'type': RPCMessageType.SELL_FILL,
         'trade_id': 1,
         'exchange': 'Binance',
-        'pair': 'ETH/USDT',
+        'pair': 'KEY/ETH',
         'gain': 'loss',
         'limit': 3.201e-05,
-        'amount': 0.1,
+        'amount': 1333.3333333333335,
         'order_type': 'market',
-        'open_rate': 500,
-        'close_rate': 550,
-        'current_rate': 3.201e-05,
+        'open_rate': 7.5e-05,
+        'close_rate': 3.201e-05,
         'profit_amount': -0.05746268,
         'profit_ratio': -0.57405275,
         'stake_currency': 'ETH',
-        'fiat_currency': 'USD',
         'buy_tag': 'buy_signal1',
         'sell_reason': SellType.STOP_LOSS.value,
-        'open_date': arrow.utcnow().shift(hours=-1),
+        'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
         'close_date': arrow.utcnow(),
     })
     assert msg_mock.call_args[0][0] \
-        == ('\N{LARGE CIRCLE} *Binance:* Sell order for ETH/USDT (#1) filled for 550.')
+        == ('\N{WARNING SIGN} *Binance:* Sold KEY/ETH (#1)\n'
+            '*Profit:* `-57.41%`\n'
+            '*Buy Tag:* `buy_signal1`\n'
+            '*Sell Reason:* `stop_loss`\n'
+            '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
+            '*Amount:* `1333.33333333`\n'
+            '*Close Rate:* `0.00003201`'
+            )
 
 
 def test_send_msg_status_notification(default_conf, mocker) -> None:
@@ -1923,7 +1936,7 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
         'close_date': arrow.utcnow(),
     })
     assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
-                                        '*Profit:* `-57.41%`\n'
+                                        '*Unrealized Profit:* `-57.41%`\n'
                                         '*Buy Tag:* `buy_signal1`\n'
                                         '*Sell Reason:* `stop_loss`\n'
                                         '*Duration:* `2:35:03 (155.1 min)`\n'