From 6261aef314427bc64c9b3c3dbabedeccb74b734b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 May 2020 09:03:48 +0200 Subject: [PATCH 1/5] Return /profit even if no trade is closed --- freqtrade/persistence.py | 1 + freqtrade/rpc/rpc.py | 19 ++++++++----------- tests/rpc/test_rpc.py | 8 ++++++-- tests/rpc/test_rpc_apiserver.py | 12 +++++++----- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index da7137cba..d9951f1fc 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -546,6 +546,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..eb822e234 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,13 @@ 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), + '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/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index e94097545..634b2f739 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -279,8 +279,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 cc63bf6e8..c68aae56d 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) From 1d6e3fea8588e2c54a8593546a9c9628a751d489 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 May 2020 09:38:12 +0200 Subject: [PATCH 2/5] Update /profit telegram message to support non-closed trades --- freqtrade/rpc/rpc.py | 1 + freqtrade/rpc/telegram.py | 29 +++++++++++++++++------------ tests/rpc/test_rpc_apiserver.py | 3 ++- tests/rpc/test_rpc_telegram.py | 6 ++++-- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index eb822e234..6addf18ba 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -310,6 +310,7 @@ class RPC: 'profit_all_percent': profit_all_percent, 'profit_all_fiat': profit_all_fiat, 'trade_count': len(trades), + '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 '', diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 488fa9f37..78cdaef62 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -328,18 +328,23 @@ class Telegram(RPC): best_pair = stats['best_pair'] best_rate = stats['best_rate'] # 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}%`" + if stats['closed_trade_count'] > 0: + 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") + 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) except RPCException as e: self._send_msg(str(e)) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index c68aae56d..5cab10244 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -431,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..0b990281f 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 closed trade' 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 From 46456516bb677077eeafb0fb853a6517a1b7b1cf Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 May 2020 10:10:45 +0200 Subject: [PATCH 3/5] Remove exception handler --- freqtrade/rpc/telegram.py | 69 +++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 78cdaef62..4bf19192d 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -311,43 +311,40 @@ 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'] - # Message to display - if stats['closed_trade_count'] > 0: - 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") - else: - markdown_msg = "`No closed trade` \n" + 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'] + # Message to display + if stats['closed_trade_count'] > 0: + 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") + 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) - except RPCException as e: - self._send_msg(str(e)) + 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: From 91f84f1a4304266d7f697a0418683bf3a202d796 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 19:28:30 +0200 Subject: [PATCH 4/5] Fix typo in close trade message --- freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc_telegram.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 4bf19192d..dcb667d2d 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -328,7 +328,7 @@ class Telegram(RPC): best_rate = stats['best_rate'] # Message to display if stats['closed_trade_count'] > 0: - markdown_msg = ("*ROI:* Close trades\n" + 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") diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 0b990281f..29820fba3 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -446,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] From cc90e7b413f2b300fe692b0b75d415c6e1c3a3d9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 May 2020 19:42:09 +0200 Subject: [PATCH 5/5] Show "No trades yet." when no trade happened yet --- freqtrade/rpc/telegram.py | 35 ++++++++++++++++++---------------- tests/rpc/test_rpc_telegram.py | 2 +- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index dcb667d2d..7eef18dfc 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -326,24 +326,27 @@ class Telegram(RPC): avg_duration = stats['avg_duration'] best_pair = stats['best_pair'] best_rate = stats['best_rate'] - # Message to display - 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") + if stats['trade_count'] == 0: + markdown_msg = 'No trades yet.' else: - markdown_msg = "`No closed trade` \n" + # Message to display + 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}%`") + 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 diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 29820fba3..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