diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index a0b9133c9..111ccfe2a 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -553,6 +553,7 @@ class Trade(_DECL_BASE): def get_best_pair(): """ Get best pair with closed trade. + :returns: Tuple containing (pair, profit_sum) """ best_pair = Trade.session.query( Trade.pair, func.sum(Trade.close_profit).label('profit_sum') diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 248b4a421..6addf18ba 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -281,11 +281,6 @@ class RPC: best_pair = Trade.get_best_pair() - if not best_pair: - raise RPCException('no closed trade') - - bp_pair, bp_rate = best_pair - # 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 @@ -304,6 +299,8 @@ class RPC: fiat_display_currency ) if self._fiat_converter else 0 + first_date = trades[0].open_date if trades else None + last_date = trades[-1].open_date if trades else None num = float(len(durations) or 1) return { 'profit_closed_coin': profit_closed_coin_sum, @@ -313,13 +310,14 @@ class RPC: 'profit_all_percent': profit_all_percent, 'profit_all_fiat': profit_all_fiat, 'trade_count': len(trades), - 'first_trade_date': arrow.get(trades[0].open_date).humanize(), - 'first_trade_timestamp': int(trades[0].open_date.timestamp() * 1000), - 'latest_trade_date': arrow.get(trades[-1].open_date).humanize(), - 'latest_trade_timestamp': int(trades[-1].open_date.timestamp() * 1000), + 'closed_trade_count': len([t for t in trades if not t.is_open]), + 'first_trade_date': arrow.get(first_date).humanize() if first_date else '', + 'first_trade_timestamp': int(first_date.timestamp() * 1000) if first_date else 0, + 'latest_trade_date': arrow.get(last_date).humanize() if last_date else '', + 'latest_trade_timestamp': int(last_date.timestamp() * 1000) if last_date else 0, 'avg_duration': str(timedelta(seconds=sum(durations) / num)).split('.')[0], - 'best_pair': bp_pair, - 'best_rate': round(bp_rate * 100, 2), + 'best_pair': best_pair[0] if best_pair else '', + 'best_rate': round(best_pair[1] * 100, 2) if best_pair else 0, } def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict: diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 488fa9f37..7eef18dfc 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -311,38 +311,43 @@ class Telegram(RPC): stake_cur = self._config['stake_currency'] fiat_disp_cur = self._config.get('fiat_display_currency', '') - try: - stats = self._rpc_trade_statistics( - stake_cur, - fiat_disp_cur) - profit_closed_coin = stats['profit_closed_coin'] - profit_closed_percent = stats['profit_closed_percent'] - profit_closed_fiat = stats['profit_closed_fiat'] - profit_all_coin = stats['profit_all_coin'] - profit_all_percent = stats['profit_all_percent'] - profit_all_fiat = stats['profit_all_fiat'] - trade_count = stats['trade_count'] - first_trade_date = stats['first_trade_date'] - latest_trade_date = stats['latest_trade_date'] - avg_duration = stats['avg_duration'] - best_pair = stats['best_pair'] - best_rate = stats['best_rate'] + stats = self._rpc_trade_statistics( + stake_cur, + fiat_disp_cur) + profit_closed_coin = stats['profit_closed_coin'] + profit_closed_percent = stats['profit_closed_percent'] + profit_closed_fiat = stats['profit_closed_fiat'] + profit_all_coin = stats['profit_all_coin'] + profit_all_percent = stats['profit_all_percent'] + profit_all_fiat = stats['profit_all_fiat'] + trade_count = stats['trade_count'] + first_trade_date = stats['first_trade_date'] + latest_trade_date = stats['latest_trade_date'] + avg_duration = stats['avg_duration'] + best_pair = stats['best_pair'] + best_rate = stats['best_rate'] + if stats['trade_count'] == 0: + markdown_msg = 'No trades yet.' + else: # Message to display - markdown_msg = "*ROI:* Close trades\n" \ - f"∙ `{profit_closed_coin:.8f} {stake_cur} "\ - f"({profit_closed_percent:.2f}%)`\n" \ - f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n" \ - f"*ROI:* All trades\n" \ - f"∙ `{profit_all_coin:.8f} {stake_cur} ({profit_all_percent:.2f}%)`\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" \ - f"*Latest Trade opened:* `{latest_trade_date}`\n" \ - f"*Avg. Duration:* `{avg_duration}`\n" \ - f"*Best Performing:* `{best_pair}: {best_rate:.2f}%`" - self._send_msg(markdown_msg) - except RPCException as e: - self._send_msg(str(e)) + 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_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_fiat:.3f} {fiat_disp_cur}`\n" + f"*Total Trade Count:* `{trade_count}`\n" + f"*First Trade opened:* `{first_trade_date}`\n" + f"*Latest Trade opened:* `{latest_trade_date}`") + if stats['closed_trade_count'] > 0: + markdown_msg += (f"\n*Avg. Duration:* `{avg_duration}`\n" + f"*Best Performing:* `{best_pair}: {best_rate:.2f}%`") + self._send_msg(markdown_msg) @authorized_only def _balance(self, update: Update, context: CallbackContext) -> None: diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index a68ca191a..7b7824310 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -290,8 +290,12 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() - with pytest.raises(RPCException, match=r'.*no closed trade*'): - rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) + res = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) + assert res['trade_count'] == 0 + assert res['first_trade_date'] == '' + assert res['first_trade_timestamp'] == 0 + assert res['latest_trade_date'] == '' + assert res['latest_trade_timestamp'] == 0 # Create some test data freqtradebot.enter_positions() diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index c284ccb7b..c7f937b0c 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -396,9 +396,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li ) rc = client_get(client, f"{BASE_URI}/profit") - assert_response(rc, 502) - assert len(rc.json) == 1 - assert rc.json == {"error": "Error querying _profit: no closed trade"} + assert_response(rc, 200) + assert rc.json['trade_count'] == 0 ftbot.enter_positions() trade = Trade.query.first() @@ -406,8 +405,11 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) rc = client_get(client, f"{BASE_URI}/profit") - assert_response(rc, 502) - assert rc.json == {"error": "Error querying _profit: no closed trade"} + assert_response(rc, 200) + # One open trade + assert rc.json['trade_count'] == 1 + assert rc.json['best_pair'] == '' + assert rc.json['best_rate'] == 0 trade.update(limit_sell_order) @@ -429,7 +431,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li 'profit_closed_coin': 6.217e-05, 'profit_closed_fiat': 0, 'profit_closed_percent': 6.2, - 'trade_count': 1 + 'trade_count': 1, + 'closed_trade_count': 1, } diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 730bb2677..500acaa30 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -420,7 +420,7 @@ 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 'no closed trade' in msg_mock.call_args_list[0][0][0] + assert 'No trades yet.' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() # Create some test data @@ -432,7 +432,9 @@ 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 'no closed trade' in msg_mock.call_args_list[-1][0][0] + 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] msg_mock.reset_mock() # Update the ticker with a market going up @@ -444,7 +446,7 @@ 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:* Close trades' in msg_mock.call_args_list[-1][0][0] + 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.933 USD`' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]