From 4143ebbeae53eb27706cf15573f4f36b905ac8e0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 10:51:11 +0200 Subject: [PATCH] Add CAGR calculation to backtesting --- docs/backtesting.md | 2 ++ freqtrade/data/btanalysis.py | 11 +++++++++++ freqtrade/optimize/optimize_reports.py | 4 +++- tests/data/test_btanalysis.py | 27 +++++++++++++++++++------- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index f732068f1..a0a304400 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -299,6 +299,7 @@ A backtesting result will look like that: | Final balance | 0.01762792 BTC | | Absolute profit | 0.00762792 BTC | | Total profit % | 76.2% | +| CAGR % | 460.87% | | Trades per day | 3.575 | | Avg. stake amount | 0.001 BTC | | Total trade volume | 0.429 BTC | @@ -388,6 +389,7 @@ It contains some useful key metrics about performance of your strategy on backte | Final balance | 0.01762792 BTC | | Absolute profit | 0.00762792 BTC | | Total profit % | 76.2% | +| CAGR % | 460.87% | | Avg. stake amount | 0.001 BTC | | Total trade volume | 0.429 BTC | | | | diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 8abcc6747..206a6f5f3 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -553,3 +553,14 @@ def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[f csum_max = csum_df['sum'].max() + starting_balance return csum_min, csum_max + + +def calculate_cagr(days_passed: int, starting_balance: float, final_balance: float) -> float: + """ + Calculate CAGR + :param days_passed: Days passed between start and ending balance + :param starting_balance: Starting balance + :param final_balance: Final balance to calculate CAGR against + :return: CAGR + """ + return (final_balance / starting_balance) ** (1 / (days_passed / 365)) - 1 diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 0ceb3a411..e8bd035d1 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -9,7 +9,7 @@ from pandas import DataFrame, to_datetime from tabulate import tabulate from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT -from freqtrade.data.btanalysis import (calculate_csum, calculate_market_change, +from freqtrade.data.btanalysis import (calculate_cagr, calculate_csum, calculate_market_change, calculate_max_drawdown) from freqtrade.misc import (decimals_per_coin, file_dump_joblib, file_dump_json, get_backtest_metadata_filename, round_coin_value) @@ -446,6 +446,7 @@ def generate_strategy_stats(pairlist: List[str], 'profit_total_abs': results['profit_abs'].sum(), 'profit_total_long_abs': results.loc[~results['is_short'], 'profit_abs'].sum(), 'profit_total_short_abs': results.loc[results['is_short'], 'profit_abs'].sum(), + 'cagr': calculate_cagr(backtest_days, start_balance, content['final_balance']), 'backtest_start': min_date.strftime(DATETIME_PRINT_FORMAT), 'backtest_start_ts': int(min_date.timestamp() * 1000), 'backtest_end': max_date.strftime(DATETIME_PRINT_FORMAT), @@ -746,6 +747,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: ('Absolute profit ', round_coin_value(strat_results['profit_total_abs'], strat_results['stake_currency'])), ('Total profit %', f"{strat_results['profit_total']:.2%}"), + ('CAGR %', f"{strat_results['cagr']:.2%}"), ('Trades per day', strat_results['trades_per_day']), ('Avg. daily profit %', f"{(strat_results['profit_total'] / strat_results['backtest_days']):.2%}"), diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 2b53e4900..eaf703b2d 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -8,13 +8,13 @@ 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, analyze_trade_parallelism, calculate_csum, - calculate_market_change, calculate_max_drawdown, - 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_backtest_metadata, load_trades, - load_trades_from_db) +from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelism, calculate_cagr, + calculate_csum, calculate_market_change, + calculate_max_drawdown, 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_backtest_metadata, load_trades, load_trades_from_db) from freqtrade.data.history import load_data, load_pair_history from freqtrade.exceptions import OperationalException from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades @@ -336,6 +336,19 @@ def test_calculate_csum(testdatadir): csum_min, csum_max = calculate_csum(DataFrame()) +@pytest.mark.parametrize('start,end,days, expected', [ + (64900, 176000, 3 * 365, 0.3945), + (64900, 176000, 365, 1.7119), + (1000, 1000, 365, 0.0), + (1000, 1500, 365, 0.5), + (1000, 1500, 100, 3.3927), # sub year + (0.01000000, 0.01762792, 120, 4.6087), # sub year BTC values +]) +def test_calculate_cagr(start, end, days, expected): + + assert round(calculate_cagr(days, start, end), 4) == expected + + 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,