From b51f946ee07d579a09f0b0368412454fc6e92ef7 Mon Sep 17 00:00:00 2001 From: theluxaz Date: Mon, 25 Oct 2021 23:43:22 +0300 Subject: [PATCH] Fixed models and rpc performance functions, added skeletons for tests. --- freqtrade/persistence/models.py | 124 ++++++++++++++------------------ freqtrade/rpc/rpc.py | 12 ++-- tests/rpc/test_rpc.py | 114 +++++++++++++++++++++++++++++ tests/rpc/test_rpc_telegram.py | 99 +++++++++++++++++++++++++ 4 files changed, 272 insertions(+), 77 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index a3c6656af..8ccf8bbef 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -850,7 +850,8 @@ class Trade(_DECL_BASE, LocalTrade): .group_by(Trade.pair) \ .order_by(desc('profit_sum_abs')) \ .all() - return [ + + response = [ { 'pair': pair, 'profit': profit, @@ -859,6 +860,8 @@ class Trade(_DECL_BASE, LocalTrade): } for pair, profit, profit_abs, count in pair_rates ] + [x.update({'profit': round(x['profit'] * 100, 2)}) for x in response] + return response @staticmethod def get_buy_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: @@ -868,36 +871,31 @@ class Trade(_DECL_BASE, LocalTrade): NOTE: Not supported in Backtesting. """ + filters = [Trade.is_open.is_(False)] if(pair is not None): - tag_perf = Trade.query.with_entities( - Trade.buy_tag, - func.sum(Trade.close_profit).label('profit_sum'), - func.sum(Trade.close_profit_abs).label('profit_sum_abs'), - func.count(Trade.pair).label('count') - ).filter(Trade.is_open.is_(False))\ - .filter(Trade.pair == pair) \ - .order_by(desc('profit_sum_abs')) \ - .all() - else: - tag_perf = Trade.query.with_entities( - Trade.buy_tag, - func.sum(Trade.close_profit).label('profit_sum'), - func.sum(Trade.close_profit_abs).label('profit_sum_abs'), - func.count(Trade.pair).label('count') - ).filter(Trade.is_open.is_(False))\ - .group_by(Trade.buy_tag) \ - .order_by(desc('profit_sum_abs')) \ - .all() + filters.append(Trade.pair == pair) - return [ + buy_tag_perf = Trade.query.with_entities( + Trade.buy_tag, + func.sum(Trade.close_profit).label('profit_sum'), + func.sum(Trade.close_profit_abs).label('profit_sum_abs'), + func.count(Trade.pair).label('count') + ).filter(*filters)\ + .group_by(Trade.buy_tag) \ + .order_by(desc('profit_sum_abs')) \ + .all() + + response = [ { 'buy_tag': buy_tag if buy_tag is not None else "Other", 'profit': profit, 'profit_abs': profit_abs, 'count': count } - for buy_tag, profit, profit_abs, count in tag_perf + for buy_tag, profit, profit_abs, count in buy_tag_perf ] + [x.update({'profit': round(x['profit'] * 100, 2)}) for x in response] + return response @staticmethod def get_sell_reason_performance(pair: Optional[str]) -> List[Dict[str, Any]]: @@ -906,36 +904,32 @@ class Trade(_DECL_BASE, LocalTrade): Can either be average for all pairs or a specific pair provided NOTE: Not supported in Backtesting. """ - if(pair is not None): - tag_perf = Trade.query.with_entities( - Trade.sell_reason, - func.sum(Trade.close_profit).label('profit_sum'), - func.sum(Trade.close_profit_abs).label('profit_sum_abs'), - func.count(Trade.pair).label('count') - ).filter(Trade.is_open.is_(False))\ - .filter(Trade.pair == pair) \ - .order_by(desc('profit_sum_abs')) \ - .all() - else: - tag_perf = Trade.query.with_entities( - Trade.sell_reason, - func.sum(Trade.close_profit).label('profit_sum'), - func.sum(Trade.close_profit_abs).label('profit_sum_abs'), - func.count(Trade.pair).label('count') - ).filter(Trade.is_open.is_(False))\ - .group_by(Trade.sell_reason) \ - .order_by(desc('profit_sum_abs')) \ - .all() - return [ + filters = [Trade.is_open.is_(False)] + if(pair is not None): + filters.append(Trade.pair == pair) + + sell_tag_perf = Trade.query.with_entities( + Trade.sell_reason, + func.sum(Trade.close_profit).label('profit_sum'), + func.sum(Trade.close_profit_abs).label('profit_sum_abs'), + func.count(Trade.pair).label('count') + ).filter(*filters)\ + .group_by(Trade.sell_reason) \ + .order_by(desc('profit_sum_abs')) \ + .all() + + response = [ { 'sell_reason': sell_reason if sell_reason is not None else "Other", 'profit': profit, 'profit_abs': profit_abs, 'count': count } - for sell_reason, profit, profit_abs, count in tag_perf + for sell_reason, profit, profit_abs, count in sell_tag_perf ] + [x.update({'profit': round(x['profit'] * 100, 2)}) for x in response] + return response @staticmethod def get_mix_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: @@ -944,34 +938,25 @@ class Trade(_DECL_BASE, LocalTrade): Can either be average for all pairs or a specific pair provided NOTE: Not supported in Backtesting. """ - if(pair is not None): - tag_perf = Trade.query.with_entities( - Trade.id, - Trade.buy_tag, - Trade.sell_reason, - func.sum(Trade.close_profit).label('profit_sum'), - func.sum(Trade.close_profit_abs).label('profit_sum_abs'), - func.count(Trade.pair).label('count') - ).filter(Trade.is_open.is_(False))\ - .filter(Trade.pair == pair) \ - .order_by(desc('profit_sum_abs')) \ - .all() - else: - tag_perf = Trade.query.with_entities( - Trade.id, - Trade.buy_tag, - Trade.sell_reason, - func.sum(Trade.close_profit).label('profit_sum'), - func.sum(Trade.close_profit_abs).label('profit_sum_abs'), - func.count(Trade.pair).label('count') - ).filter(Trade.is_open.is_(False))\ - .group_by(Trade.id) \ - .order_by(desc('profit_sum_abs')) \ - .all() + filters = [Trade.is_open.is_(False)] + if(pair is not None): + filters.append(Trade.pair == pair) + + mix_tag_perf = Trade.query.with_entities( + Trade.id, + Trade.buy_tag, + Trade.sell_reason, + func.sum(Trade.close_profit).label('profit_sum'), + func.sum(Trade.close_profit_abs).label('profit_sum_abs'), + func.count(Trade.pair).label('count') + ).filter(*filters)\ + .group_by(Trade.id) \ + .order_by(desc('profit_sum_abs')) \ + .all() return_list: List[Dict] = [] - for id, buy_tag, sell_reason, profit, profit_abs, count in tag_perf: + for id, buy_tag, sell_reason, profit, profit_abs, count in mix_tag_perf: buy_tag = buy_tag if buy_tag is not None else "Other" sell_reason = sell_reason if sell_reason is not None else "Other" @@ -993,6 +978,7 @@ class Trade(_DECL_BASE, LocalTrade): 'count': 1 + return_list[i]["count"]} i += 1 + [x.update({'profit': round(x['profit'] * 100, 2)}) for x in return_list] return return_list @staticmethod diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 4ef9213eb..42d502cd8 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -685,8 +685,7 @@ class RPC: Shows a performance statistic from finished trades """ pair_rates = Trade.get_overall_performance() - # Round and convert to % - [x.update({'profit': round(x['profit'] * 100, 2)}) for x in pair_rates] + return pair_rates def _rpc_buy_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: @@ -695,8 +694,7 @@ class RPC: Shows a performance statistic from finished trades """ buy_tags = Trade.get_buy_tag_performance(pair) - # Round and convert to % - [x.update({'profit': round(x['profit'] * 100, 2)}) for x in buy_tags] + return buy_tags def _rpc_sell_reason_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: @@ -705,8 +703,7 @@ class RPC: Shows a performance statistic from finished trades """ sell_reasons = Trade.get_sell_reason_performance(pair) - # Round and convert to % - [x.update({'profit': round(x['profit'] * 100, 2)}) for x in sell_reasons] + return sell_reasons def _rpc_mix_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: @@ -715,8 +712,7 @@ class RPC: Shows a performance statistic from finished trades """ mix_tags = Trade.get_mix_tag_performance(pair) - # Round and convert to % - [x.update({'profit': round(x['profit'] * 100, 2)}) for x in mix_tags] + return mix_tags def _rpc_count(self) -> Dict[str, float]: diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index f8c923958..78805a456 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -827,6 +827,120 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, assert res[0]['count'] == 1 assert prec_satoshi(res[0]['profit'], 6.2) + # TEST FOR TRADES WITH NO BUY TAG + # TEST TRADE WITH ONE BUY_TAG AND OTHER TWO TRADES WITH THE SAME TAG + # TEST THE SAME FOR A PAIR + + +def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, + limit_sell_order, mocker) -> None: + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_balances=MagicMock(return_value=ticker), + fetch_ticker=ticker, + get_fee=fee, + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + patch_get_signal(freqtradebot) + rpc = RPC(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + res = rpc._rpc_buy_tag_performance(None) + assert len(res) == 1 + assert res[0]['pair'] == 'ETH/BTC' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 6.2) + + # TEST FOR TRADES WITH NO SELL REASON + # TEST TRADE WITH ONE SELL REASON AND OTHER TWO TRADES WITH THE SAME reason + # TEST THE SAME FOR A PAIR + + +def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, fee, + limit_sell_order, mocker) -> None: + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_balances=MagicMock(return_value=ticker), + fetch_ticker=ticker, + get_fee=fee, + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + patch_get_signal(freqtradebot) + rpc = RPC(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + res = rpc._rpc_sell_reason_performance(None) + assert len(res) == 1 + assert res[0]['pair'] == 'ETH/BTC' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 6.2) + + # TEST FOR TRADES WITH NO TAGS + # TEST TRADE WITH ONE TAG MIX AND OTHER TWO TRADES WITH THE SAME TAG MIX + # TEST THE SAME FOR A PAIR + + +def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, + limit_sell_order, mocker) -> None: + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_balances=MagicMock(return_value=ticker), + fetch_ticker=ticker, + get_fee=fee, + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + patch_get_signal(freqtradebot) + rpc = RPC(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + res = rpc._rpc_mix_tag_performance(None) + assert len(res) == 1 + assert res[0]['pair'] == 'ETH/BTC' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 6.2) + def test_rpc_count(mocker, default_conf, ticker, fee) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 01d6d92cf..306181eae 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -978,6 +978,105 @@ def test_performance_handle(default_conf, update, ticker, fee, assert 'Performance' in msg_mock.call_args_list[0][0][0] assert 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' in msg_mock.call_args_list[0][0][0] + # TEST FOR TRADES WITH NO BUY TAG + # TEST TRADE WITH ONE BUY_TAG AND OTHER TWO TRADES WITH THE SAME TAG + # TEST THE SAME FOR A PAIR + + +def test_buy_tag_performance_handle(default_conf, update, ticker, fee, + limit_buy_order, limit_sell_order, mocker) -> None: + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + get_fee=fee, + ) + telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) + patch_get_signal(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + telegram._buy_tag_performance(update=update, context=MagicMock()) + assert msg_mock.call_count == 1 + assert 'Performance' in msg_mock.call_args_list[0][0][0] + assert 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' in msg_mock.call_args_list[0][0][0] + + # TEST FOR TRADES WITH NO SELL REASON + # TEST TRADE WITH ONE SELL REASON AND OTHER TWO TRADES WITH THE SAME reason + # TEST THE SAME FOR A PAIR + + +def test_sell_reason_performance_handle(default_conf, update, ticker, fee, + limit_buy_order, limit_sell_order, mocker) -> None: + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + get_fee=fee, + ) + telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) + patch_get_signal(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + telegram._sell_reason_performance(update=update, context=MagicMock()) + assert msg_mock.call_count == 1 + assert 'Performance' in msg_mock.call_args_list[0][0][0] + assert 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' in msg_mock.call_args_list[0][0][0] + + # TEST FOR TRADES WITH NO TAGS + # TEST TRADE WITH ONE TAG MIX AND OTHER TWO TRADES WITH THE SAME TAG MIX + # TEST THE SAME FOR A PAIR + + +def test_mix_tag_performance_handle(default_conf, update, ticker, fee, + limit_buy_order, limit_sell_order, mocker) -> None: + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + get_fee=fee, + ) + telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) + patch_get_signal(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + telegram._mix_tag_performance(update=update, context=MagicMock()) + assert msg_mock.call_count == 1 + assert 'Performance' in msg_mock.call_args_list[0][0][0] + assert 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' in msg_mock.call_args_list[0][0][0] + def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple(