Merge pull request #6165 from freqtrade/drawdown_fixes
Improved drawdown calculation
This commit is contained in:
@@ -47,10 +47,9 @@ class CalmarHyperOptLoss(IHyperOptLoss):
|
||||
|
||||
# calculate max drawdown
|
||||
try:
|
||||
_, _, _, high_val, low_val = calculate_max_drawdown(
|
||||
_, _, _, _, _, max_drawdown = calculate_max_drawdown(
|
||||
results, value_col="profit_abs"
|
||||
)
|
||||
max_drawdown = (high_val - low_val) / high_val
|
||||
except ValueError:
|
||||
max_drawdown = 0
|
||||
|
||||
|
@@ -308,8 +308,7 @@ class HyperoptTools():
|
||||
|
||||
if not has_drawdown:
|
||||
# Ensure compatibility with older versions of hyperopt results
|
||||
trials['results_metrics.max_drawdown_abs'] = None
|
||||
trials['results_metrics.max_drawdown'] = None
|
||||
trials['results_metrics.max_drawdown_account'] = None
|
||||
|
||||
# New mode, using backtest result for metrics
|
||||
trials['results_metrics.winsdrawslosses'] = trials.apply(
|
||||
@@ -320,12 +319,15 @@ class HyperoptTools():
|
||||
'results_metrics.winsdrawslosses',
|
||||
'results_metrics.profit_mean', 'results_metrics.profit_total_abs',
|
||||
'results_metrics.profit_total', 'results_metrics.holding_avg',
|
||||
'results_metrics.max_drawdown', 'results_metrics.max_drawdown_abs',
|
||||
'results_metrics.max_drawdown',
|
||||
'results_metrics.max_drawdown_account', 'results_metrics.max_drawdown_abs',
|
||||
'loss', 'is_initial_point', 'is_best']]
|
||||
|
||||
trials.columns = ['Best', 'Epoch', 'Trades', ' Win Draw Loss', 'Avg profit',
|
||||
'Total profit', 'Profit', 'Avg duration', 'Max Drawdown',
|
||||
'max_drawdown_abs', 'Objective', 'is_initial_point', 'is_best']
|
||||
trials.columns = [
|
||||
'Best', 'Epoch', 'Trades', ' Win Draw Loss', 'Avg profit',
|
||||
'Total profit', 'Profit', 'Avg duration', 'max_drawdown', 'max_drawdown_account',
|
||||
'max_drawdown_abs', 'Objective', 'is_initial_point', 'is_best'
|
||||
]
|
||||
|
||||
return trials
|
||||
|
||||
@@ -341,9 +343,9 @@ class HyperoptTools():
|
||||
tabulate.PRESERVE_WHITESPACE = True
|
||||
trials = json_normalize(results, max_level=1)
|
||||
|
||||
has_drawdown = 'results_metrics.max_drawdown_abs' in trials.columns
|
||||
has_account_drawdown = 'results_metrics.max_drawdown_account' in trials.columns
|
||||
|
||||
trials = HyperoptTools.prepare_trials_columns(trials, has_drawdown)
|
||||
trials = HyperoptTools.prepare_trials_columns(trials, has_account_drawdown)
|
||||
|
||||
trials['is_profit'] = False
|
||||
trials.loc[trials['is_initial_point'], 'Best'] = '* '
|
||||
@@ -368,19 +370,20 @@ class HyperoptTools():
|
||||
|
||||
stake_currency = config['stake_currency']
|
||||
|
||||
if has_drawdown:
|
||||
trials['Max Drawdown'] = trials.apply(
|
||||
lambda x: '{} {}'.format(
|
||||
round_coin_value(x['max_drawdown_abs'], stake_currency),
|
||||
f"({x['Max Drawdown']:,.2%})".rjust(10, ' ')
|
||||
).rjust(25 + len(stake_currency))
|
||||
if x['Max Drawdown'] != 0.0 else '--'.rjust(25 + len(stake_currency)),
|
||||
axis=1
|
||||
)
|
||||
else:
|
||||
trials = trials.drop(columns=['Max Drawdown'])
|
||||
trials[f"Max Drawdown{' (Acct)' if has_account_drawdown else ''}"] = trials.apply(
|
||||
lambda x: "{} {}".format(
|
||||
round_coin_value(x['max_drawdown_abs'], stake_currency),
|
||||
(f"({x['max_drawdown_account']:,.2%})"
|
||||
if has_account_drawdown
|
||||
else f"({x['max_drawdown']:,.2%})"
|
||||
).rjust(10, ' ')
|
||||
).rjust(25 + len(stake_currency))
|
||||
if x['max_drawdown'] != 0.0 or x['max_drawdown_account'] != 0.0
|
||||
else '--'.rjust(25 + len(stake_currency)),
|
||||
axis=1
|
||||
)
|
||||
|
||||
trials = trials.drop(columns=['max_drawdown_abs'])
|
||||
trials = trials.drop(columns=['max_drawdown_abs', 'max_drawdown', 'max_drawdown_account'])
|
||||
|
||||
trials['Profit'] = trials.apply(
|
||||
lambda x: '{} {}'.format(
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Union
|
||||
@@ -194,29 +195,21 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List
|
||||
return tabular_data
|
||||
|
||||
|
||||
def generate_strategy_comparison(all_results: Dict) -> List[Dict]:
|
||||
def generate_strategy_comparison(bt_stats: Dict) -> List[Dict]:
|
||||
"""
|
||||
Generate summary per strategy
|
||||
:param all_results: Dict of <Strategyname: DataFrame> containing results for all strategies
|
||||
:param bt_stats: Dict of <Strategyname: DataFrame> containing results for all strategies
|
||||
:return: List of Dicts containing the metrics per Strategy
|
||||
"""
|
||||
|
||||
tabular_data = []
|
||||
for strategy, results in all_results.items():
|
||||
tabular_data.append(_generate_result_line(
|
||||
results['results'], results['config']['dry_run_wallet'], strategy)
|
||||
)
|
||||
try:
|
||||
max_drawdown_per, _, _, _, _ = calculate_max_drawdown(results['results'],
|
||||
value_col='profit_ratio')
|
||||
max_drawdown_abs, _, _, _, _ = calculate_max_drawdown(results['results'],
|
||||
value_col='profit_abs')
|
||||
except ValueError:
|
||||
max_drawdown_per = 0
|
||||
max_drawdown_abs = 0
|
||||
tabular_data[-1]['max_drawdown_per'] = round(max_drawdown_per * 100, 2)
|
||||
tabular_data[-1]['max_drawdown_abs'] = \
|
||||
round_coin_value(max_drawdown_abs, results['config']['stake_currency'], False)
|
||||
for strategy, result in bt_stats.items():
|
||||
tabular_data.append(deepcopy(result['results_per_pair'][-1]))
|
||||
# Update "key" to strategy (results_per_pair has it as "Total").
|
||||
tabular_data[-1]['key'] = strategy
|
||||
tabular_data[-1]['max_drawdown_account'] = result['max_drawdown_account']
|
||||
tabular_data[-1]['max_drawdown_abs'] = round_coin_value(
|
||||
result['max_drawdown_abs'], result['stake_currency'], False)
|
||||
return tabular_data
|
||||
|
||||
|
||||
@@ -462,12 +455,14 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
|
||||
}
|
||||
|
||||
try:
|
||||
max_drawdown, _, _, _, _ = calculate_max_drawdown(
|
||||
max_drawdown_legacy, _, _, _, _, _ = calculate_max_drawdown(
|
||||
results, value_col='profit_ratio')
|
||||
drawdown_abs, drawdown_start, drawdown_end, high_val, low_val = calculate_max_drawdown(
|
||||
results, value_col='profit_abs')
|
||||
(drawdown_abs, drawdown_start, drawdown_end, high_val, low_val,
|
||||
max_drawdown) = calculate_max_drawdown(
|
||||
results, value_col='profit_abs', starting_balance=starting_balance)
|
||||
strat_stats.update({
|
||||
'max_drawdown': max_drawdown,
|
||||
'max_drawdown': max_drawdown_legacy, # Deprecated - do not use
|
||||
'max_drawdown_account': max_drawdown,
|
||||
'max_drawdown_abs': drawdown_abs,
|
||||
'drawdown_start': drawdown_start.strftime(DATETIME_PRINT_FORMAT),
|
||||
'drawdown_start_ts': drawdown_start.timestamp() * 1000,
|
||||
@@ -487,6 +482,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
|
||||
except ValueError:
|
||||
strat_stats.update({
|
||||
'max_drawdown': 0.0,
|
||||
'max_drawdown_account': 0.0,
|
||||
'max_drawdown_abs': 0.0,
|
||||
'max_drawdown_low': 0.0,
|
||||
'max_drawdown_high': 0.0,
|
||||
@@ -521,7 +517,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
|
||||
min_date, max_date, market_change=market_change)
|
||||
result['strategy'][strategy] = strat_stats
|
||||
|
||||
strategy_results = generate_strategy_comparison(all_results=all_results)
|
||||
strategy_results = generate_strategy_comparison(bt_stats=result['strategy'])
|
||||
|
||||
result['strategy_comparison'] = strategy_results
|
||||
|
||||
@@ -646,7 +642,7 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str:
|
||||
headers.append('Drawdown')
|
||||
|
||||
# Align drawdown string on the center two space separator.
|
||||
drawdown = [f'{t["max_drawdown_per"]:.2f}' for t in strategy_results]
|
||||
drawdown = [f'{t["max_drawdown_account"] * 100:.2f}' for t in strategy_results]
|
||||
dd_pad_abs = max([len(t['max_drawdown_abs']) for t in strategy_results])
|
||||
dd_pad_per = max([len(dd) for dd in drawdown])
|
||||
drawdown = [f'{t["max_drawdown_abs"]:>{dd_pad_abs}} {stake_currency} {dd:>{dd_pad_per}}%'
|
||||
@@ -716,7 +712,10 @@ def text_table_add_metrics(strat_results: Dict) -> str:
|
||||
('Max balance', round_coin_value(strat_results['csum_max'],
|
||||
strat_results['stake_currency'])),
|
||||
|
||||
('Drawdown', f"{strat_results['max_drawdown']:.2%}"),
|
||||
# Compatibility to show old hyperopt results
|
||||
('Drawdown (Account)', f"{strat_results['max_drawdown_account']:.2%}")
|
||||
if 'max_drawdown_account' in strat_results else (
|
||||
'Drawdown', f"{strat_results['max_drawdown']:.2%}"),
|
||||
('Drawdown', round_coin_value(strat_results['max_drawdown_abs'],
|
||||
strat_results['stake_currency'])),
|
||||
('Drawdown high', round_coin_value(strat_results['max_drawdown_high'],
|
||||
|
Reference in New Issue
Block a user