mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 10:21:59 +00:00
Merge pull request #8912 from froggleston/rpc_expectancy
Add expectancy to RPC calls and telegram
This commit is contained in:
commit
bdb778cb9f
|
@ -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.
|
||||
|
||||
|
|
|
@ -136,6 +136,9 @@ class Profit(BaseModel):
|
|||
winning_trades: int
|
||||
losing_trades: int
|
||||
profit_factor: float
|
||||
winrate: float
|
||||
expectancy: float
|
||||
expectancy_ratio: float
|
||||
max_drawdown: float
|
||||
max_drawdown_abs: float
|
||||
trading_volume: Optional[float]
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
@ -521,6 +523,17 @@ 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 = (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),
|
||||
'profit_abs': trade.close_profit_abs}
|
||||
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_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': first_date.strftime(DATETIME_PRINT_FORMAT) 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,
|
||||
|
@ -576,6 +589,9 @@ class RPC:
|
|||
'winning_trades': winning_trades,
|
||||
'losing_trades': losing_trades,
|
||||
'profit_factor': profit_factor,
|
||||
'winrate': winrate,
|
||||
'expectancy': expectancy,
|
||||
'expectancy_ratio': expectancy_ratio,
|
||||
'max_drawdown': max_drawdown,
|
||||
'max_drawdown_abs': max_drawdown_abs,
|
||||
'trading_volume': trading_volume,
|
||||
|
@ -609,6 +625,23 @@ 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 = (
|
||||
(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:
|
||||
""" Returns current account balance per crypto """
|
||||
currencies: List[Dict] = []
|
||||
|
|
|
@ -849,6 +849,10 @@ 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_ratio = stats['expectancy_ratio']
|
||||
|
||||
if stats['trade_count'] == 0:
|
||||
markdown_msg = f"No trades yet.\n*Bot started:* `{stats['bot_start_date']}`"
|
||||
else:
|
||||
|
@ -873,7 +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']}`"
|
||||
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:
|
||||
markdown_msg += (
|
||||
|
|
|
@ -402,6 +402,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_ratio'] == float('inf')
|
||||
|
||||
# Create some test data
|
||||
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_percent_mean']) == -57.86
|
||||
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['first_trade_humanized'] == '2 days ago'
|
||||
assert stats['latest_trade_humanized'] == '17 minutes ago'
|
||||
|
|
|
@ -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.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_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': 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_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': 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'],
|
||||
'losing_trades': expected['losing_trades'],
|
||||
'profit_factor': expected['profit_factor'],
|
||||
'winrate': expected['winrate'],
|
||||
'expectancy': expected['expectancy'],
|
||||
'expectancy_ratio': expected['expectancy_ratio'],
|
||||
'max_drawdown': ANY,
|
||||
'max_drawdown_abs': ANY,
|
||||
'trading_volume': expected['trading_volume'],
|
||||
|
|
|
@ -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 '*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 (Ratio):*' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*Trading volume:* `126 USDT`' in msg_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user