Repository: freqtrade/freqtrade

Issue:
Hyperopt-list and Hyperopt-show crash WSL 2 Backend
## Describe your environment

  * Operating system: windows 10 w/ Docker
  * Python Version: Says not found, but everything works so I definitely have it. My guess is 3.8 (`python -V`)
  * CCXT version: Also not recognized (`pip freeze | grep ccxt`)
  * Freqtrade Version: 2-21.5 (`freqtrade -V` or `docker-compose run --rm freqtrade -V` for Freqtrade running in docker)
  
Note: All issues other than enhancement requests will be closed without further comment if the above template is deleted or not filled out.

## Describe the problem:

*Explain the problem you have encountered*

So I've tried to do this on a linux virtual machine, as well as on my windows 10 machine using Docker. After running hyperopt on a strategy optimizing for sharpe ratio, I try to look through other results that were found by using the hyperopt-show and hyperopt-list commands, but after about a minute on the virtual machine it just prints the message "Killed", or on Windows 10 
I get "Windows named pipe error: the pipe has been ended. (code: 109)" as well as a windows notification saying that The WSL 2 Backend has stopped unexpectedly. This happens every time without fail, and I'm unable to view my hyperopt results.

### Steps to reproduce:

  1. Run hyperopt with a strategy optimizing for Sharpe ~5000 epochs
  2. When done, run hyperopt-list or hyperopt-show command
  
### Observed Results:

  * What happened?
  Process killed, or WSL 2 Backend Crash

  * What did you expect to happen?
  I expected to see my hyperopt results

### Relevant code exceptions or logs

Note: Please copy/paste text of the messages, no screenshots of logs please.

Linux: "Killed".

Windows 10 w/ Docker: "Windows named pipe error: the pipe has been ended. (code: 109)"
Windows 10 Notification Popup: "The WSL 2 Backend has stopped unexpectedly."



Discussion:
best check how big the latest file in `user_data/hyperopt_results` is.

with 5000 epochs, it might get pretty large - so most likely, your pc is running out of memory...
Without further information, there is nothing we can do to support or investigate.

I hope this means that you were able to solve this issue - if not feel free to comment below / reopen with the information requested above.

Resolving patch:
diff --git a/freqtrade/optimize/hyperopt_epoch_filters.py b/freqtrade/optimize/hyperopt_epoch_filters.py
--- a/freqtrade/optimize/hyperopt_epoch_filters.py
+++ b/freqtrade/optimize/hyperopt_epoch_filters.py
@@ -7,7 +7,7 @@
 logger = logging.getLogger(__name__)
 
 
-def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
+def hyperopt_filter_epochs(epochs: List, filteroptions: dict, log: bool = True) -> List:
     """
     Filter our items from the list of hyperopt results
     """
@@ -24,11 +24,11 @@ def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
     epochs = _hyperopt_filter_epochs_profit(epochs, filteroptions)
 
     epochs = _hyperopt_filter_epochs_objective(epochs, filteroptions)
-
-    logger.info(f"{len(epochs)} " +
-                ("best " if filteroptions['only_best'] else "") +
-                ("profitable " if filteroptions['only_profitable'] else "") +
-                "epochs found.")
+    if log:
+        logger.info(f"{len(epochs)} " +
+                    ("best " if filteroptions['only_best'] else "") +
+                    ("profitable " if filteroptions['only_profitable'] else "") +
+                    "epochs found.")
     return epochs
 
 
diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py
--- a/freqtrade/optimize/hyperopt_tools.py
+++ b/freqtrade/optimize/hyperopt_tools.py
@@ -4,7 +4,7 @@
 from copy import deepcopy
 from datetime import datetime, timezone
 from pathlib import Path
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, Dict, Iterator, List, Optional, Tuple
 
 import numpy as np
 import rapidjson
@@ -90,37 +90,33 @@ def has_space(config: Dict[str, Any], space: str) -> bool:
             return any(s in config['spaces'] for s in [space, 'all', 'default'])
 
     @staticmethod
-    def _read_results(results_file: Path) -> List:
+    def _read_results(results_file: Path, batch_size: int = 10) -> Iterator[List[Any]]:
         """
-        Read hyperopt results from file
+        Stream hyperopt results from file
         """
         import rapidjson
         logger.info(f"Reading epochs from '{results_file}'")
         with results_file.open('r') as f:
-            data = [rapidjson.loads(line) for line in f]
-        return data
+            data = []
+            for line in f:
+                data += [rapidjson.loads(line)]
+                if len(data) >= batch_size:
+                    yield data
+                    data = []
+        yield data
 
     @staticmethod
-    def load_previous_results(results_file: Path) -> List:
-        """
-        Load data for epochs from the file if we have one
-        """
-        epochs: List = []
+    def _test_hyperopt_results_exist(results_file) -> bool:
         if results_file.is_file() and results_file.stat().st_size > 0:
             if results_file.suffix == '.pickle':
                 raise OperationalException(
                     "Legacy hyperopt results are no longer supported."
                     "Please rerun hyperopt or use an older version to load this file."
                 )
-            else:
-                epochs = HyperoptTools._read_results(results_file)
-            # Detection of some old format, without 'is_best' field saved
-            if epochs[0].get('is_best') is None:
-                raise OperationalException(
-                    "The file with HyperoptTools results is incompatible with this version "
-                    "of Freqtrade and cannot be loaded.")
-            logger.info(f"Loaded {len(epochs)} previous evaluations from disk.")
-        return epochs
+            return True
+        else:
+            # No file found.
+            return False
 
     @staticmethod
     def load_filtered_results(results_file: Path, config: Dict[str, Any]) -> Tuple[List, int]:
@@ -138,12 +134,24 @@ def load_filtered_results(results_file: Path, config: Dict[str, Any]) -> Tuple[L
             'filter_min_objective': config.get('hyperopt_list_min_objective', None),
             'filter_max_objective': config.get('hyperopt_list_max_objective', None),
         }
+        if not HyperoptTools._test_hyperopt_results_exist(results_file):
+            # No file found.
+            return [], 0
+
+        epochs = []
+        total_epochs = 0
+        for epochs_tmp in HyperoptTools._read_results(results_file):
+            if total_epochs == 0 and epochs_tmp[0].get('is_best') is None:
+                raise OperationalException(
+                    "The file with HyperoptTools results is incompatible with this version "
+                    "of Freqtrade and cannot be loaded.")
+            total_epochs += len(epochs_tmp)
+            epochs += hyperopt_filter_epochs(epochs_tmp, filteroptions, log=False)
 
-        # Previous evaluations
-        epochs = HyperoptTools.load_previous_results(results_file)
-        total_epochs = len(epochs)
+        logger.info(f"Loaded {total_epochs} previous evaluations from disk.")
 
-        epochs = hyperopt_filter_epochs(epochs, filteroptions)
+        # Final filter run ...
+        epochs = hyperopt_filter_epochs(epochs, filteroptions, log=True)
 
         return epochs, total_epochs
 


Test patch:
diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py
--- a/tests/commands/test_commands.py
+++ b/tests/commands/test_commands.py
@@ -941,8 +941,16 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
 def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmpdir):
     csv_file = Path(tmpdir) / "test.csv"
     mocker.patch(
-        'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results',
-        MagicMock(return_value=saved_hyperopt_results)
+        'freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist',
+        return_value=True
+        )
+
+    def fake_iterator(*args, **kwargs):
+        yield from [saved_hyperopt_results]
+
+    mocker.patch(
+        'freqtrade.optimize.hyperopt_tools.HyperoptTools._read_results',
+        side_effect=fake_iterator
     )
 
     args = [
@@ -1175,8 +1183,16 @@ def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results, tmpdir):
 
 def test_hyperopt_show(mocker, capsys, saved_hyperopt_results):
     mocker.patch(
-        'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results',
-        MagicMock(return_value=saved_hyperopt_results)
+        'freqtrade.optimize.hyperopt_tools.HyperoptTools._test_hyperopt_results_exist',
+        return_value=True
+    )
+
+    def fake_iterator(*args, **kwargs):
+        yield from [saved_hyperopt_results]
+
+    mocker.patch(
+        'freqtrade.optimize.hyperopt_tools.HyperoptTools._read_results',
+        side_effect=fake_iterator
     )
     mocker.patch('freqtrade.commands.hyperopt_commands.show_backtest_result')
 
diff --git a/tests/optimize/test_hyperopt_tools.py b/tests/optimize/test_hyperopt_tools.py
--- a/tests/optimize/test_hyperopt_tools.py
+++ b/tests/optimize/test_hyperopt_tools.py
@@ -20,9 +20,14 @@ def create_results() -> List[Dict]:
 
 
 def test_save_results_saves_epochs(hyperopt, tmpdir, caplog) -> None:
+
+    hyperopt.results_file = Path(tmpdir / 'ut_results.fthypt')
+
+    hyperopt_epochs = HyperoptTools.load_filtered_results(hyperopt.results_file, {})
+    assert hyperopt_epochs == ([], 0)
+
     # Test writing to temp dir and reading again
     epochs = create_results()
-    hyperopt.results_file = Path(tmpdir / 'ut_results.fthypt')
 
     caplog.set_level(logging.DEBUG)
 
@@ -33,15 +38,28 @@ def test_save_results_saves_epochs(hyperopt, tmpdir, caplog) -> None:
     hyperopt._save_result(epochs[0])
     assert log_has(f"2 epochs saved to '{hyperopt.results_file}'.", caplog)
 
-    hyperopt_epochs = HyperoptTools.load_previous_results(hyperopt.results_file)
+    hyperopt_epochs = HyperoptTools.load_filtered_results(hyperopt.results_file, {})
     assert len(hyperopt_epochs) == 2
+    assert hyperopt_epochs[1] == 2
+    assert len(hyperopt_epochs[0]) == 2
+
+    result_gen = HyperoptTools._read_results(hyperopt.results_file, 1)
+    epoch = next(result_gen)
+    assert len(epoch) == 1
+    assert epoch[0] == epochs[0]
+    epoch = next(result_gen)
+    assert len(epoch) == 1
+    epoch = next(result_gen)
+    assert len(epoch) == 0
+    with pytest.raises(StopIteration):
+        next(result_gen)
 
 
 def test_load_previous_results2(mocker, testdatadir, caplog) -> None:
     results_file = testdatadir / 'hyperopt_results_SampleStrategy.pickle'
     with pytest.raises(OperationalException,
                        match=r"Legacy hyperopt results are no longer supported.*"):
-        HyperoptTools.load_previous_results(results_file)
+        HyperoptTools.load_filtered_results(results_file, {})
 
 
 @pytest.mark.parametrize("spaces, expected_results", [