From 6e56f84fe3135a3ad1a62f429eaf35ebe2983cd2 Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 15 Jul 2023 15:32:52 +0100 Subject: [PATCH 01/13] Add expectancy and winrate to rpc trade statistics --- freqtrade/rpc/rpc.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index ffed3c6d6..9266e239e 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -521,6 +521,24 @@ class RPC: profit_factor = winning_profit / abs(losing_profit) if losing_profit else float('inf') + mean_winning_profit = (winning_profit / winning_trades) if winning_trades > 0 else 0 + mean_losing_profit = (losing_profit / losing_trades) if losing_trades > 0 else 0 + + winrate = winning_trades / closed_trade_count if closed_trade_count > 0 else 0 + loserate = 100 - winrate + + expectancy = 1 + if mean_winning_profit > 0 and mean_losing_profit > 0: + expectancy = (1 + (mean_winning_profit / mean_losing_profit)) * (winrate / 100) - 1 + else: + if mean_winning_profit == 0: + expectancy = 0 + + expectancy_rate = ( + ((winrate/100) * mean_winning_profit) - + ((loserate/100) * mean_losing_profit) + ) + trades_df = DataFrame([{'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT), 'profit_abs': trade.close_profit_abs} for trade in trades if not trade.is_open and trade.close_date]) @@ -574,6 +592,9 @@ class RPC: 'winning_trades': winning_trades, 'losing_trades': losing_trades, 'profit_factor': profit_factor, + 'winrate': winrate, + 'expectancy': expectancy, + 'expectancy_rate': expectancy_rate, 'max_drawdown': max_drawdown, 'max_drawdown_abs': max_drawdown_abs, 'trading_volume': trading_volume, From 4235ab0c7e88af716ba90e03d8da18ac0b7f70e0 Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 15 Jul 2023 15:39:47 +0100 Subject: [PATCH 02/13] Add expectancy and winrate to telegram --- freqtrade/rpc/telegram.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 51f7fb9ee..92b500642 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -815,6 +815,9 @@ class Telegram(RPCHandler): avg_duration = stats['avg_duration'] best_pair = stats['best_pair'] best_pair_profit_ratio = stats['best_pair_profit_ratio'] + winrate = stats['winrate'] + expectancy = stats['expectancy'] + expectancy_rate = stats['expectancy_rate'] if stats['trade_count'] == 0: markdown_msg = f"No trades yet.\n*Bot started:* `{stats['bot_start_date']}`" else: @@ -839,7 +842,9 @@ class Telegram(RPCHandler): f"*{'First Trade opened' if not timescale else 'Showing Profit since'}:* " f"`{first_trade_date}`\n" f"*Latest Trade opened:* `{latest_trade_date}`\n" - f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`" + f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}\n" + f"*Winrate:* `({winrate:.2f}%)`\n" + f"*Expectancy (Rate):* `{expectancy:.2f} ({expectancy_rate:.2f})`\n" ) if stats['closed_trade_count'] > 0: markdown_msg += ( From 096cb0d1ee636f9231224b4ee0189cc505a3728f Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 15 Jul 2023 16:09:13 +0100 Subject: [PATCH 03/13] Add tests, fix winrate calc --- freqtrade/rpc/rpc.py | 14 ++++++++------ tests/rpc/test_rpc.py | 5 +++++ tests/rpc/test_rpc_telegram.py | 2 ++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 9266e239e..1312199bc 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -494,6 +494,8 @@ class RPC: profit_all_coin.append(profit_abs) profit_all_ratio.append(profit_ratio) + closed_trade_count = len([t for t in trades if not t.is_open]) + best_pair = Trade.get_best_pair(start_date) trading_volume = Trade.get_trading_volume(start_date) @@ -524,18 +526,18 @@ class RPC: mean_winning_profit = (winning_profit / winning_trades) if winning_trades > 0 else 0 mean_losing_profit = (losing_profit / losing_trades) if losing_trades > 0 else 0 - winrate = winning_trades / closed_trade_count if closed_trade_count > 0 else 0 - loserate = 100 - winrate + winrate = (winning_trades / closed_trade_count)*100 if closed_trade_count > 0 else 0 + loserate = (100 - winrate) - expectancy = 1 + expectancy = 1.0 if mean_winning_profit > 0 and mean_losing_profit > 0: expectancy = (1 + (mean_winning_profit / mean_losing_profit)) * (winrate / 100) - 1 else: if mean_winning_profit == 0: - expectancy = 0 + expectancy = 0.0 expectancy_rate = ( - ((winrate/100) * mean_winning_profit) - + ((winrate/100) * mean_winning_profit) - ((loserate/100) * mean_losing_profit) ) @@ -580,7 +582,7 @@ class RPC: 'profit_all_percent': round(profit_all_ratio_fromstart * 100, 2), 'profit_all_fiat': profit_all_fiat, 'trade_count': len(trades), - 'closed_trade_count': len([t for t in trades if not t.is_open]), + 'closed_trade_count': closed_trade_count, '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/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index ff08a0564..9dc151ee8 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -403,6 +403,8 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: assert res['first_trade_timestamp'] == 0 assert res['latest_trade_date'] == '' assert res['latest_trade_timestamp'] == 0 + assert res['expectancy'] == 0 + assert res['expectancy_rate'] == 0 # Create some test data create_mock_trades_usdt(fee) @@ -414,12 +416,15 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: assert pytest.approx(stats['profit_all_coin']) == -77.45964918 assert pytest.approx(stats['profit_all_percent_mean']) == -57.86 assert pytest.approx(stats['profit_all_fiat']) == -85.205614098 + assert pytest.approx(stats['winrate']) == 66.666666667 assert stats['trade_count'] == 7 assert stats['first_trade_date'] == '2 days ago' assert stats['latest_trade_date'] == '17 minutes ago' assert stats['avg_duration'] in ('0:17:40') assert stats['best_pair'] == 'XRP/USDT' assert stats['best_rate'] == 10.0 + assert stats['expectancy'] == 1.0 + assert stats['expectancy_rate'] == 3.64 # Test non-available pair mocker.patch(f'{EXMS}.get_rate', diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 7978a2a23..823b270ef 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -743,6 +743,8 @@ def test_telegram_profit_handle( assert '*Best Performing:* `ETH/USDT: 9.45%`' in msg_mock.call_args_list[-1][0][0] assert '*Max Drawdown:*' in msg_mock.call_args_list[-1][0][0] assert '*Profit factor:*' in msg_mock.call_args_list[-1][0][0] + assert '*Winrate:*' in msg_mock.call_args_list[-1][0][0] + assert '*Expectancy (Rate):*' in msg_mock.call_args_list[-1][0][0] assert '*Trading volume:* `126 USDT`' in msg_mock.call_args_list[-1][0][0] From 3ce17b740b0c0b0160c283e6c543d00f942d101e Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 15 Jul 2023 16:25:19 +0100 Subject: [PATCH 04/13] Fix flake8 problems --- freqtrade/rpc/rpc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index fe676a2ba..c68bd7d92 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -526,19 +526,19 @@ class RPC: mean_winning_profit = (winning_profit / winning_trades) if winning_trades > 0 else 0 mean_losing_profit = (losing_profit / losing_trades) if losing_trades > 0 else 0 - winrate = (winning_trades / closed_trade_count)*100 if closed_trade_count > 0 else 0 + winrate = (winning_trades / closed_trade_count) * 100 if closed_trade_count > 0 else 0 loserate = (100 - winrate) - + expectancy = 1.0 if mean_winning_profit > 0 and mean_losing_profit > 0: expectancy = (1 + (mean_winning_profit / mean_losing_profit)) * (winrate / 100) - 1 else: if mean_winning_profit == 0: expectancy = 0.0 - + expectancy_rate = ( - ((winrate/100) * mean_winning_profit) - - ((loserate/100) * mean_losing_profit) + ((winrate / 100) * mean_winning_profit) - + ((loserate / 100) * mean_losing_profit) ) trades_df = DataFrame([{'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT), From e57bb6bc970e58d2bf673dc158f1147f363bf00e Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 15 Jul 2023 16:27:58 +0100 Subject: [PATCH 05/13] Add api schema entries --- freqtrade/rpc/api_server/api_schemas.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 2c2d40a3d..0de594baf 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -136,6 +136,9 @@ class Profit(BaseModel): winning_trades: int losing_trades: int profit_factor: float + winrate: float + expectancy: float + expectancy_rate: float max_drawdown: float max_drawdown_abs: float trading_volume: Optional[float] From d4b282d6f740a99871a6b801fe6aed932b82f8ce Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 15 Jul 2023 16:51:45 +0100 Subject: [PATCH 06/13] Fix expectancy calc and tests --- freqtrade/rpc/rpc.py | 4 ++-- tests/rpc/test_rpc.py | 2 +- tests/rpc/test_rpc_apiserver.py | 12 +++++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index c68bd7d92..626cbd208 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -530,8 +530,8 @@ class RPC: loserate = (100 - winrate) expectancy = 1.0 - if mean_winning_profit > 0 and mean_losing_profit > 0: - expectancy = (1 + (mean_winning_profit / mean_losing_profit)) * (winrate / 100) - 1 + if mean_winning_profit > 0 and abs(mean_losing_profit) > 0: + expectancy = (1 + (mean_winning_profit / abs(mean_losing_profit))) * (winrate / 100) - 1 else: if mean_winning_profit == 0: expectancy = 0.0 diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 1c59748ed..47a5b424a 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -416,13 +416,13 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: assert pytest.approx(stats['profit_all_percent_mean']) == -57.86 assert pytest.approx(stats['profit_all_fiat']) == -85.205614098 assert pytest.approx(stats['winrate']) == 66.666666667 + assert pytest.approx(stats['expectancy']) == 0.223308883 assert stats['trade_count'] == 7 assert stats['first_trade_humanized'] == '2 days ago' assert stats['latest_trade_humanized'] == '17 minutes ago' assert stats['avg_duration'] in ('0:17:40') assert stats['best_pair'] == 'XRP/USDT' assert stats['best_rate'] == 10.0 - assert stats['expectancy'] == 1.0 assert stats['expectancy_rate'] == 3.64 # Test non-available pair diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index f793b1f9c..9de97159e 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -829,7 +829,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_mean': -0.75, 'profit_closed_ratio_sum': -0.015, 'profit_closed_percent_sum': -1.5, 'profit_closed_ratio': -6.739057628404269e-06, 'profit_closed_percent': -0.0, 'winning_trades': 0, 'losing_trades': 2, - 'profit_factor': 0.0, 'trading_volume': 91.074, + 'profit_factor': 0.0, 'winrate':0.0, 'expectancy': 0.0, 'expectancy_rate': 0.0033695635, + 'trading_volume': 91.074, } ), ( @@ -844,7 +845,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_mean': 0.75, 'profit_closed_ratio_sum': 0.015, 'profit_closed_percent_sum': 1.5, 'profit_closed_ratio': 7.391275897987988e-07, 'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0, - 'profit_factor': None, 'trading_volume': 91.074, + 'profit_factor': None, 'winrate':100.0, 'expectancy': 1.0, 'expectancy_rate': 0.0003695635, + 'trading_volume': 91.074, } ), ( @@ -859,7 +861,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_mean': 0.25, 'profit_closed_ratio_sum': 0.005, 'profit_closed_percent_sum': 0.5, 'profit_closed_ratio': -5.429078808526421e-06, 'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1, - 'profit_factor': 0.02775724835771106, 'trading_volume': 91.074, + 'profit_factor': 0.02775724835771106, 'winrate':50.0, 'expectancy': -0.48612137582114445, + 'expectancy_rate': 0.0028695635, 'trading_volume': 91.074, } ) ]) @@ -916,6 +919,9 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected) 'winning_trades': expected['winning_trades'], 'losing_trades': expected['losing_trades'], 'profit_factor': expected['profit_factor'], + 'winrate': expected['winrate'], + 'expectancy': expected['expectancy'], + 'expectancy_rate': expected['expectancy_rate'], 'max_drawdown': ANY, 'max_drawdown_abs': ANY, 'trading_volume': expected['trading_volume'], From 59cb9e39dde0c7a9192db113330dbedf44ac7a1b Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 15 Jul 2023 16:55:24 +0100 Subject: [PATCH 07/13] Fix another trailing whitespace --- freqtrade/rpc/api_server/api_schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 0de594baf..9a238e2c2 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -138,7 +138,7 @@ class Profit(BaseModel): profit_factor: float winrate: float expectancy: float - expectancy_rate: float + expectancy_rate: float max_drawdown: float max_drawdown_abs: float trading_volume: Optional[float] From 9d36dc7ac63690600056a74cb0bab8a03c95f0ca Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 15 Jul 2023 16:59:33 +0100 Subject: [PATCH 08/13] Fix line length --- tests/rpc/test_rpc_apiserver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 9de97159e..3b319dbb0 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -829,7 +829,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_mean': -0.75, 'profit_closed_ratio_sum': -0.015, 'profit_closed_percent_sum': -1.5, 'profit_closed_ratio': -6.739057628404269e-06, 'profit_closed_percent': -0.0, 'winning_trades': 0, 'losing_trades': 2, - 'profit_factor': 0.0, 'winrate':0.0, 'expectancy': 0.0, 'expectancy_rate': 0.0033695635, + 'profit_factor': 0.0, 'winrate': 0.0, 'expectancy': 0.0, 'expectancy_rate': 0.0033695635, 'trading_volume': 91.074, } ), @@ -845,8 +845,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_mean': 0.75, 'profit_closed_ratio_sum': 0.015, 'profit_closed_percent_sum': 1.5, 'profit_closed_ratio': 7.391275897987988e-07, 'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0, - 'profit_factor': None, 'winrate':100.0, 'expectancy': 1.0, 'expectancy_rate': 0.0003695635, - 'trading_volume': 91.074, + 'profit_factor': None, 'winrate': 100.0, 'expectancy': 1.0, + 'expectancy_rate': 0.0003695635, 'trading_volume': 91.074, } ), ( @@ -861,7 +861,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_mean': 0.25, 'profit_closed_ratio_sum': 0.005, 'profit_closed_percent_sum': 0.5, 'profit_closed_ratio': -5.429078808526421e-06, 'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1, - 'profit_factor': 0.02775724835771106, 'winrate':50.0, 'expectancy': -0.48612137582114445, + 'profit_factor': 0.02775724835771106, 'winrate': 50.0, 'expectancy': -0.48612137582114445, 'expectancy_rate': 0.0028695635, 'trading_volume': 91.074, } ) From 1fd2a2532da2dec87f47f422188362dbcc43e3bb Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 15 Jul 2023 17:06:52 +0100 Subject: [PATCH 09/13] Reduce trade stats function complexity --- freqtrade/rpc/rpc.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 626cbd208..92760bf06 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -529,17 +529,10 @@ class RPC: winrate = (winning_trades / closed_trade_count) * 100 if closed_trade_count > 0 else 0 loserate = (100 - winrate) - expectancy = 1.0 - if mean_winning_profit > 0 and abs(mean_losing_profit) > 0: - expectancy = (1 + (mean_winning_profit / abs(mean_losing_profit))) * (winrate / 100) - 1 - else: - if mean_winning_profit == 0: - expectancy = 0.0 - - expectancy_rate = ( - ((winrate / 100) * mean_winning_profit) - - ((loserate / 100) * mean_losing_profit) - ) + expectancy, expectancy_rate = self.__calc_expectancy(mean_winning_profit, + mean_losing_profit, + winrate, + loserate) trades_df = DataFrame([{'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT), 'profit_abs': trade.close_profit_abs} @@ -632,6 +625,24 @@ class RPC: return est_stake, est_bot_stake + def __calc_expectancy( + self, mean_winning_profit: float, mean_losing_profit: float, + winrate: float, loserate: float) -> Tuple[float, float]: + expectancy = 1.0 + if mean_winning_profit > 0 and abs(mean_losing_profit) > 0: + expectancy = ( + (1 + (mean_winning_profit / abs(mean_losing_profit))) * (winrate / 100) - 1 + ) + elif mean_winning_profit == 0: + expectancy = 0.0 + + expectancy_rate = ( + ((winrate / 100) * mean_winning_profit) - + ((loserate / 100) * mean_losing_profit) + ) + + return expectancy, expectancy_rate + def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict: """ Returns current account balance per crypto """ currencies: List[Dict] = [] From 79f7f82c590c39849ef1994eb6318df538a751ee Mon Sep 17 00:00:00 2001 From: froggleston Date: Sun, 16 Jul 2023 16:52:06 +0100 Subject: [PATCH 10/13] Fix telegram output --- freqtrade/rpc/rpc.py | 1 + freqtrade/rpc/telegram.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 92760bf06..c778025ac 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -628,6 +628,7 @@ class RPC: def __calc_expectancy( self, mean_winning_profit: float, mean_losing_profit: float, winrate: float, loserate: float) -> Tuple[float, float]: + expectancy = 1.0 if mean_winning_profit > 0 and abs(mean_losing_profit) > 0: expectancy = ( diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 33db5a76d..9b3cc4714 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -852,6 +852,7 @@ class Telegram(RPCHandler): winrate = stats['winrate'] expectancy = stats['expectancy'] expectancy_rate = stats['expectancy_rate'] + if stats['trade_count'] == 0: markdown_msg = f"No trades yet.\n*Bot started:* `{stats['bot_start_date']}`" else: @@ -876,9 +877,9 @@ class Telegram(RPCHandler): f"*{'First Trade opened' if not timescale else 'Showing Profit since'}:* " f"`{first_trade_date}`\n" f"*Latest Trade opened:* `{latest_trade_date}`\n" - f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}\n" - f"*Winrate:* `({winrate:.2f}%)`\n" - f"*Expectancy (Rate):* `{expectancy:.2f} ({expectancy_rate:.2f})`\n" + f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`\n" + f"*Winrate:* `{winrate:.2f}%`\n" + f"*Expectancy (Rate):* `{expectancy:.2f} ({expectancy_rate:.2f})`" ) if stats['closed_trade_count'] > 0: markdown_msg += ( From 6ccc12f33795981d2839c2dbd4fa27e9b493ac21 Mon Sep 17 00:00:00 2001 From: froggleston Date: Mon, 17 Jul 2023 14:16:22 +0100 Subject: [PATCH 11/13] Fix calcs, rename ratio, add docs --- docs/telegram-usage.md | 5 +++++ freqtrade/rpc/api_server/api_schemas.py | 2 +- freqtrade/rpc/rpc.py | 30 ++++++++++++------------- freqtrade/rpc/telegram.py | 4 ++-- tests/rpc/test_rpc.py | 6 ++--- tests/rpc/test_rpc_apiserver.py | 15 +++++++------ tests/rpc/test_rpc_telegram.py | 2 +- 7 files changed, 34 insertions(+), 30 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 1b36c60ad..f501d0e49 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -287,12 +287,17 @@ Return a summary of your profit/loss and performance. > **Best Performing:** `PAY/BTC: 50.23%` > **Trading volume:** `0.5 BTC` > **Profit factor:** `1.04` +> **Win / Loss:** `102 / 36` +> **Winrate:** `73.91%` +> **Expectancy (Ratio):** `4.87 (1.66)` > **Max Drawdown:** `9.23% (0.01255 BTC)` The relative profit of `1.2%` is the average profit per trade. The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`. Starting capital is either taken from the `available_capital` setting, or calculated by using current wallet size - profits. Profit Factor is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy. +Expectancy corresponds to the average return per currency unit at risk, i.e. the winrate and the risk-reward ratio (the average gain of winning trades compared to the average loss of losing trades). +Expectancy Ratio is expected profit or loss of a subsequent trade based on the performance of all past trades. Max drawdown corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`. Bot started date will refer to the date the bot was first started. For older bots, this will default to the first trade's open date. diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 9a238e2c2..04769b119 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -138,7 +138,7 @@ class Profit(BaseModel): profit_factor: float winrate: float expectancy: float - expectancy_rate: float + expectancy_ratio: float max_drawdown: float max_drawdown_abs: float trading_volume: Optional[float] diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index c778025ac..b1125e225 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -524,15 +524,15 @@ class RPC: profit_factor = winning_profit / abs(losing_profit) if losing_profit else float('inf') mean_winning_profit = (winning_profit / winning_trades) if winning_trades > 0 else 0 - mean_losing_profit = (losing_profit / losing_trades) if losing_trades > 0 else 0 + mean_losing_profit = (abs(losing_profit) / losing_trades) if losing_trades > 0 else 0 winrate = (winning_trades / closed_trade_count) * 100 if closed_trade_count > 0 else 0 loserate = (100 - winrate) - expectancy, expectancy_rate = self.__calc_expectancy(mean_winning_profit, - mean_losing_profit, - winrate, - loserate) + expectancy, expectancy_ratio = self.__calc_expectancy(mean_winning_profit, + mean_losing_profit, + winrate, + loserate) trades_df = DataFrame([{'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT), 'profit_abs': trade.close_profit_abs} @@ -591,7 +591,7 @@ class RPC: 'profit_factor': profit_factor, 'winrate': winrate, 'expectancy': expectancy, - 'expectancy_rate': expectancy_rate, + 'expectancy_ratio': expectancy_ratio, 'max_drawdown': max_drawdown, 'max_drawdown_abs': max_drawdown_abs, 'trading_volume': trading_volume, @@ -629,20 +629,18 @@ class RPC: self, mean_winning_profit: float, mean_losing_profit: float, winrate: float, loserate: float) -> Tuple[float, float]: - expectancy = 1.0 - if mean_winning_profit > 0 and abs(mean_losing_profit) > 0: - expectancy = ( - (1 + (mean_winning_profit / abs(mean_losing_profit))) * (winrate / 100) - 1 - ) - elif mean_winning_profit == 0: - expectancy = 0.0 - - expectancy_rate = ( + expectancy = ( ((winrate / 100) * mean_winning_profit) - ((loserate / 100) * mean_losing_profit) ) - return expectancy, expectancy_rate + expectancy_ratio = float('inf') + if mean_losing_profit > 0: + expectancy_ratio = ( + ((1 + (mean_winning_profit / mean_losing_profit)) * (winrate / 100)) - 1 + ) + + return expectancy, expectancy_ratio def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict: """ Returns current account balance per crypto """ diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 9b3cc4714..1afd6f801 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -851,7 +851,7 @@ class Telegram(RPCHandler): best_pair_profit_ratio = stats['best_pair_profit_ratio'] winrate = stats['winrate'] expectancy = stats['expectancy'] - expectancy_rate = stats['expectancy_rate'] + expectancy_ratio = stats['expectancy_ratio'] if stats['trade_count'] == 0: markdown_msg = f"No trades yet.\n*Bot started:* `{stats['bot_start_date']}`" @@ -879,7 +879,7 @@ class Telegram(RPCHandler): f"*Latest Trade opened:* `{latest_trade_date}`\n" f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`\n" f"*Winrate:* `{winrate:.2f}%`\n" - f"*Expectancy (Rate):* `{expectancy:.2f} ({expectancy_rate:.2f})`" + f"*Expectancy (Ratio):* `{expectancy:.2f} ({expectancy_ratio:.2f})`" ) if stats['closed_trade_count'] > 0: markdown_msg += ( diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 47a5b424a..9c9bca56b 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -403,7 +403,7 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: assert res['latest_trade_date'] == '' assert res['latest_trade_timestamp'] == 0 assert res['expectancy'] == 0 - assert res['expectancy_rate'] == 0 + assert res['expectancy_ratio'] == float('inf') # Create some test data create_mock_trades_usdt(fee) @@ -416,14 +416,14 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: assert pytest.approx(stats['profit_all_percent_mean']) == -57.86 assert pytest.approx(stats['profit_all_fiat']) == -85.205614098 assert pytest.approx(stats['winrate']) == 66.666666667 - assert pytest.approx(stats['expectancy']) == 0.223308883 + assert pytest.approx(stats['expectancy']) == 0.9133333333333327 assert stats['trade_count'] == 7 assert stats['first_trade_humanized'] == '2 days ago' assert stats['latest_trade_humanized'] == '17 minutes ago' assert stats['avg_duration'] in ('0:17:40') assert stats['best_pair'] == 'XRP/USDT' assert stats['best_rate'] == 10.0 - assert stats['expectancy_rate'] == 3.64 + assert stats['expectancy_ratio'] == 0.22330888345558253 # Test non-available pair mocker.patch(f'{EXMS}.get_rate', diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 3b319dbb0..846def0fb 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -829,8 +829,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_mean': -0.75, 'profit_closed_ratio_sum': -0.015, 'profit_closed_percent_sum': -1.5, 'profit_closed_ratio': -6.739057628404269e-06, 'profit_closed_percent': -0.0, 'winning_trades': 0, 'losing_trades': 2, - 'profit_factor': 0.0, 'winrate': 0.0, 'expectancy': 0.0, 'expectancy_rate': 0.0033695635, - 'trading_volume': 91.074, + 'profit_factor': 0.0, 'winrate': 0.0, 'expectancy': -0.0033695635, + 'expectancy_ratio': -1.0, 'trading_volume': 91.074, } ), ( @@ -845,8 +845,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_mean': 0.75, 'profit_closed_ratio_sum': 0.015, 'profit_closed_percent_sum': 1.5, 'profit_closed_ratio': 7.391275897987988e-07, 'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0, - 'profit_factor': None, 'winrate': 100.0, 'expectancy': 1.0, - 'expectancy_rate': 0.0003695635, 'trading_volume': 91.074, + 'profit_factor': None, 'winrate': 100.0, 'expectancy': 0.0003695635, + 'expectancy_ratio': None, 'trading_volume': 91.074, } ), ( @@ -861,8 +861,9 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_mean': 0.25, 'profit_closed_ratio_sum': 0.005, 'profit_closed_percent_sum': 0.5, 'profit_closed_ratio': -5.429078808526421e-06, 'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1, - 'profit_factor': 0.02775724835771106, 'winrate': 50.0, 'expectancy': -0.48612137582114445, - 'expectancy_rate': 0.0028695635, 'trading_volume': 91.074, + 'profit_factor': 0.02775724835771106, 'winrate': 50.0, + 'expectancy': -0.0027145635000000003, 'expectancy_ratio': -0.48612137582114445, + 'trading_volume': 91.074, } ) ]) @@ -921,7 +922,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected) 'profit_factor': expected['profit_factor'], 'winrate': expected['winrate'], 'expectancy': expected['expectancy'], - 'expectancy_rate': expected['expectancy_rate'], + 'expectancy_ratio': expected['expectancy_ratio'], 'max_drawdown': ANY, 'max_drawdown_abs': ANY, 'trading_volume': expected['trading_volume'], diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index ee4115f97..72d70bfb9 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -800,7 +800,7 @@ async def test_telegram_profit_handle( assert '*Max Drawdown:*' in msg_mock.call_args_list[-1][0][0] assert '*Profit factor:*' in msg_mock.call_args_list[-1][0][0] assert '*Winrate:*' in msg_mock.call_args_list[-1][0][0] - assert '*Expectancy (Rate):*' in msg_mock.call_args_list[-1][0][0] + assert '*Expectancy (Ratio):*' in msg_mock.call_args_list[-1][0][0] assert '*Trading volume:* `126 USDT`' in msg_mock.call_args_list[-1][0][0] From f95f954df7e313c52189b5b777babfc19648b8a5 Mon Sep 17 00:00:00 2001 From: froggleston Date: Tue, 18 Jul 2023 22:25:17 +0100 Subject: [PATCH 12/13] Convert winrate to ratio instead of % in calculations --- freqtrade/rpc/rpc.py | 10 +++++----- freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc.py | 6 +++--- tests/rpc/test_rpc_apiserver.py | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index b1125e225..53e3f97e5 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -526,8 +526,8 @@ class RPC: mean_winning_profit = (winning_profit / winning_trades) if winning_trades > 0 else 0 mean_losing_profit = (abs(losing_profit) / losing_trades) if losing_trades > 0 else 0 - winrate = (winning_trades / closed_trade_count) * 100 if closed_trade_count > 0 else 0 - loserate = (100 - winrate) + winrate = (winning_trades / closed_trade_count) if closed_trade_count > 0 else 0 + loserate = (1 - winrate) expectancy, expectancy_ratio = self.__calc_expectancy(mean_winning_profit, mean_losing_profit, @@ -630,14 +630,14 @@ class RPC: winrate: float, loserate: float) -> Tuple[float, float]: expectancy = ( - ((winrate / 100) * mean_winning_profit) - - ((loserate / 100) * mean_losing_profit) + (winrate * mean_winning_profit) - + (loserate * mean_losing_profit) ) expectancy_ratio = float('inf') if mean_losing_profit > 0: expectancy_ratio = ( - ((1 + (mean_winning_profit / mean_losing_profit)) * (winrate / 100)) - 1 + ((1 + (mean_winning_profit / mean_losing_profit)) * winrate) - 1 ) return expectancy, expectancy_ratio diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 1afd6f801..d85d20894 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -878,7 +878,7 @@ class Telegram(RPCHandler): f"`{first_trade_date}`\n" f"*Latest Trade opened:* `{latest_trade_date}`\n" f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`\n" - f"*Winrate:* `{winrate:.2f}%`\n" + f"*Winrate:* `{winrate:.2%}%`\n" f"*Expectancy (Ratio):* `{expectancy:.2f} ({expectancy_ratio:.2f})`" ) if stats['closed_trade_count'] > 0: diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 9c9bca56b..65db6770a 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -415,15 +415,15 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: assert pytest.approx(stats['profit_all_coin']) == -77.45964918 assert pytest.approx(stats['profit_all_percent_mean']) == -57.86 assert pytest.approx(stats['profit_all_fiat']) == -85.205614098 - assert pytest.approx(stats['winrate']) == 66.666666667 - assert pytest.approx(stats['expectancy']) == 0.9133333333333327 + assert pytest.approx(stats['winrate']) == 0.666666667 + assert pytest.approx(stats['expectancy']) == 0.913333333 + assert pytest.approx(stats['expectancy_ratio']) == 0.223308883 assert stats['trade_count'] == 7 assert stats['first_trade_humanized'] == '2 days ago' assert stats['latest_trade_humanized'] == '17 minutes ago' assert stats['avg_duration'] in ('0:17:40') assert stats['best_pair'] == 'XRP/USDT' assert stats['best_rate'] == 10.0 - assert stats['expectancy_ratio'] == 0.22330888345558253 # Test non-available pair mocker.patch(f'{EXMS}.get_rate', diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 846def0fb..851e035b1 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -845,7 +845,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_mean': 0.75, 'profit_closed_ratio_sum': 0.015, 'profit_closed_percent_sum': 1.5, 'profit_closed_ratio': 7.391275897987988e-07, 'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0, - 'profit_factor': None, 'winrate': 100.0, 'expectancy': 0.0003695635, + 'profit_factor': None, 'winrate': 1.0, 'expectancy': 0.0003695635, 'expectancy_ratio': None, 'trading_volume': 91.074, } ), @@ -861,7 +861,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_mean': 0.25, 'profit_closed_ratio_sum': 0.005, 'profit_closed_percent_sum': 0.5, 'profit_closed_ratio': -5.429078808526421e-06, 'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1, - 'profit_factor': 0.02775724835771106, 'winrate': 50.0, + 'profit_factor': 0.02775724835771106, 'winrate': 0.5, 'expectancy': -0.0027145635000000003, 'expectancy_ratio': -0.48612137582114445, 'trading_volume': 91.074, } From ab5b2728685d97108128a8b0ef6bcb5b44eda3f8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 19 Jul 2023 19:53:00 +0200 Subject: [PATCH 13/13] Fix double-%% --- freqtrade/rpc/telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index d85d20894..aced89d7a 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -878,7 +878,7 @@ class Telegram(RPCHandler): f"`{first_trade_date}`\n" f"*Latest Trade opened:* `{latest_trade_date}`\n" f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`\n" - f"*Winrate:* `{winrate:.2%}%`\n" + f"*Winrate:* `{winrate:.2%}`\n" f"*Expectancy (Ratio):* `{expectancy:.2f} ({expectancy_ratio:.2f})`" ) if stats['closed_trade_count'] > 0: