diff --git a/docs/backtesting.md b/docs/backtesting.md index ee9926f32..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 | @@ -413,6 +415,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. @@ -472,11 +475,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 2be3c3e62..ce6758210 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,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', - 'Wins', 'Draws', 'Losses'] + 'Win Draw Loss Win%'] + + +def _generate_wins_draws_losses(wins, draws, losses): + 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: @@ -164,6 +174,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 @@ -208,6 +229,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 +242,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, } @@ -437,7 +461,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, @@ -454,9 +479,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 Win%', 'Avg Profit %', 'Cum Profit %', f'Tot Profit {stake_currency}', @@ -464,7 +487,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'], @@ -482,11 +506,22 @@ 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('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'] - ] 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") @@ -496,6 +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']) + + # 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']), @@ -508,7 +555,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 +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', zero_duration_trades), ('', ''), # Empty line to improve readability ('Min balance', round_coin_value(strat_results['csum_min'], diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 1afa5c59c..575bb9092 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 @@ -27,25 +28,22 @@ 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], } ) 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 | 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', @@ -270,14 +268,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, @@ -325,9 +323,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 +341,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 +353,17 @@ 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' + '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |' + ' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n' '|---------------+--------+----------------+----------------+------------------+' - '----------------+----------------+--------+---------+----------|\n' + '----------------+----------------+-------------------------+-----------------------|\n' '| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |' - ' 36.67 | 0:17:00 | 3 | 0 | 0 |\n' + ' 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 |' + ' 43.33 | 0:20:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |' ) strategy_results = generate_strategy_comparison(all_results=results) - assert text_table_strategy(strategy_results, 'BTC') == result_str