From a99c53f1ec4f89cd5fa2fe6c57f958f9fa4fd1cf Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 5 Apr 2020 14:29:03 +0200 Subject: [PATCH 1/5] Add test showing that high is before low --- tests/data/test_btanalysis.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 463e5ae36..9f23ecf8f 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -191,3 +191,18 @@ def test_calculate_max_drawdown(testdatadir): assert low == Timestamp('2018-01-30 04:45:00', tz='UTC') with pytest.raises(ValueError, match='Trade dataframe empty.'): drawdown, h, low = calculate_max_drawdown(DataFrame()) + + +def test_calculate_max_drawdown2(): + values = [0.011580, 0.010048, 0.011340, 0.012161, 0.010416, 0.010009, 0.020024, + -0.024662, -0.022350, 0.020496, -0.029859, -0.030511, 0.010041, 0.010872, + -0.025782, 0.010400, 0.012374, 0.012467, 0.114741, 0.010303, 0.010088, + -0.033961, 0.010680, 0.010886, -0.029274, 0.011178, 0.010693, 0.010711] + + dates = [Arrow(2020, 1, 1).shift(days=i) for i in range(len(values))] + df = DataFrame(zip(values, dates), columns=['profit', 'open_time']) + drawdown, h, low = calculate_max_drawdown(df, date_col='open_time', value_col='profit') + assert isinstance(drawdown, float) + # High must be before low + assert h < low + assert drawdown == 0.091755 From e204170eb65055d487ab68e23150c658162d0007 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 5 Apr 2020 14:29:40 +0200 Subject: [PATCH 2/5] Fix max_drawdown bug finding low before high! --- freqtrade/data/btanalysis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 23a9f720c..1ff737b90 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -219,7 +219,7 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_time' max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value'] - high_date = profit_results.loc[max_drawdown_df['high_value'].idxmax(), date_col] - low_date = profit_results.loc[max_drawdown_df['drawdown'].idxmin(), date_col] - + idxmin = max_drawdown_df['drawdown'].idxmin() + high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col] + low_date = profit_results.loc[idxmin, date_col] return abs(min(max_drawdown_df['drawdown'])), high_date, low_date From 41d5c40f1099a0f35c9906a6554edb7ff1de9015 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 5 Apr 2020 14:43:01 +0200 Subject: [PATCH 3/5] Correctly test drawdown plot --- freqtrade/data/btanalysis.py | 2 ++ tests/data/test_btanalysis.py | 4 ++++ tests/test_plotting.py | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 1ff737b90..4505ea52a 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -220,6 +220,8 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_time' max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value'] idxmin = max_drawdown_df['drawdown'].idxmin() + if idxmin == 0: + raise ValueError("No losing trade, therefore no drawdown.") high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col] low_date = profit_results.loc[idxmin, date_col] return abs(min(max_drawdown_df['drawdown'])), high_date, low_date diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 9f23ecf8f..4eec20976 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -206,3 +206,7 @@ def test_calculate_max_drawdown2(): # High must be before low assert h < low assert drawdown == 0.091755 + + df = DataFrame(zip(values[:5], dates[:5]), columns=['profit', 'open_time']) + with pytest.raises(ValueError, match='No losing trade, therefore no drawdown.'): + calculate_max_drawdown(df, date_col='open_time', value_col='profit') diff --git a/tests/test_plotting.py b/tests/test_plotting.py index a5c965429..0258b94d1 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -266,7 +266,7 @@ def test_generate_profit_graph(testdatadir): filename = testdatadir / "backtest-result_test.json" trades = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") - pairs = ["TRX/BTC", "ADA/BTC"] + pairs = ["TRX/BTC", "XLM/BTC"] trades = trades[trades['close_time'] < pd.Timestamp('2018-01-12', tz='UTC')] data = history.load_data(datadir=testdatadir, @@ -292,7 +292,7 @@ def test_generate_profit_graph(testdatadir): profit = find_trace_in_fig_data(figure.data, "Profit") assert isinstance(profit, go.Scatter) - profit = find_trace_in_fig_data(figure.data, "Max drawdown 0.00%") + profit = find_trace_in_fig_data(figure.data, "Max drawdown 10.45%") assert isinstance(profit, go.Scatter) for pair in pairs: From 4ee0cbb5751c2bdb67724d379c6f2e3b1d12b339 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2020 10:40:02 +0200 Subject: [PATCH 4/5] Reset index to correctly gather index --- freqtrade/data/btanalysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 4505ea52a..a96e43dff 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -213,7 +213,7 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_time' """ if len(trades) == 0: raise ValueError("Trade dataframe empty.") - profit_results = trades.sort_values(date_col) + profit_results = trades.sort_values(date_col).reset_index() max_drawdown_df = pd.DataFrame() max_drawdown_df['cumulative'] = profit_results[value_col].cumsum() max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() From ddf37ef059655c93ec9109382815e683ae834dd2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Apr 2020 08:02:42 +0200 Subject: [PATCH 5/5] Add test to demonstrate that the dataframe is not changed --- freqtrade/data/btanalysis.py | 2 +- tests/data/test_btanalysis.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index a96e43dff..0c2fbff1e 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -213,7 +213,7 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_time' """ if len(trades) == 0: raise ValueError("Trade dataframe empty.") - profit_results = trades.sort_values(date_col).reset_index() + 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() diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 4eec20976..7b894cccc 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -201,7 +201,13 @@ def test_calculate_max_drawdown2(): dates = [Arrow(2020, 1, 1).shift(days=i) for i in range(len(values))] df = DataFrame(zip(values, dates), columns=['profit', 'open_time']) + # sort by profit and reset index + df = df.sort_values('profit').reset_index(drop=True) + df1 = df.copy() drawdown, h, low = calculate_max_drawdown(df, date_col='open_time', value_col='profit') + # Ensure df has not been altered. + assert df.equals(df1) + assert isinstance(drawdown, float) # High must be before low assert h < low