diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 560dd42f1..56c16f966 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -361,6 +361,36 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, return df +def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_col: str + ) -> pd.DataFrame: + max_drawdown_df = pd.DataFrame() + max_drawdown_df['cumulative'] = profit_results[value_col].cumsum() + max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() + max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value'] + max_drawdown_df['date'] = profit_results.loc[:, date_col] + return max_drawdown_df + + +def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date', + value_col: str = 'profit_ratio' + ): + """ + 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. + :raise: ValueError if trade-dataframe was found empty. + """ + if len(trades) == 0: + raise ValueError("Trade dataframe empty.") + profit_results = trades.sort_values(date_col).reset_index(drop=True) + max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col) + + return max_drawdown_df + + 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]: @@ -376,10 +406,7 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date' if len(trades) == 0: raise ValueError("Trade dataframe empty.") profit_results = trades.sort_values(date_col).reset_index(drop=True) - max_drawdown_df = pd.DataFrame() - max_drawdown_df['cumulative'] = profit_results[value_col].cumsum() - max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() - max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value'] + max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col) idxmin = max_drawdown_df['drawdown'].idxmin() if idxmin == 0: diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index b06dfce0a..47f1b8849 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -11,10 +11,10 @@ from freqtrade.constants import LAST_BT_RESULT_FN from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, BT_DATA_COLUMNS_MID, BT_DATA_COLUMNS_OLD, analyze_trade_parallelism, calculate_csum, calculate_market_change, calculate_max_drawdown, - combine_dataframes_with_mean, create_cum_profit, - extract_trades_of_period, get_latest_backtest_filename, - get_latest_hyperopt_file, load_backtest_data, load_trades, - load_trades_from_db) + calculate_underwater, combine_dataframes_with_mean, + create_cum_profit, extract_trades_of_period, + get_latest_backtest_filename, get_latest_hyperopt_file, + load_backtest_data, load_trades, load_trades_from_db) from freqtrade.data.history import load_data, load_pair_history from tests.conftest import create_mock_trades from tests.conftest_trades import MOCK_TRADE_COUNT @@ -291,9 +291,16 @@ def test_calculate_max_drawdown(testdatadir): assert isinstance(lval, float) assert hdate == Timestamp('2018-01-24 14:25:00', tz='UTC') assert lowdate == Timestamp('2018-01-30 04:45:00', tz='UTC') + + underwater = calculate_underwater(bt_data) + assert isinstance(underwater, DataFrame) + with pytest.raises(ValueError, match='Trade dataframe empty.'): drawdown, hdate, lowdate, hval, lval = calculate_max_drawdown(DataFrame()) + with pytest.raises(ValueError, match='Trade dataframe empty.'): + calculate_underwater(DataFrame()) + def test_calculate_csum(testdatadir): filename = testdatadir / "backtest-result_test.json"