Repository: freqtrade/freqtrade

Issue:
Columns in hyperopt output
Let's discuss improvements to hyperopt (and possibly to backtesting, to be aligned) column headers.

Current:
```
+--------+---------+----------+--------------+---------------------------+----------------+-------------+
|   Best |   Epoch |   Trades |   Avg profit |                    Profit |   Avg duration |   Objective |
|--------+---------+----------+--------------+---------------------------+----------------+-------------|
|   Best |   1/100 |       21 |       -0.07% |   -2.94404909    (-1.47%) |        376.4 m |     2.20748 |
|   Best |   2/100 |      354 |       -0.01% |  -10.29255100    (-5.14%) |        131.2 m |     1.81064 |
|   Best |   3/100 |      180 |        0.01% |    4.57664850     (2.29%) |        695.7 m |     0.40082 |
|      * |   4/100 |        2 |        0.00% |                        -- |      1,377.5 m |         N/A |
|      * |   5/100 |       24 |       -0.33% |  -15.71527070    (-7.85%) |        628.3 m |     2.97213 |
```

Proposed as the starting point:
```
+--------+---------+--------------+------------+---------------------------+----------------+--------------+
|        |   Epoch |       Trades | Avg profit |             Total profit, |      Avg trade | Optimization |
|        |         |      (W/D/L) |  per trade |     absolute (percentage) |       duration |    objective |
|--------+---------+--------------+------------+---------------------------+----------------+--------------|
|   Best |   1/100 | 21 (10/1/10) |     -0.07% |   -2.94404909    (-1.47%) |        376.4 m |      2.20748 |
|   Best |   2/100 |          354 |     -0.01% |  -10.29255100    (-5.14%) |        131.2 m |      1.81064 |
|        |         |  (200/4/150) |            |                           |                |              |
|   Best |   3/100 |          180 |      0.01% |    4.57664850     (2.29%) |        695.7 m |      0.40082 |
|      * |   4/100 |            2 |      0.00% |                        -- |      1,377.5 m |          N/A |
|      * |   5/100 |           24 |     -0.33% |  -15.71527070    (-7.85%) |        628.3 m |      2.97213 |
```



Discussion:

Resolving patch:
diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py
--- a/freqtrade/optimize/hyperopt.py
+++ b/freqtrade/optimize/hyperopt.py
@@ -312,11 +312,16 @@ def get_result_table(config: dict, results: list, total_epochs: int, highlight_b
 
         trials = json_normalize(results, max_level=1)
         trials['Best'] = ''
+        if 'results_metrics.winsdrawslosses' not in trials.columns:
+            # Ensure compatibility with older versions of hyperopt results
+            trials['results_metrics.winsdrawslosses'] = 'N/A'
+
         trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count',
+                         'results_metrics.winsdrawslosses',
                          'results_metrics.avg_profit', 'results_metrics.total_profit',
                          'results_metrics.profit', 'results_metrics.duration',
                          'loss', 'is_initial_point', 'is_best']]
-        trials.columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Total profit',
+        trials.columns = ['Best', 'Epoch', 'Trades', 'W/D/L', 'Avg profit', 'Total profit',
                           'Profit', 'Avg duration', 'Objective', 'is_initial_point', 'is_best']
         trials['is_profit'] = False
         trials.loc[trials['is_initial_point'], 'Best'] = '*     '
@@ -558,9 +563,17 @@ def _get_results_dict(self, backtesting_results, min_date, max_date,
         }
 
     def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict:
+        wins = len(backtesting_results[backtesting_results.profit_percent > 0])
+        draws = len(backtesting_results[backtesting_results.profit_percent == 0])
+        losses = len(backtesting_results[backtesting_results.profit_percent < 0])
         return {
             'trade_count': len(backtesting_results.index),
+            'wins': wins,
+            'draws': draws,
+            'losses': losses,
+            'winsdrawslosses': f"{wins}/{draws}/{losses}",
             'avg_profit': backtesting_results.profit_percent.mean() * 100.0,
+            'median_profit': backtesting_results.profit_percent.median() * 100.0,
             'total_profit': backtesting_results.profit_abs.sum(),
             'profit': backtesting_results.profit_percent.sum() * 100.0,
             'duration': backtesting_results.trade_duration.mean(),
@@ -572,7 +585,10 @@ def _format_results_explanation_string(self, results_metrics: Dict) -> str:
         """
         stake_cur = self.config['stake_currency']
         return (f"{results_metrics['trade_count']:6d} trades. "
+                f"{results_metrics['wins']}/{results_metrics['draws']}"
+                f"/{results_metrics['losses']} Wins/Draws/Losses. "
                 f"Avg profit {results_metrics['avg_profit']: 6.2f}%. "
+                f"Median profit {results_metrics['median_profit']: 6.2f}%. "
                 f"Total profit {results_metrics['total_profit']: 11.8f} {stake_cur} "
                 f"({results_metrics['profit']: 7.2f}\N{GREEK CAPITAL LETTER SIGMA}%). "
                 f"Avg duration {results_metrics['duration']:5.1f} min."


Test patch:
diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py
--- a/tests/optimize/test_hyperopt.py
+++ b/tests/optimize/test_hyperopt.py
@@ -744,8 +744,10 @@ def test_generate_optimizer(mocker, default_conf) -> None:
     }
     response_expected = {
         'loss': 1.9840569076926293,
-        'results_explanation': ('     1 trades. Avg profit   2.31%. Total profit  0.00023300 BTC '
-                                '(   2.31\N{GREEK CAPITAL LETTER SIGMA}%). Avg duration 100.0 min.'
+        'results_explanation': ('     1 trades. 1/0/0 Wins/Draws/Losses. '
+                                'Avg profit   2.31%. Median profit   2.31%. Total profit  '
+                                '0.00023300 BTC (   2.31\N{GREEK CAPITAL LETTER SIGMA}%). '
+                                'Avg duration 100.0 min.'
                                 ).encode(locale.getpreferredencoding(), 'replace').decode('utf-8'),
         'params_details': {'buy': {'adx-enabled': False,
                                    'adx-value': 0,
@@ -776,10 +778,15 @@ def test_generate_optimizer(mocker, default_conf) -> None:
                                         'trailing_stop_positive_offset': 0.07}},
         'params_dict': optimizer_param,
         'results_metrics': {'avg_profit': 2.3117,
+                            'draws': 0,
                             'duration': 100.0,
+                            'losses': 0,
+                            'winsdrawslosses': '1/0/0',
+                            'median_profit': 2.3117,
                             'profit': 2.3117,
                             'total_profit': 0.000233,
-                            'trade_count': 1},
+                            'trade_count': 1,
+                            'wins': 1},
         'total_profit': 0.00023300
     }