diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 2a700348f..11574c1f7 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -219,6 +219,7 @@ class Telegram(RPCHandler): else: msg['stake_amount_fiat'] = 0 + msg['currency'] = msg['pair'].split('/')[0] content = [] content.append( f"\N{LARGE BLUE CIRCLE} *{msg['exchange']}:* Buying {msg['pair']}" @@ -226,7 +227,7 @@ class Telegram(RPCHandler): ) if msg.get('buy_tag', None): content.append(f"*Buy Tag:* `{msg['buy_tag']}`\n") - content.append(f"*Amount:* `{msg['amount']:.8f} {msg['pair'].split('/')[0]}`\n") + content.append(f"*Amount:* `{msg['amount']:.8f}` {msg['currency']}\n") content.append(f"*Open Rate:* `{msg['limit']:.8f}`\n") content.append(f"*Current Rate:* `{msg['current_rate']:.8f}`\n") content.append( @@ -241,13 +242,14 @@ class Telegram(RPCHandler): message += ")`" return message - def _format_buy_msg_fill(self, msg: Dict[str, Any]) -> str: + def _format_buy_fill_msg(self, msg: Dict[str, Any]) -> str: if self._rpc._fiat_converter: msg['stake_amount_fiat'] = self._rpc._fiat_converter.convert_amount( msg['stake_amount'], msg['stake_currency'], msg['fiat_currency']) else: msg['stake_amount_fiat'] = 0 + msg['currency'] = msg['pair'].split('/')[0] content = [] content.append( f"\N{CHECK MARK} *{msg['exchange']}:* Bought {msg['pair']}" @@ -255,7 +257,8 @@ class Telegram(RPCHandler): ) if msg.get('buy_tag', None): content.append(f"*Buy Tag:* `{msg['buy_tag']}`\n") - content.append(f"*Amount:* `{msg['amount']:.8f} {msg['pair'].split('/')[0]}`\n") + content.append(f"*Amount:* `{msg['amount']:.8f}` {msg['currency']}\n") + content.append(f"*Open Rate:* `{msg['open_rate']:.8f}`\n") content.append( f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}" ) @@ -269,38 +272,6 @@ class Telegram(RPCHandler): return message def _format_sell_msg(self, msg: Dict[str, Any]) -> str: - msg['amount'] = round(msg['amount'], 8) - msg['duration'] = msg['close_date'].replace( - microsecond=0) - msg['open_date'].replace(microsecond=0) - msg['duration_min'] = msg['duration'].total_seconds() / 60 - msg['buy_tag'] = msg['buy_tag'] if "buy_tag" in msg.keys() else 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._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) - else: - msg['profit_extra'] = '' - - msg['currency'] = msg['pair'].split('/')[0] - message = ("\N{LARGE RED CIRCLE} *{exchange}:* Selling {pair} (#{trade_id})\n" - "*Unrealized 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}` {currency}\n" - "*Open Rate:* `{open_rate:.8f}`\n" - "*Current Rate:* `{current_rate:.8f}`\n" - "*Close Rate:* `{close_rate:.8f}`").format( - **msg) # TODO: Updated from `limit`, confirm this is correct variable? - - return message - - def _format_sell_msg_fill(self, msg: Dict[str, Any]) -> str: msg['amount'] = round(msg['amount'], 8) msg['profit_percent'] = round(msg['profit_ratio'] * 100, 2) msg['duration'] = msg['close_date'].replace( @@ -320,18 +291,49 @@ class Telegram(RPCHandler): ' / {profit_fiat:.3f} {fiat_currency})').format(**msg) else: msg['profit_extra'] = '' - msg['currency'] = msg['pair'].split('/')[0] + message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n" + "*Unrealized 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}` {currency}\n" + "*Open Rate:* `{open_rate:.8f}`\n" + "*Current Rate:* `{current_rate:.8f}`\n" + "*Close Rate:* `{limit:.8f}`").format(**msg) + + return message + + def _format_sell_fill_msg(self, msg: Dict[str, Any]) -> str: import pprint pprint.pprint(msg) + msg['amount'] = round(msg['amount'], 8) + msg['profit_percent'] = round(msg['profit_ratio'] * 100, 2) + msg['duration'] = msg['close_date'].replace( + microsecond=0) - msg['open_date'].replace(microsecond=0) + msg['duration_min'] = msg['duration'].total_seconds() / 60 + + msg['buy_tag'] = msg['buy_tag'] if "buy_tag" in msg.keys() else None + msg['emoji'] = self._get_sell_emoji(msg) + + # 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._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) + else: + msg['profit_extra'] = '' + msg['currency'] = msg['pair'].split('/')[0] message = ("{emoji} *{exchange}:* Sold {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}` {currency}\n" - "*Close Rate:* `{close_rate:.8f}`").format( - **msg) # TODO: Updated from `limit`, confirm this is correct variable to use? Limit not in dict for _fill + "*Close Rate:* `{close_rate:.8f}`").format(**msg) return message @@ -347,10 +349,10 @@ class Telegram(RPCHandler): "Reason: {reason}.".format(**msg)) elif msg_type == RPCMessageType.BUY_FILL: - message = self._format_buy_msg_fill(msg) + message = self._format_buy_fill_msg(msg) elif msg_type == RPCMessageType.SELL_FILL: - message = self._format_sell_msg_fill(msg) + message = self._format_sell_fill_msg(msg) elif msg_type == RPCMessageType.SELL: message = self._format_sell_msg(msg) @@ -360,11 +362,13 @@ class Telegram(RPCHandler): "*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) @@ -748,9 +752,9 @@ class Telegram(RPCHandler): duration_msg = tabulate( [ ['Wins', str(timedelta(seconds=durations['wins'])) - if durations['wins'] != 'N/A' else 'N/A'], + if durations['wins'] != 'N/A' else 'N/A'], ['Losses', str(timedelta(seconds=durations['losses'])) - if durations['losses'] != 'N/A' else 'N/A'] + if durations['losses'] != 'N/A' else 'N/A'] ], headers=['', 'Avg. Duration'] ) @@ -981,9 +985,9 @@ class Telegram(RPCHandler): trade_id = int(context.args[0]) msg = self._rpc._rpc_delete(trade_id) self._send_msg(( - '`{result_msg}`\n' - 'Please make sure to take care of this asset on the exchange manually.' - ).format(**msg)) + '`{result_msg}`\n' + 'Please make sure to take care of this asset on the exchange manually.' + ).format(**msg)) except RPCException as e: self._send_msg(str(e)) @@ -1002,7 +1006,7 @@ class Telegram(RPCHandler): output = "Performance:\n" for i, trade in enumerate(trades): stat_line = ( - f"{i + 1}.\t {trade['pair']}\t" + f"{i+1}.\t {trade['pair']}\t" f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit_ratio']:.2%}) " f"({trade['count']})\n") @@ -1037,7 +1041,7 @@ class Telegram(RPCHandler): output = "Buy Tag Performance:\n" for i, trade in enumerate(trades): stat_line = ( - f"{i + 1}.\t {trade['buy_tag']}\t" + f"{i+1}.\t {trade['buy_tag']}\t" f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit_ratio']:.2%}) " f"({trade['count']})\n") @@ -1072,7 +1076,7 @@ class Telegram(RPCHandler): output = "Sell Reason Performance:\n" for i, trade in enumerate(trades): stat_line = ( - f"{i + 1}.\t {trade['sell_reason']}\t" + f"{i+1}.\t {trade['sell_reason']}\t" f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit_ratio']:.2%}) " f"({trade['count']})\n") @@ -1107,7 +1111,7 @@ class Telegram(RPCHandler): output = "Mix Tag Performance:\n" for i, trade in enumerate(trades): stat_line = ( - f"{i + 1}.\t {trade['mix_tag']}\t" + f"{i+1}.\t {trade['mix_tag']}\t" f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit']:.2%}) " f"({trade['count']})\n") diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 68f7457d5..0b4ff968b 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1613,7 +1613,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None: assert msg_mock.call_args[0][0] \ == '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \ '*Buy Tag:* `buy_signal_01`\n' \ - '*Amount:* `1333.33333333`\n' \ + '*Amount:* `1333.33333333` ETH\n' \ '*Open Rate:* `0.00001099`\n' \ '*Current Rate:* `0.00001099`\n' \ '*Total:* `(0.00100000 BTC, 12.345 USD)`' @@ -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` ETH\n' \ + '*Open Rate:* `0.00001099`\n' \ + '*Total:* `(0.00100000 BTC, 12.345 USD)`' def test_send_msg_sell_notification(default_conf, mocker) -> None: @@ -1727,11 +1735,11 @@ 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' - '*Amount:* `1333.33333333`\n' + '*Amount:* `1333.33333333` KEY\n' '*Open Rate:* `0.00007500`\n' '*Current Rate:* `0.00003201`\n' '*Close Rate:* `0.00003201`' @@ -1759,11 +1767,11 @@ 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' - '*Amount:* `1333.33333333`\n' + '*Amount:* `1333.33333333` KEY\n' '*Open Rate:* `0.00007500`\n' '*Current Rate:* `0.00003201`\n' '*Close Rate:* `0.00003201`' @@ -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` KEY\n' + '*Close Rate:* `0.00003201`' + ) def test_send_msg_status_notification(default_conf, mocker) -> None: @@ -1892,7 +1905,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: }) assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' '*Buy Tag:* `buy_signal_01`\n' - '*Amount:* `1333.33333333`\n' + '*Amount:* `1333.33333333` ETH\n' '*Open Rate:* `0.00001099`\n' '*Current Rate:* `0.00001099`\n' '*Total:* `(0.00100000 BTC)`') @@ -1923,11 +1936,11 @@ 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' - '*Amount:* `1333.33333333`\n' + '*Amount:* `1333.33333333` KEY\n' '*Open Rate:* `0.00007500`\n' '*Current Rate:* `0.00003201`\n' '*Close Rate:* `0.00003201`'