From 7a2b50ce8be62fc9c071a2300b26cf7f68f2cc2c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 4 Jan 2022 15:57:58 +0100 Subject: [PATCH] Update drawdown calculation to account drawdown --- docs/backtesting.md | 7 ++++--- freqtrade/data/btanalysis.py | 24 ++++++++++++++++++------ freqtrade/optimize/optimize_reports.py | 13 ++++++------- freqtrade/plot/plotting.py | 2 +- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index ad62c84b3..64480acd9 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -312,7 +312,7 @@ A backtesting result will look like that: | | | | Min balance | 0.00945123 BTC | | Max balance | 0.01846651 BTC | -| Drawdown | 50.63% | +| Drawdown (Account) | 13.33% | | Drawdown | 0.0015 BTC | | Drawdown high | 0.0013 BTC | | Drawdown low | -0.0002 BTC | @@ -399,7 +399,7 @@ It contains some useful key metrics about performance of your strategy on backte | | | | Min balance | 0.00945123 BTC | | Max balance | 0.01846651 BTC | -| Drawdown | 50.63% | +| Drawdown (Account) | 13.33% | | Drawdown | 0.0015 BTC | | Drawdown high | 0.0013 BTC | | Drawdown low | -0.0002 BTC | @@ -426,7 +426,8 @@ It contains some useful key metrics about performance of your strategy on backte - `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades. - `Rejected Buy signals`: Buy signals that could not be acted upon due to max_open_trades being reached. - `Min balance` / `Max balance`: Lowest and Highest Wallet balance during the backtest period. -- `Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced). +- `Drawdown (Account)`: Maximum Account Drawdown experienced. Calculated as $(Absolute Drawdown) / (DrawdownHigh + startingBalance)$. +- `Drawdown`: Maximum, absolute drawdown experienced. Difference between Drawdown High and Low. - `Drawdown high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost. - `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command). - `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column. diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 56c16f966..2f991d247 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -392,15 +392,17 @@ def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date', def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date', - value_col: str = 'profit_ratio' - ) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float]: + value_col: str = 'profit_abs', starting_balance: float = 0 + ) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float, float]: """ Calculate max drawdown and the corresponding close dates :param trades: DataFrame containing trades (requires columns close_date and profit_ratio) :param date_col: Column in DataFrame to use for dates (defaults to 'close_date') - :param value_col: Column in DataFrame to use for values (defaults to 'profit_ratio') - :return: Tuple (float, highdate, lowdate, highvalue, lowvalue) with absolute max drawdown, - high and low time and high and low value. + :param value_col: Column in DataFrame to use for values (defaults to 'profit_abs') + :param starting_balance: Portfolio starting balance - properly calculate relative drawdown. + :return: Tuple (float, highdate, lowdate, highvalue, lowvalue, relative_drawdown) + with absolute max drawdown, high and low time and high and low value, + and the relative account drawdown :raise: ValueError if trade-dataframe was found empty. """ if len(trades) == 0: @@ -416,7 +418,17 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date' high_val = max_drawdown_df.loc[max_drawdown_df.iloc[:idxmin] ['high_value'].idxmax(), 'cumulative'] low_val = max_drawdown_df.loc[idxmin, 'cumulative'] - return abs(min(max_drawdown_df['drawdown'])), high_date, low_date, high_val, low_val + + max_drawdown_rel = (high_val - low_val) / (high_val + starting_balance) + + return ( + abs(min(max_drawdown_df['drawdown'])), + high_date, + low_date, + high_val, + low_val, + max_drawdown_rel + ) def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[float, float]: diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index dcd6b4e1f..c083f969d 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -462,12 +462,11 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], } try: - max_drawdown, _, _, _, _ = 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_account': max_drawdown, 'max_drawdown_abs': drawdown_abs, 'drawdown_start': drawdown_start.strftime(DATETIME_PRINT_FORMAT), 'drawdown_start_ts': drawdown_start.timestamp() * 1000, @@ -486,7 +485,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, @@ -716,7 +715,7 @@ 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%}"), + ('Drawdown (Account)', f"{strat_results['max_drawdown_account']:.2%}"), ('Drawdown', round_coin_value(strat_results['max_drawdown_abs'], strat_results['stake_currency'])), ('Drawdown high', round_coin_value(strat_results['max_drawdown_high'], diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index c0888808f..3769d4c5a 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -161,7 +161,7 @@ def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame, Add scatter points indicating max drawdown """ try: - max_drawdown, highdate, lowdate, _, _ = calculate_max_drawdown(trades) + _, highdate, lowdate, _, _, max_drawdown = calculate_max_drawdown(trades) drawdown = go.Scatter( x=[highdate, lowdate],