From edcfa940937d0141f74b1bdd4f8161f76746fb83 Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Sat, 15 May 2021 11:26:22 +0300 Subject: [PATCH 1/8] Include zero duration trades in backtesting report. --- docs/backtesting.md | 1 + freqtrade/optimize/optimize_reports.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index ee9926f32..8b3aa8cc1 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -413,6 +413,7 @@ It contains some useful key metrics about performance of your strategy on backte - `Best day` / `Worst day`: Best and worst day based on daily profit. - `Days win/draw/lose`: Winning / Losing days (draws are usually days without closed trade). - `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades. +- `Zero Duration Trades`: A number of trades that completed within same candle as they opened and had `trailing_stop_loss` sell reason. A significant amount of such trades may indicate that strategy is exploiting trailing stoploss behavior in backtesting and produces unrealistic results. - `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 high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost. diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 2be3c3e62..b509f216f 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -208,6 +208,8 @@ def generate_trading_stats(results: DataFrame) -> Dict[str, Any]: winning_trades = results.loc[results['profit_ratio'] > 0] draw_trades = results.loc[results['profit_ratio'] == 0] losing_trades = results.loc[results['profit_ratio'] < 0] + zero_duration_trades = len(results.loc[(results['trade_duration'] == 0) & + (results['sell_reason'] == 'trailing_stop_loss')]) return { 'wins': len(winning_trades), @@ -219,6 +221,7 @@ def generate_trading_stats(results: DataFrame) -> Dict[str, Any]: if not winning_trades.empty else timedelta()), 'loser_holding_avg': (timedelta(minutes=round(losing_trades['trade_duration'].mean())) if not losing_trades.empty else timedelta()), + 'zero_duration_trades': zero_duration_trades, } @@ -496,6 +499,8 @@ def text_table_add_metrics(strat_results: Dict) -> str: if len(strat_results['trades']) > 0: best_trade = max(strat_results['trades'], key=lambda x: x['profit_ratio']) worst_trade = min(strat_results['trades'], key=lambda x: x['profit_ratio']) + zero_duration_trades_percent =\ + 100.0 / strat_results['total_trades'] * strat_results['zero_duration_trades'] metrics = [ ('Backtesting from', strat_results['backtest_start']), ('Backtesting to', strat_results['backtest_end']), @@ -508,7 +513,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: strat_results['stake_currency'])), ('Absolute profit ', round_coin_value(strat_results['profit_total_abs'], strat_results['stake_currency'])), - ('Total profit %', f"{round(strat_results['profit_total'] * 100, 2)}%"), + ('Total profit %', f"{round(strat_results['profit_total'] * 100, 2):}%"), ('Trades per day', strat_results['trades_per_day']), ('Avg. stake amount', round_coin_value(strat_results['avg_stake_amount'], strat_results['stake_currency'])), @@ -532,6 +537,8 @@ def text_table_add_metrics(strat_results: Dict) -> str: f"{strat_results['draw_days']} / {strat_results['losing_days']}"), ('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"), ('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"), + ('Zero Duration Trades', f"{zero_duration_trades_percent:.1f}% " + f"({strat_results['zero_duration_trades']})"), ('', ''), # Empty line to improve readability ('Min balance', round_coin_value(strat_results['csum_min'], From e1dc1357ce37243f4f8ff214480d339b7249154c Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Sat, 15 May 2021 12:00:01 +0300 Subject: [PATCH 2/8] Add drawdown column to strategy summary table. --- docs/backtesting.md | 10 +++++----- freqtrade/optimize/optimize_reports.py | 18 ++++++++++++++++- tests/optimize/test_optimize_reports.py | 26 ++++++++++++++++--------- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 8b3aa8cc1..5ee0b8a49 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -473,11 +473,11 @@ There will be an additional table comparing win/losses of the different strategi Detailed output for all strategies one after the other will be available, so make sure to scroll up to see the details per strategy. ``` -=========================================================== STRATEGY SUMMARY =========================================================== -| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | -|:------------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:| -| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | -| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | +=========================================================== STRATEGY SUMMARY ========================================================================= +| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown % | +|:------------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|-----------:| +| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | 45.2 | +| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 | ``` ## Next step diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index b509f216f..d8ef92e7c 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -164,6 +164,17 @@ def generate_strategy_comparison(all_results: Dict) -> List[Dict]: tabular_data.append(_generate_result_line( results['results'], results['config']['dry_run_wallet'], strategy) ) + try: + max_drawdown_per, _, _, _, _ = calculate_max_drawdown(results['results'], + value_col='profit_ratio') + max_drawdown_abs, _, _, _, _ = calculate_max_drawdown(results['results'], + value_col='profit_abs') + except ValueError: + max_drawdown_per = 0 + max_drawdown_abs = 0 + tabular_data[-1]['max_drawdown_per'] = round(max_drawdown_per * 100, 2) + tabular_data[-1]['max_drawdown_abs'] = \ + round_coin_value(max_drawdown_abs, results['config']['stake_currency'], False) return tabular_data @@ -485,10 +496,15 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str: """ floatfmt = _get_line_floatfmt(stake_currency) headers = _get_line_header('Strategy', stake_currency) + # _get_line_header() is also used for per-pair summary. Per-pair drawdown is mostly useless + # therefore we slip this column in only for strategy summary here. + headers.append(f'Drawdown {stake_currency}') + headers.append('Drawdown %') output = [[ t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], - t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses'] + t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses'], + t['max_drawdown_abs'], t['max_drawdown_per'] ] for t in strategy_results] # Ignore type as floatfmt does allow tuples but mypy does not know that return tabulate(output, headers=headers, diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 1afa5c59c..e87cec6fa 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -1,3 +1,4 @@ +import datetime import re from datetime import timedelta from pathlib import Path @@ -325,9 +326,12 @@ def test_text_table_strategy(default_conf): default_conf['max_open_trades'] = 2 default_conf['dry_run_wallet'] = 3 results = {} + date = datetime.datetime(year=2020, month=1, day=1, hour=12, minute=30) + delta = datetime.timedelta(days=1) results['TestStrategy1'] = {'results': pd.DataFrame( { 'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'], + 'close_date': [date, date + delta, date + delta * 2], 'profit_ratio': [0.1, 0.2, 0.3], 'profit_abs': [0.2, 0.4, 0.5], 'trade_duration': [10, 30, 10], @@ -340,6 +344,7 @@ def test_text_table_strategy(default_conf): results['TestStrategy2'] = {'results': pd.DataFrame( { 'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'], + 'close_date': [date, date + delta, date + delta * 2], 'profit_ratio': [0.4, 0.2, 0.3], 'profit_abs': [0.4, 0.4, 0.5], 'trade_duration': [15, 30, 15], @@ -351,18 +356,21 @@ def test_text_table_strategy(default_conf): ), 'config': default_conf} result_str = ( - '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot' - ' Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |\n' - '|---------------+--------+----------------+----------------+------------------+' - '----------------+----------------+--------+---------+----------|\n' - '| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |' - ' 36.67 | 0:17:00 | 3 | 0 | 0 |\n' - '| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 |' - ' 43.33 | 0:20:00 | 3 | 0 | 0 |' + '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC ' + '| Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown BTC ' + '| Drawdown % |\n' + '|---------------+--------+----------------+----------------+------------------' + '+----------------+----------------+--------+---------+----------+----------------' + '+--------------|\n' + '| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 ' + '| 36.67 | 0:17:00 | 3 | 0 | 0 | 0 ' + '| 0 |\n' + '| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 ' + '| 43.33 | 0:20:00 | 3 | 0 | 0 | 0 ' + '| 0 |' ) strategy_results = generate_strategy_comparison(all_results=results) - assert text_table_strategy(strategy_results, 'BTC') == result_str From debd98ad9a6278ca026bdb7fb32f287687f994ee Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Fri, 21 May 2021 11:29:22 +0300 Subject: [PATCH 3/8] Make results table more compact by merging win/draw/loss columns and drawdown abs/% into single columns. --- freqtrade/optimize/optimize_reports.py | 34 +++++++++++++++++--------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index d8ef92e7c..68f315926 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -43,7 +43,7 @@ def _get_line_floatfmt(stake_currency: str) -> List[str]: Generate floatformat (goes in line with _generate_result_line()) """ return ['s', 'd', '.2f', '.2f', f'.{decimals_per_coin(stake_currency)}f', - '.2f', 'd', 'd', 'd', 'd'] + '.2f', 'd', 's', 's'] def _get_line_header(first_column: str, stake_currency: str) -> List[str]: @@ -52,7 +52,11 @@ def _get_line_header(first_column: str, stake_currency: str) -> List[str]: """ return [first_column, 'Buys', 'Avg Profit %', 'Cum Profit %', f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration', - 'Wins', 'Draws', 'Losses'] + 'Win Draw Loss'] + + +def _generate_wins_draws_losses(wins, draws, losses): + return f'{wins:>4} {draws:>4} {losses:>4}' def _generate_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict: @@ -451,7 +455,8 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st floatfmt = _get_line_floatfmt(stake_currency) output = [[ t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], - t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses'] + t['profit_total_pct'], t['duration_avg'], + _generate_wins_draws_losses(t['wins'], t['draws'], t['losses']) ] for t in pair_results] # Ignore type as floatfmt does allow tuples but mypy does not know that return tabulate(output, headers=headers, @@ -468,9 +473,7 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren headers = [ 'Sell Reason', 'Sells', - 'Wins', - 'Draws', - 'Losses', + 'Win Draws Loss', 'Avg Profit %', 'Cum Profit %', f'Tot Profit {stake_currency}', @@ -478,7 +481,8 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren ] output = [[ - t['sell_reason'], t['trades'], t['wins'], t['draws'], t['losses'], + t['sell_reason'], t['trades'], + _generate_wins_draws_losses(t['wins'], t['draws'], t['losses']), t['profit_mean_pct'], t['profit_sum_pct'], round_coin_value(t['profit_total_abs'], stake_currency, False), t['profit_total_pct'], @@ -498,14 +502,20 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str: headers = _get_line_header('Strategy', stake_currency) # _get_line_header() is also used for per-pair summary. Per-pair drawdown is mostly useless # therefore we slip this column in only for strategy summary here. - headers.append(f'Drawdown {stake_currency}') - headers.append('Drawdown %') + headers.append(f'Drawdown') + + # Align drawdown string on the center two space separator. + drawdown = [f'{t["max_drawdown_per"]:.2f}' for t in strategy_results] + dd_pad_abs = max([len(t['max_drawdown_abs']) for t in strategy_results]) + dd_pad_per = max([len(dd) for dd in drawdown]) + drawdown = [f'{t["max_drawdown_abs"]:>{dd_pad_abs}} {stake_currency} {dd:>{dd_pad_per}}%' + for t, dd in zip(strategy_results, drawdown)] output = [[ t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], - t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses'], - t['max_drawdown_abs'], t['max_drawdown_per'] - ] for t in strategy_results] + t['profit_total_pct'], t['duration_avg'], + _generate_wins_draws_losses(t['wins'], t['draws'], t['losses']), drawdown] + for t, drawdown in zip(strategy_results, drawdown)] # Ignore type as floatfmt does allow tuples but mypy does not know that return tabulate(output, headers=headers, floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") From 981b2df7caec96565ba66d2abb7201326fcfaa96 Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Fri, 21 May 2021 12:00:24 +0300 Subject: [PATCH 4/8] Include win:loss ratio in results tables. --- freqtrade/optimize/optimize_reports.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 68f315926..0474ec1bf 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -52,11 +52,17 @@ def _get_line_header(first_column: str, stake_currency: str) -> List[str]: """ return [first_column, 'Buys', 'Avg Profit %', 'Cum Profit %', f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration', - 'Win Draw Loss'] + 'Win Draw Loss Win%'] def _generate_wins_draws_losses(wins, draws, losses): - return f'{wins:>4} {draws:>4} {losses:>4}' + if wins > 0 and losses == 0: + wl_ratio = '100' + elif wins == 0: + wl_ratio = '0' + else: + wl_ratio = f'{100.0 / (wins + draws + losses) * wins:.1f}' if losses > 0 else '100' + return f'{wins:>4} {draws:>4} {losses:>4} {wl_ratio:>4}' def _generate_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict: @@ -473,7 +479,7 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren headers = [ 'Sell Reason', 'Sells', - 'Win Draws Loss', + 'Win Draws Loss Win%', 'Avg Profit %', 'Cum Profit %', f'Tot Profit {stake_currency}', From 25cc4eae96b0a6fd18047ce98da9a8aef6073b76 Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Fri, 21 May 2021 12:31:16 +0300 Subject: [PATCH 5/8] Fix tests that broke after table formatting changed. --- freqtrade/optimize/optimize_reports.py | 2 +- tests/optimize/test_optimize_reports.py | 52 ++++++++++++------------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 0474ec1bf..bda531d0d 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -508,7 +508,7 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str: headers = _get_line_header('Strategy', stake_currency) # _get_line_header() is also used for per-pair summary. Per-pair drawdown is mostly useless # therefore we slip this column in only for strategy summary here. - headers.append(f'Drawdown') + headers.append('Drawdown') # Align drawdown string on the center two space separator. drawdown = [f'{t["max_drawdown_per"]:.2f}' for t in strategy_results] diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index e87cec6fa..e0ff2f219 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -39,14 +39,14 @@ def test_text_table_bt_results(): ) result_str = ( - '| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |' - ' Tot Profit % | Avg Duration | Wins | Draws | Losses |\n' - '|---------+--------+----------------+----------------+------------------+' - '----------------+----------------+--------+---------+----------|\n' - '| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 |' - ' 15.00 | 0:20:00 | 2 | 0 | 0 |\n' - '| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 |' - ' 15.00 | 0:20:00 | 2 | 0 | 0 |' + '| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |' + ' Avg Duration | Win Draw Loss Win% |\n' + '|---------+--------+----------------+----------------+------------------+----------------+' + '----------------+-------------------------|\n' + '| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 | 15.00 |' + ' 0:20:00 | 2 0 0 100 |\n' + '| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 | 15.00 |' + ' 0:20:00 | 2 0 0 100 |' ) pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC', @@ -271,14 +271,14 @@ def test_text_table_sell_reason(): ) result_str = ( - '| Sell Reason | Sells | Wins | Draws | Losses |' - ' Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |\n' - '|---------------+---------+--------+---------+----------+' - '----------------+----------------+------------------+----------------|\n' - '| roi | 2 | 2 | 0 | 0 |' - ' 15 | 30 | 0.6 | 15 |\n' - '| stop_loss | 1 | 0 | 0 | 1 |' - ' -10 | -10 | -0.2 | -5 |' + '| Sell Reason | Sells | Win Draws Loss Win% | Avg Profit % | Cum Profit % |' + ' Tot Profit BTC | Tot Profit % |\n' + '|---------------+---------+--------------------------+----------------+----------------+' + '------------------+----------------|\n' + '| roi | 2 | 2 0 0 100 | 15 | 30 |' + ' 0.6 | 15 |\n' + '| stop_loss | 1 | 0 0 1 0 | -10 | -10 |' + ' -0.2 | -5 |' ) sell_reason_stats = generate_sell_reason_stats(max_open_trades=2, @@ -356,18 +356,14 @@ def test_text_table_strategy(default_conf): ), 'config': default_conf} result_str = ( - '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC ' - '| Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown BTC ' - '| Drawdown % |\n' - '|---------------+--------+----------------+----------------+------------------' - '+----------------+----------------+--------+---------+----------+----------------' - '+--------------|\n' - '| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 ' - '| 36.67 | 0:17:00 | 3 | 0 | 0 | 0 ' - '| 0 |\n' - '| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 ' - '| 43.33 | 0:20:00 | 3 | 0 | 0 | 0 ' - '| 0 |' + '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |' + ' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n' + '|---------------+--------+----------------+----------------+------------------+' + '----------------+----------------+-------------------------+-----------------------|\n' + '| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |' + ' 36.67 | 0:17:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |\n' + '| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 |' + ' 43.33 | 0:20:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |' ) strategy_results = generate_strategy_comparison(all_results=results) From 08c707e0cf601210eb206820a2c03091a1317a2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 May 2021 15:38:13 +0200 Subject: [PATCH 6/8] Update docs with new format --- docs/backtesting.md | 60 +++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 5ee0b8a49..e720206bb 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -237,29 +237,29 @@ The most important in the backtesting is to understand the result. A backtesting result will look like that: ``` -========================================================= BACKTESTING REPORT ======================================================== -| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | -|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|--------:| -| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 | 0 | 21 | -| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 | 0 | 8 | -| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 | 0 | 14 | -| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 | 0 | 7 | -| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 | 0 | 10 | -| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 | 0 | 20 | -| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 | 0 | 15 | -| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 | 0 | 17 | -| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 | 0 | 18 | -| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 | 0 | 9 | -| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 | 0 | 21 | -| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 | 0 | 7 | -| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 | 0 | 13 | -| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 | 0 | 5 | -| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 | 0 | 9 | -| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 | 0 | 11 | -| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 | 0 | 23 | -| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 | 0 | 15 | -| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | -========================================================= SELL REASON STATS ========================================================= +========================================================= BACKTESTING REPORT ========================================================== +| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% | +|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:-------------|-------------------------:| +| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 | +| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 | +| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 | +| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 0 7 46.2 | +| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 0 10 44.4 | +| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 0 20 44.4 | +| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 0 15 42.3 | +| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 0 17 48.5 | +| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 0 18 43.8 | +| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 0 9 40.0 | +| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 0 21 34.4 | +| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 0 7 58.5 | +| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 0 13 43.5 | +| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 0 5 44.4 | +| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 0 9 43.8 | +| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 0 11 52.2 | +| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 | +| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 | +| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 | +========================================================= SELL REASON STATS ========================================================== | Sell Reason | Sells | Wins | Draws | Losses | |:-------------------|--------:|------:|-------:|--------:| | trailing_stop_loss | 205 | 150 | 0 | 55 | @@ -267,11 +267,11 @@ A backtesting result will look like that: | sell_signal | 56 | 36 | 0 | 20 | | force_sell | 2 | 0 | 0 | 2 | ====================================================== LEFT OPEN TRADES REPORT ====================================================== -| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | -|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|--------:| -| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 | 0 | 0 | -| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 | 0 | 0 | -| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 | 0 | 0 | +| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% | +|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:| +| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 | +| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 | +| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 | =============== SUMMARY METRICS =============== | Metric | Value | |-----------------------+---------------------| @@ -297,6 +297,7 @@ A backtesting result will look like that: | Days win/draw/lose | 12 / 82 / 25 | | Avg. Duration Winners | 4:23:00 | | Avg. Duration Loser | 6:55:00 | +| Zero Duration Trades | 4.6% (20) | | | | | Min balance | 0.00945123 BTC | | Max balance | 0.01846651 BTC | @@ -318,7 +319,7 @@ The last line will give you the overall performance of your strategy, here: ``` -| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 | +| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 | ``` The bot has made `429` trades for an average duration of `4:12:00`, with a performance of `76.20%` (profit), that means it has @@ -384,6 +385,7 @@ It contains some useful key metrics about performance of your strategy on backte | Days win/draw/lose | 12 / 82 / 25 | | Avg. Duration Winners | 4:23:00 | | Avg. Duration Loser | 6:55:00 | +| Zero Duration Trades | 4.6% (20) | | | | | Min balance | 0.00945123 BTC | | Max balance | 0.01846651 BTC | From db985cbc2ea4bfa29b2d605ca6c2c0a0d152e579 Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Sun, 23 May 2021 09:24:50 +0300 Subject: [PATCH 7/8] Fix hyperopt-show failing to display old results with missing new fields. --- freqtrade/optimize/optimize_reports.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index bda531d0d..ce6758210 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -531,8 +531,18 @@ def text_table_add_metrics(strat_results: Dict) -> str: if len(strat_results['trades']) > 0: best_trade = max(strat_results['trades'], key=lambda x: x['profit_ratio']) worst_trade = min(strat_results['trades'], key=lambda x: x['profit_ratio']) - zero_duration_trades_percent =\ - 100.0 / strat_results['total_trades'] * strat_results['zero_duration_trades'] + + # Newly added fields should be ignored if they are missing in strat_results. hyperopt-show + # command stores these results and newer version of freqtrade must be able to handle old + # results with missing new fields. + zero_duration_trades = '--' + + if 'zero_duration_trades' in strat_results: + zero_duration_trades_per = \ + 100.0 / strat_results['total_trades'] * strat_results['zero_duration_trades'] + zero_duration_trades = f'{zero_duration_trades_per}% ' \ + f'({strat_results["zero_duration_trades"]})' + metrics = [ ('Backtesting from', strat_results['backtest_start']), ('Backtesting to', strat_results['backtest_end']), @@ -569,8 +579,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: f"{strat_results['draw_days']} / {strat_results['losing_days']}"), ('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"), ('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"), - ('Zero Duration Trades', f"{zero_duration_trades_percent:.1f}% " - f"({strat_results['zero_duration_trades']})"), + ('Zero Duration Trades', zero_duration_trades), ('', ''), # Empty line to improve readability ('Min balance', round_coin_value(strat_results['csum_min'], From 916ece6a29fa140398bdc637b8bcf8194e0f7614 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 May 2021 09:15:36 +0200 Subject: [PATCH 8/8] More realistic testcase for results --- tests/optimize/test_optimize_reports.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index e0ff2f219..575bb9092 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -28,13 +28,10 @@ def test_text_table_bt_results(): results = pd.DataFrame( { - 'pair': ['ETH/BTC', 'ETH/BTC'], - 'profit_ratio': [0.1, 0.2], - 'profit_abs': [0.2, 0.4], - 'trade_duration': [10, 30], - 'wins': [2, 0], - 'draws': [0, 0], - 'losses': [0, 0] + 'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'], + 'profit_ratio': [0.1, 0.2, -0.05], + 'profit_abs': [0.2, 0.4, -0.1], + 'trade_duration': [10, 30, 20], } ) @@ -43,10 +40,10 @@ def test_text_table_bt_results(): ' Avg Duration | Win Draw Loss Win% |\n' '|---------+--------+----------------+----------------+------------------+----------------+' '----------------+-------------------------|\n' - '| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 | 15.00 |' - ' 0:20:00 | 2 0 0 100 |\n' - '| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 | 15.00 |' - ' 0:20:00 | 2 0 0 100 |' + '| ETH/BTC | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |' + ' 0:20:00 | 2 0 1 66.7 |\n' + '| TOTAL | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |' + ' 0:20:00 | 2 0 1 66.7 |' ) pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC',