Merge pull request #8912 from froggleston/rpc_expectancy

Add expectancy to RPC calls and telegram
This commit is contained in:
Matthias
2023-07-19 20:21:09 +02:00
committed by GitHub
7 changed files with 66 additions and 5 deletions
+5
View File
@@ -287,12 +287,17 @@ Return a summary of your profit/loss and performance.
> **Best Performing:** `PAY/BTC: 50.23%` > **Best Performing:** `PAY/BTC: 50.23%`
> **Trading volume:** `0.5 BTC` > **Trading volume:** `0.5 BTC`
> **Profit factor:** `1.04` > **Profit factor:** `1.04`
> **Win / Loss:** `102 / 36`
> **Winrate:** `73.91%`
> **Expectancy (Ratio):** `4.87 (1.66)`
> **Max Drawdown:** `9.23% (0.01255 BTC)` > **Max Drawdown:** `9.23% (0.01255 BTC)`
The relative profit of `1.2%` is the average profit per trade. 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`. 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. 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. 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)`. 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. 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.
+3
View File
@@ -136,6 +136,9 @@ class Profit(BaseModel):
winning_trades: int winning_trades: int
losing_trades: int losing_trades: int
profit_factor: float profit_factor: float
winrate: float
expectancy: float
expectancy_ratio: float
max_drawdown: float max_drawdown: float
max_drawdown_abs: float max_drawdown_abs: float
trading_volume: Optional[float] trading_volume: Optional[float]
+34 -1
View File
@@ -494,6 +494,8 @@ class RPC:
profit_all_coin.append(profit_abs) profit_all_coin.append(profit_abs)
profit_all_ratio.append(profit_ratio) 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) best_pair = Trade.get_best_pair(start_date)
trading_volume = Trade.get_trading_volume(start_date) trading_volume = Trade.get_trading_volume(start_date)
@@ -521,6 +523,17 @@ class RPC:
profit_factor = winning_profit / abs(losing_profit) if losing_profit else float('inf') 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 = (abs(losing_profit) / losing_trades) if losing_trades > 0 else 0
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,
winrate,
loserate)
trades_df = DataFrame([{'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT), trades_df = DataFrame([{'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT),
'profit_abs': trade.close_profit_abs} 'profit_abs': trade.close_profit_abs}
for trade in trades if not trade.is_open and trade.close_date]) for trade in trades if not trade.is_open and trade.close_date])
@@ -562,7 +575,7 @@ class RPC:
'profit_all_percent': round(profit_all_ratio_fromstart * 100, 2), 'profit_all_percent': round(profit_all_ratio_fromstart * 100, 2),
'profit_all_fiat': profit_all_fiat, 'profit_all_fiat': profit_all_fiat,
'trade_count': len(trades), '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': first_date.strftime(DATETIME_PRINT_FORMAT) if first_date else '', 'first_trade_date': first_date.strftime(DATETIME_PRINT_FORMAT) if first_date else '',
'first_trade_humanized': dt_humanize(first_date) if first_date else '', 'first_trade_humanized': dt_humanize(first_date) if first_date else '',
'first_trade_timestamp': int(first_date.timestamp() * 1000) if first_date else 0, 'first_trade_timestamp': int(first_date.timestamp() * 1000) if first_date else 0,
@@ -576,6 +589,9 @@ class RPC:
'winning_trades': winning_trades, 'winning_trades': winning_trades,
'losing_trades': losing_trades, 'losing_trades': losing_trades,
'profit_factor': profit_factor, 'profit_factor': profit_factor,
'winrate': winrate,
'expectancy': expectancy,
'expectancy_ratio': expectancy_ratio,
'max_drawdown': max_drawdown, 'max_drawdown': max_drawdown,
'max_drawdown_abs': max_drawdown_abs, 'max_drawdown_abs': max_drawdown_abs,
'trading_volume': trading_volume, 'trading_volume': trading_volume,
@@ -609,6 +625,23 @@ class RPC:
return est_stake, est_bot_stake 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 = (
(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) - 1
)
return expectancy, expectancy_ratio
def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict: def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict:
""" Returns current account balance per crypto """ """ Returns current account balance per crypto """
currencies: List[Dict] = [] currencies: List[Dict] = []
+7 -1
View File
@@ -849,6 +849,10 @@ class Telegram(RPCHandler):
avg_duration = stats['avg_duration'] avg_duration = stats['avg_duration']
best_pair = stats['best_pair'] best_pair = stats['best_pair']
best_pair_profit_ratio = stats['best_pair_profit_ratio'] best_pair_profit_ratio = stats['best_pair_profit_ratio']
winrate = stats['winrate']
expectancy = stats['expectancy']
expectancy_ratio = stats['expectancy_ratio']
if stats['trade_count'] == 0: if stats['trade_count'] == 0:
markdown_msg = f"No trades yet.\n*Bot started:* `{stats['bot_start_date']}`" markdown_msg = f"No trades yet.\n*Bot started:* `{stats['bot_start_date']}`"
else: else:
@@ -873,7 +877,9 @@ class Telegram(RPCHandler):
f"*{'First Trade opened' if not timescale else 'Showing Profit since'}:* " f"*{'First Trade opened' if not timescale else 'Showing Profit since'}:* "
f"`{first_trade_date}`\n" f"`{first_trade_date}`\n"
f"*Latest Trade opened:* `{latest_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:.2%}`\n"
f"*Expectancy (Ratio):* `{expectancy:.2f} ({expectancy_ratio:.2f})`"
) )
if stats['closed_trade_count'] > 0: if stats['closed_trade_count'] > 0:
markdown_msg += ( markdown_msg += (
+5
View File
@@ -402,6 +402,8 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
assert res['first_trade_timestamp'] == 0 assert res['first_trade_timestamp'] == 0
assert res['latest_trade_date'] == '' assert res['latest_trade_date'] == ''
assert res['latest_trade_timestamp'] == 0 assert res['latest_trade_timestamp'] == 0
assert res['expectancy'] == 0
assert res['expectancy_ratio'] == float('inf')
# Create some test data # Create some test data
create_mock_trades_usdt(fee) create_mock_trades_usdt(fee)
@@ -413,6 +415,9 @@ 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_coin']) == -77.45964918
assert pytest.approx(stats['profit_all_percent_mean']) == -57.86 assert pytest.approx(stats['profit_all_percent_mean']) == -57.86
assert pytest.approx(stats['profit_all_fiat']) == -85.205614098 assert pytest.approx(stats['profit_all_fiat']) == -85.205614098
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['trade_count'] == 7
assert stats['first_trade_humanized'] == '2 days ago' assert stats['first_trade_humanized'] == '2 days ago'
assert stats['latest_trade_humanized'] == '17 minutes ago' assert stats['latest_trade_humanized'] == '17 minutes ago'
+10 -3
View File
@@ -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_mean': -0.75, 'profit_closed_ratio_sum': -0.015,
'profit_closed_percent_sum': -1.5, 'profit_closed_ratio': -6.739057628404269e-06, 'profit_closed_percent_sum': -1.5, 'profit_closed_ratio': -6.739057628404269e-06,
'profit_closed_percent': -0.0, 'winning_trades': 0, 'losing_trades': 2, '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.0033695635,
'expectancy_ratio': -1.0, '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_mean': 0.75, 'profit_closed_ratio_sum': 0.015,
'profit_closed_percent_sum': 1.5, 'profit_closed_ratio': 7.391275897987988e-07, 'profit_closed_percent_sum': 1.5, 'profit_closed_ratio': 7.391275897987988e-07,
'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0, 'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0,
'profit_factor': None, 'trading_volume': 91.074, 'profit_factor': None, 'winrate': 1.0, 'expectancy': 0.0003695635,
'expectancy_ratio': None, 'trading_volume': 91.074,
} }
), ),
( (
@@ -859,7 +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_mean': 0.25, 'profit_closed_ratio_sum': 0.005,
'profit_closed_percent_sum': 0.5, 'profit_closed_ratio': -5.429078808526421e-06, 'profit_closed_percent_sum': 0.5, 'profit_closed_ratio': -5.429078808526421e-06,
'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1, 'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1,
'profit_factor': 0.02775724835771106, 'trading_volume': 91.074, 'profit_factor': 0.02775724835771106, 'winrate': 0.5,
'expectancy': -0.0027145635000000003, 'expectancy_ratio': -0.48612137582114445,
'trading_volume': 91.074,
} }
) )
]) ])
@@ -916,6 +920,9 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
'winning_trades': expected['winning_trades'], 'winning_trades': expected['winning_trades'],
'losing_trades': expected['losing_trades'], 'losing_trades': expected['losing_trades'],
'profit_factor': expected['profit_factor'], 'profit_factor': expected['profit_factor'],
'winrate': expected['winrate'],
'expectancy': expected['expectancy'],
'expectancy_ratio': expected['expectancy_ratio'],
'max_drawdown': ANY, 'max_drawdown': ANY,
'max_drawdown_abs': ANY, 'max_drawdown_abs': ANY,
'trading_volume': expected['trading_volume'], 'trading_volume': expected['trading_volume'],
+2
View File
@@ -799,6 +799,8 @@ async def test_telegram_profit_handle(
assert '*Best Performing:* `ETH/USDT: 9.45%`' in msg_mock.call_args_list[-1][0][0] 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 '*Max Drawdown:*' in msg_mock.call_args_list[-1][0][0]
assert '*Profit factor:*' 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 (Ratio):*' in msg_mock.call_args_list[-1][0][0]
assert '*Trading volume:* `126 USDT`' in msg_mock.call_args_list[-1][0][0] assert '*Trading volume:* `126 USDT`' in msg_mock.call_args_list[-1][0][0]