Add Drawdown and profit_factor to /profit

#6816
This commit is contained in:
Matthias 2022-06-18 11:14:28 +02:00
parent d77ce468ea
commit 6a15d36d14
6 changed files with 51 additions and 8 deletions

View File

@ -497,8 +497,10 @@ def generate_strategy_stats(pairlist: List[str],
(drawdown_abs, drawdown_start, drawdown_end, high_val, low_val, (drawdown_abs, drawdown_start, drawdown_end, high_val, low_val,
max_drawdown) = calculate_max_drawdown( max_drawdown) = calculate_max_drawdown(
results, value_col='profit_abs', starting_balance=start_balance) results, value_col='profit_abs', starting_balance=start_balance)
# max_relative_drawdown = Underwater
(_, _, _, _, _, max_relative_drawdown) = calculate_max_drawdown( (_, _, _, _, _, max_relative_drawdown) = calculate_max_drawdown(
results, value_col='profit_abs', starting_balance=start_balance, relative=True) results, value_col='profit_abs', starting_balance=start_balance, relative=True)
strat_stats.update({ strat_stats.update({
'max_drawdown': max_drawdown_legacy, # Deprecated - do not use 'max_drawdown': max_drawdown_legacy, # Deprecated - do not use
'max_drawdown_account': max_drawdown, 'max_drawdown_account': max_drawdown,

View File

@ -104,6 +104,9 @@ class Profit(BaseModel):
best_pair_profit_ratio: float best_pair_profit_ratio: float
winning_trades: int winning_trades: int
losing_trades: int losing_trades: int
profit_factor: float
max_drawdown: float
max_drawdown_abs: float
class SellReason(BaseModel): class SellReason(BaseModel):

View File

@ -18,6 +18,7 @@ from freqtrade import __version__
from freqtrade.configuration.timerange import TimeRange from freqtrade.configuration.timerange import TimeRange
from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT
from freqtrade.data.history import load_data from freqtrade.data.history import load_data
from freqtrade.data.metrics import calculate_max_drawdown
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, State, from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, State,
TradingMode) TradingMode)
from freqtrade.exceptions import ExchangeError, PricingError from freqtrade.exceptions import ExchangeError, PricingError
@ -415,6 +416,8 @@ class RPC:
durations = [] durations = []
winning_trades = 0 winning_trades = 0
losing_trades = 0 losing_trades = 0
winning_profit = 0.0
losing_profit = 0.0
for trade in trades: for trade in trades:
current_rate: float = 0.0 current_rate: float = 0.0
@ -430,8 +433,10 @@ class RPC:
profit_closed_ratio.append(profit_ratio) profit_closed_ratio.append(profit_ratio)
if trade.close_profit >= 0: if trade.close_profit >= 0:
winning_trades += 1 winning_trades += 1
winning_profit += trade.close_profit_abs
else: else:
losing_trades += 1 losing_trades += 1
losing_profit += trade.close_profit_abs
else: else:
# Get current rate # Get current rate
try: try:
@ -470,6 +475,21 @@ class RPC:
profit_closed_ratio_fromstart = profit_closed_coin_sum / starting_balance profit_closed_ratio_fromstart = profit_closed_coin_sum / starting_balance
profit_all_ratio_fromstart = profit_all_coin_sum / starting_balance profit_all_ratio_fromstart = profit_all_coin_sum / starting_balance
profit_factor = winning_profit / abs(losing_profit) if losing_profit else float('inf')
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])
max_drawdown_abs = 0.0
max_drawdown = 0.0
if len(trades_df) > 0:
try:
(max_drawdown_abs, _, _, _, _, max_drawdown) = calculate_max_drawdown(
trades_df, value_col='profit_abs', starting_balance=starting_balance)
except ValueError:
# ValueError if no losing trade.
pass
profit_all_fiat = self._fiat_converter.convert_amount( profit_all_fiat = self._fiat_converter.convert_amount(
profit_all_coin_sum, profit_all_coin_sum,
stake_currency, stake_currency,
@ -508,6 +528,9 @@ class RPC:
'best_pair_profit_ratio': best_pair[1] if best_pair else 0, 'best_pair_profit_ratio': best_pair[1] if best_pair else 0,
'winning_trades': winning_trades, 'winning_trades': winning_trades,
'losing_trades': losing_trades, 'losing_trades': losing_trades,
'profit_factor': profit_factor,
'max_drawdown': max_drawdown,
'max_drawdown_abs': max_drawdown_abs,
} }
def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict: def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict:

View File

@ -730,12 +730,17 @@ class Telegram(RPCHandler):
f"*Total Trade Count:* `{trade_count}`\n" f"*Total Trade Count:* `{trade_count}`\n"
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']}`"
) )
if stats['closed_trade_count'] > 0: if stats['closed_trade_count'] > 0:
markdown_msg += (f"\n*Avg. Duration:* `{avg_duration}`\n" markdown_msg += (
f"*Best Performing:* `{best_pair}: {best_pair_profit_ratio:.2%}`") f"\n*Avg. Duration:* `{avg_duration}`\n"
f"*Best Performing:* `{best_pair}: {best_pair_profit_ratio:.2%}`\n"
f"*Profit factor:* `{stats['profit_factor']:.2f}`\n"
f"*Max Drawdown:* `{stats['max_drawdown']:.2%} "
f"({round_coin_value(stats['max_drawdown_abs'], stake_cur)})`"
)
self._send_msg(markdown_msg, reload_able=True, callback_path="update_profit", self._send_msg(markdown_msg, reload_able=True, callback_path="update_profit",
query=update.callback_query) query=update.callback_query)

View File

@ -724,7 +724,9 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
'profit_closed_fiat': -83.19455985, 'profit_closed_ratio_mean': -0.0075, 'profit_closed_fiat': -83.19455985, 'profit_closed_ratio_mean': -0.0075,
'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,
}
), ),
( (
False, False,
@ -737,7 +739,9 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
'profit_closed_fiat': 9.124559849999999, 'profit_closed_ratio_mean': 0.0075, 'profit_closed_fiat': 9.124559849999999, 'profit_closed_ratio_mean': 0.0075,
'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,
}
), ),
( (
None, None,
@ -750,7 +754,9 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
'profit_closed_fiat': -67.02260985, 'profit_closed_ratio_mean': 0.0025, 'profit_closed_fiat': -67.02260985, 'profit_closed_ratio_mean': 0.0025,
'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,
}
) )
]) ])
def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected): def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected):
@ -803,6 +809,9 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
'closed_trade_count': 2, 'closed_trade_count': 2,
'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'],
'max_drawdown': ANY,
'max_drawdown_abs': ANY,
} }

View File

@ -704,11 +704,12 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
assert '∙ `6.253 USD`' in msg_mock.call_args_list[-1][0][0] assert '∙ `6.253 USD`' 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 '*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]
@pytest.mark.parametrize('is_short', [True, False]) @pytest.mark.parametrize('is_short', [True, False])
def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee, def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> None:
limit_buy_order, limit_sell_order, mocker, is_short) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',