diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index a5005f35e..00a170ee3 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -132,6 +132,14 @@ class RPC: except DependencyException: current_rate = NAN current_profit = trade.calc_profit_ratio(current_rate) + current_profit_abs = trade.calc_profit(current_rate) + # Calculate guaranteed profit (in case of trailing stop) + stoploss_entry_dist = trade.calc_profit(trade.stop_loss) + stoploss_entry_dist_ratio = trade.calc_profit_ratio(trade.stop_loss) + # calculate distance to stoploss + stoploss_current_dist = trade.stop_loss - current_rate + stoploss_current_dist_ratio = stoploss_current_dist / current_rate + fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%' if trade.close_profit is not None else None) trade_dict = trade.to_json() @@ -142,6 +150,11 @@ class RPC: current_rate=current_rate, current_profit=current_profit, current_profit_pct=round(current_profit * 100, 2), + current_profit_abs=current_profit_abs, + stoploss_current_dist=stoploss_current_dist, + stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8), + stoploss_entry_dist=stoploss_entry_dist, + stoploss_entry_dist_ratio=round(stoploss_entry_dist_ratio, 8), open_order='({} {} rem={:.8f})'.format( order['type'], order['side'], order['remaining'] ) if order else None, @@ -285,8 +298,9 @@ class RPC: # Prepare data to display profit_closed_coin_sum = round(sum(profit_closed_coin), 8) - profit_closed_percent = (round(mean(profit_closed_ratio) * 100, 2) if profit_closed_ratio - else 0.0) + profit_closed_ratio_mean = mean(profit_closed_ratio) if profit_closed_ratio else 0.0 + profit_closed_ratio_sum = sum(profit_closed_ratio) if profit_closed_ratio else 0.0 + profit_closed_fiat = self._fiat_converter.convert_amount( profit_closed_coin_sum, stake_currency, @@ -294,7 +308,8 @@ class RPC: ) if self._fiat_converter else 0 profit_all_coin_sum = round(sum(profit_all_coin), 8) - profit_all_percent = round(mean(profit_all_ratio) * 100, 2) if profit_all_ratio else 0.0 + profit_all_ratio_mean = mean(profit_all_ratio) if profit_all_ratio else 0.0 + profit_all_ratio_sum = sum(profit_all_ratio) if profit_all_ratio else 0.0 profit_all_fiat = self._fiat_converter.convert_amount( profit_all_coin_sum, stake_currency, @@ -306,10 +321,18 @@ class RPC: num = float(len(durations) or 1) return { 'profit_closed_coin': profit_closed_coin_sum, - 'profit_closed_percent': profit_closed_percent, + 'profit_closed_percent': round(profit_closed_ratio_mean * 100, 2), # DEPRECATED + 'profit_closed_percent_mean': round(profit_closed_ratio_mean * 100, 2), + 'profit_closed_ratio_mean': profit_closed_ratio_mean, + 'profit_closed_percent_sum': round(profit_closed_ratio_sum * 100, 2), + 'profit_closed_ratio_sum': profit_closed_ratio_sum, 'profit_closed_fiat': profit_closed_fiat, 'profit_all_coin': profit_all_coin_sum, - 'profit_all_percent': profit_all_percent, + 'profit_all_percent': round(profit_all_ratio_mean * 100, 2), # DEPRECATED + 'profit_all_percent_mean': round(profit_all_ratio_mean * 100, 2), + 'profit_all_ratio_mean': profit_all_ratio_mean, + 'profit_all_percent_sum': round(profit_all_ratio_sum * 100, 2), + 'profit_all_ratio_sum': profit_all_ratio_sum, 'profit_all_fiat': profit_all_fiat, 'trade_count': len(trades), 'closed_trade_count': len([t for t in trades if not t.is_open]), diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 2ea697ed4..965318f64 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -19,7 +19,6 @@ logger = logging.getLogger(__name__) 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 @@ class Telegram(RPC): 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 @@ class Telegram(RPC): 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 @@ class Telegram(RPC): 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 @@ class Telegram(RPC): # 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 @@ class Telegram(RPC): 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 @@ class Telegram(RPC): # 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}` " + @@ -315,10 +332,12 @@ class Telegram(RPC): stake_cur, fiat_disp_cur) profit_closed_coin = stats['profit_closed_coin'] - profit_closed_percent = stats['profit_closed_percent'] + profit_closed_percent_mean = stats['profit_closed_percent_mean'] + profit_closed_percent_sum = stats['profit_closed_percent_sum'] profit_closed_fiat = stats['profit_closed_fiat'] profit_all_coin = stats['profit_all_coin'] - profit_all_percent = stats['profit_all_percent'] + profit_all_percent_mean = stats['profit_all_percent_mean'] + profit_all_percent_sum = stats['profit_all_percent_sum'] profit_all_fiat = stats['profit_all_fiat'] trade_count = stats['trade_count'] first_trade_date = stats['first_trade_date'] @@ -333,13 +352,16 @@ class Telegram(RPC): if stats['closed_trade_count'] > 0: markdown_msg = ("*ROI:* Closed trades\n" f"∙ `{profit_closed_coin:.8f} {stake_cur} " - f"({profit_closed_percent:.2f}%)`\n" + f"({profit_closed_percent_mean:.2f}%) " + f"({profit_closed_percent_sum} \N{GREEK CAPITAL LETTER SIGMA}%)`\n" f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n") else: markdown_msg = "`No closed trade` \n" markdown_msg += (f"*ROI:* All trades\n" - f"∙ `{profit_all_coin:.8f} {stake_cur} ({profit_all_percent:.2f}%)`\n" + f"∙ `{profit_all_coin:.8f} {stake_cur} " + f"({profit_all_percent_mean:.2f}%) " + f"({profit_all_percent_sum} \N{GREEK CAPITAL LETTER SIGMA}%)`\n" f"∙ `{profit_all_fiat:.3f} {fiat_disp_cur}`\n" f"*Total Trade Count:* `{trade_count}`\n" f"*First Trade opened:* `{first_trade_date}`\n" @@ -363,14 +385,14 @@ class Telegram(RPC): "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 +609,7 @@ class Telegram(RPC): "*/profit:* `Lists cumulative profit from all finished trades`\n" \ "*/forcesell |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 :* `Shows profit or loss per day, over the last n days`\n" \ "*/count:* `Show number of trades running compared to allowed number of trades`" \ diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 1de73ada9..ef1c1bc16 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -42,8 +42,12 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: rpc._rpc_trade_status() freqtradebot.enter_positions() + trades = Trade.get_open_trades() + trades[0].open_order_id = None + freqtradebot.exit_positions(trades) + results = rpc._rpc_trade_status() - assert { + assert results[0] == { 'trade_id': 1, 'pair': 'ETH/BTC', 'base_currency': 'BTC', @@ -54,11 +58,11 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'fee_open': ANY, 'fee_open_cost': ANY, 'fee_open_currency': ANY, - 'fee_close': ANY, + 'fee_close': fee.return_value, 'fee_close_cost': ANY, 'fee_close_currency': ANY, 'open_rate_requested': ANY, - 'open_trade_price': ANY, + 'open_trade_price': 0.0010025, 'close_rate_requested': ANY, 'sell_reason': ANY, 'sell_order_status': ANY, @@ -80,28 +84,32 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'close_profit_abs': None, 'current_profit': -0.00408133, 'current_profit_pct': -0.41, - 'stop_loss': 0.0, - 'stop_loss_abs': 0.0, - 'stop_loss_pct': None, - 'stop_loss_ratio': None, + 'current_profit_abs': -4.09e-06, + 'stop_loss': 9.882e-06, + 'stop_loss_abs': 9.882e-06, + 'stop_loss_pct': -10.0, + 'stop_loss_ratio': -0.1, 'stoploss_order_id': None, - 'stoploss_last_update': None, - 'stoploss_last_update_timestamp': None, - 'initial_stop_loss': 0.0, - 'initial_stop_loss_abs': 0.0, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, - 'open_order': '(limit buy rem=0.00000000)', + 'stoploss_last_update': ANY, + 'stoploss_last_update_timestamp': ANY, + 'initial_stop_loss': 9.882e-06, + 'initial_stop_loss_abs': 9.882e-06, + 'initial_stop_loss_pct': -10.0, + 'initial_stop_loss_ratio': -0.1, + 'stoploss_current_dist': -1.1080000000000002e-06, + 'stoploss_current_dist_ratio': -0.10081893, + 'stoploss_entry_dist': -0.00010475, + 'stoploss_entry_dist_ratio': -0.10448878, + 'open_order': None, 'exchange': 'bittrex', - - } == results[0] + } mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate', MagicMock(side_effect=DependencyException("Pair 'ETH/BTC' not available"))) results = rpc._rpc_trade_status() assert isnan(results[0]['current_profit']) assert isnan(results[0]['current_rate']) - assert { + assert results[0] == { 'trade_id': 1, 'pair': 'ETH/BTC', 'base_currency': 'BTC', @@ -112,7 +120,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'fee_open': ANY, 'fee_open_cost': ANY, 'fee_open_currency': ANY, - 'fee_close': ANY, + 'fee_close': fee.return_value, 'fee_close_cost': ANY, 'fee_close_currency': ANY, 'open_rate_requested': ANY, @@ -138,20 +146,25 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'close_profit_abs': None, 'current_profit': ANY, 'current_profit_pct': ANY, - 'stop_loss': 0.0, - 'stop_loss_abs': 0.0, - 'stop_loss_pct': None, - 'stop_loss_ratio': None, + 'current_profit_abs': ANY, + 'stop_loss': 9.882e-06, + 'stop_loss_abs': 9.882e-06, + 'stop_loss_pct': -10.0, + 'stop_loss_ratio': -0.1, 'stoploss_order_id': None, - 'stoploss_last_update': None, - 'stoploss_last_update_timestamp': None, - 'initial_stop_loss': 0.0, - 'initial_stop_loss_abs': 0.0, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, - 'open_order': '(limit buy rem=0.00000000)', + 'stoploss_last_update': ANY, + 'stoploss_last_update_timestamp': ANY, + 'initial_stop_loss': 9.882e-06, + 'initial_stop_loss_abs': 9.882e-06, + 'initial_stop_loss_pct': -10.0, + 'initial_stop_loss_ratio': -0.1, + 'stoploss_current_dist': ANY, + 'stoploss_current_dist_ratio': ANY, + 'stoploss_entry_dist': -0.00010475, + 'stoploss_entry_dist_ratio': -0.10448878, + 'open_order': None, 'exchange': 'bittrex', - } == results[0] + } def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 6a850ecc5..9b247fefc 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -430,9 +430,17 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li 'profit_all_coin': 6.217e-05, 'profit_all_fiat': 0, 'profit_all_percent': 6.2, + 'profit_all_percent_mean': 6.2, + 'profit_all_ratio_mean': 0.06201058, + 'profit_all_percent_sum': 6.2, + 'profit_all_ratio_sum': 0.06201058, 'profit_closed_coin': 6.217e-05, 'profit_closed_fiat': 0, 'profit_closed_percent': 6.2, + 'profit_closed_ratio_mean': 0.06201058, + 'profit_closed_percent_mean': 6.2, + 'profit_closed_ratio_sum': 0.06201058, + 'profit_closed_percent_sum': 6.2, 'trade_count': 1, 'closed_trade_count': 1, } @@ -497,6 +505,10 @@ def test_api_status(botclient, mocker, ticker, fee, markets): assert rc.json == [] ftbot.enter_positions() + trades = Trade.get_open_trades() + trades[0].open_order_id = None + ftbot.exit_positions(trades) + rc = client_get(client, f"{BASE_URI}/status") assert_response(rc) assert len(rc.json) == 1 @@ -511,25 +523,30 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'close_rate': None, 'current_profit': -0.00408133, 'current_profit_pct': -0.41, + 'current_profit_abs': -4.09e-06, 'current_rate': 1.099e-05, 'open_date': ANY, 'open_date_hum': 'just now', 'open_timestamp': ANY, - 'open_order': '(limit buy rem=0.00000000)', + 'open_order': None, 'open_rate': 1.098e-05, 'pair': 'ETH/BTC', 'stake_amount': 0.001, - 'stop_loss': 0.0, - 'stop_loss_abs': 0.0, - 'stop_loss_pct': None, - 'stop_loss_ratio': None, + 'stop_loss': 9.882e-06, + 'stop_loss_abs': 9.882e-06, + 'stop_loss_pct': -10.0, + 'stop_loss_ratio': -0.1, 'stoploss_order_id': None, - 'stoploss_last_update': None, - 'stoploss_last_update_timestamp': None, - 'initial_stop_loss': 0.0, - 'initial_stop_loss_abs': 0.0, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, + 'stoploss_last_update': ANY, + 'stoploss_last_update_timestamp': ANY, + 'initial_stop_loss': 9.882e-06, + 'initial_stop_loss_abs': 9.882e-06, + 'initial_stop_loss_pct': -10.0, + 'initial_stop_loss_ratio': -0.1, + 'stoploss_current_dist': -1.1080000000000002e-06, + 'stoploss_current_dist_ratio': -0.10081893, + 'stoploss_entry_dist': -0.00010475, + 'stoploss_entry_dist_ratio': -0.10448878, 'trade_id': 1, 'close_rate_requested': None, 'current_rate': 1.099e-05, @@ -541,9 +558,9 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'fee_open_currency': None, 'open_date': ANY, 'is_open': True, - 'max_rate': 0.0, - 'min_rate': None, - 'open_order_id': ANY, + 'max_rate': 1.099e-05, + 'min_rate': 1.098e-05, + 'open_order_id': None, 'open_rate_requested': 1.098e-05, 'open_trade_price': 0.0010025, 'sell_reason': None, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f81127c4c..21b9df81c 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -434,7 +434,8 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, assert msg_mock.call_count == 1 assert 'No closed trade' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] - assert '∙ `-0.00000500 BTC (-0.50%)`' in msg_mock.call_args_list[-1][0][0] + assert ('∙ `-0.00000500 BTC (-0.50%) (-0.5 \N{GREEK CAPITAL LETTER SIGMA}%)`' + in msg_mock.call_args_list[-1][0][0]) msg_mock.reset_mock() # Update the ticker with a market going up @@ -447,10 +448,12 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, telegram._profit(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert '*ROI:* Closed trades' in msg_mock.call_args_list[-1][0][0] - assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] + assert ('∙ `0.00006217 BTC (6.20%) (6.2 \N{GREEK CAPITAL LETTER SIGMA}%)`' + in msg_mock.call_args_list[-1][0][0]) assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] - assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] + assert ('∙ `0.00006217 BTC (6.20%) (6.2 \N{GREEK CAPITAL LETTER SIGMA}%)`' + in msg_mock.call_args_list[-1][0][0]) assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0] assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0] @@ -1222,7 +1225,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 +1247,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 +1280,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 +1308,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 +1338,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 +1349,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 +1383,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 +1442,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 +1478,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 +1488,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()