From 5c263c7ffd546ec50fc96dbab49ba502bdf0ff33 Mon Sep 17 00:00:00 2001 From: Florian Reitmeir Date: Thu, 24 Dec 2020 22:17:24 +0100 Subject: [PATCH 1/2] add backtesting results abs profit min/abs profit max, to get a better view if a strategy has a enough money to succeed --- freqtrade/data/btanalysis.py | 18 ++++++++++++++++++ freqtrade/optimize/optimize_reports.py | 18 +++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 828fb78f3..8e851a8e8 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -383,3 +383,21 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date' 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 + + +def calculate_csum(trades: pd.DataFrame) -> Tuple[float, float]: + """ + Calculate min/max cumsum of trades, to show if the wallet/stake amount ratio is sane + :param trades: DataFrame containing trades (requires columns close_date and profit_percent) + :return: Tuple (float, float) with cumsum of profit_abs + :raise: ValueError if trade-dataframe was found empty. + """ + if len(trades) == 0: + raise ValueError("Trade dataframe empty.") + + csum_df = pd.DataFrame() + csum_df['sum'] = trades['profit_abs'].cumsum() + csum_min = csum_df['sum'].min() + csum_max = csum_df['sum'].max() + + return csum_min, csum_max diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 118253e86..88b2028ba 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -9,7 +9,8 @@ from pandas import DataFrame from tabulate import tabulate from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN -from freqtrade.data.btanalysis import calculate_market_change, calculate_max_drawdown +from freqtrade.data.btanalysis import (calculate_csum, calculate_market_change, + calculate_max_drawdown) from freqtrade.misc import decimals_per_coin, file_dump_json, round_coin_value @@ -324,6 +325,13 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame], 'drawdown_end': drawdown_end, 'drawdown_end_ts': drawdown_end.timestamp() * 1000, }) + + csum_min, csum_max = calculate_csum(results) + strat_stats.update({ + 'csum_min': csum_min, + 'csum_max': csum_max + }) + except ValueError: strat_stats.update({ 'max_drawdown': 0.0, @@ -331,6 +339,8 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame], 'drawdown_start_ts': 0, 'drawdown_end': datetime(1970, 1, 1, tzinfo=timezone.utc), 'drawdown_end_ts': 0, + 'csum_min': 0, + 'csum_max': 0 }) strategy_results = generate_strategy_metrics(all_results=all_results) @@ -439,6 +449,12 @@ def text_table_add_metrics(strat_results: Dict) -> str: ('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"), ('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"), ('', ''), # Empty line to improve readability + + ('Abs Profit Min', round_coin_value(strat_results['csum_min'], + strat_results['stake_currency'])), + ('Abs Profit Max', round_coin_value(strat_results['csum_max'], + strat_results['stake_currency'])), + ('Max Drawdown', f"{round(strat_results['max_drawdown'] * 100, 2)}%"), ('Drawdown Start', strat_results['drawdown_start'].strftime(DATETIME_PRINT_FORMAT)), ('Drawdown End', strat_results['drawdown_end'].strftime(DATETIME_PRINT_FORMAT)), From 1a166f639d79d718984c9d6df19ce70d078cfeaa Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 14 Feb 2021 19:44:13 +0100 Subject: [PATCH 2/2] Add test for calcuate_csum --- tests/data/test_btanalysis.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 96ac6f63c..3c4687745 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -8,11 +8,12 @@ from pandas import DataFrame, DateOffset, Timestamp, to_datetime from freqtrade.configuration import TimeRange 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_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) + 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) 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 @@ -284,6 +285,20 @@ def test_calculate_max_drawdown(testdatadir): drawdown, h, low = calculate_max_drawdown(DataFrame()) +def test_calculate_csum(testdatadir): + filename = testdatadir / "backtest-result_test.json" + bt_data = load_backtest_data(filename) + csum_min, csum_max = calculate_csum(bt_data) + + assert isinstance(csum_min, float) + assert isinstance(csum_max, float) + assert csum_min < 0.01 + assert csum_max > 0.02 + + with pytest.raises(ValueError, match='Trade dataframe empty.'): + csum_min, csum_max = calculate_csum(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,