Store epochs as json per line

This commit is contained in:
Matthias 2021-05-12 05:58:25 +02:00
parent 7398ea88e0
commit 06bf1aa274
2 changed files with 57 additions and 52 deletions

View File

@ -5,15 +5,16 @@ This module contains the hyperopt logic
""" """
import logging import logging
import os
import random import random
import warnings import warnings
from datetime import datetime, timezone from datetime import datetime, timezone
from math import ceil from math import ceil
from operator import itemgetter
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import progressbar import progressbar
import rapidjson
from colorama import Fore, Style from colorama import Fore, Style
from colorama import init as colorama_init from colorama import init as colorama_init
from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects
@ -86,7 +87,7 @@ class Hyperopt:
time_now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") time_now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
strategy = str(self.config['strategy']) strategy = str(self.config['strategy'])
self.results_file: Path = (self.config['user_data_dir'] / 'hyperopt_results' / self.results_file: Path = (self.config['user_data_dir'] / 'hyperopt_results' /
f'strategy_{strategy}_hyperopt_results_{time_now}.pickle') f'strategy_{strategy}_hyperopt_results_{time_now}.fthypt')
self.data_pickle_file = (self.config['user_data_dir'] / self.data_pickle_file = (self.config['user_data_dir'] /
'hyperopt_results' / 'hyperopt_tickerdata.pkl') 'hyperopt_results' / 'hyperopt_tickerdata.pkl')
self.total_epochs = config.get('epochs', 0) self.total_epochs = config.get('epochs', 0)
@ -96,9 +97,7 @@ class Hyperopt:
self.clean_hyperopt() self.clean_hyperopt()
self.num_epochs_saved = 0 self.num_epochs_saved = 0
self.current_best_epoch: Optional[Dict[str, Any]] = None
# Previous evaluations
self.epochs: List = []
# Populate functions here (hasattr is slow so should not be run during "regular" operations) # Populate functions here (hasattr is slow so should not be run during "regular" operations)
if hasattr(self.custom_hyperopt, 'populate_indicators'): if hasattr(self.custom_hyperopt, 'populate_indicators'):
@ -156,15 +155,18 @@ class Hyperopt:
# and the values are taken from the list of parameters. # and the values are taken from the list of parameters.
return {d.name: v for d, v in zip(dimensions, raw_params)} return {d.name: v for d, v in zip(dimensions, raw_params)}
def _save_results(self) -> None: def _save_result(self, epoch: Dict) -> None:
""" """
Save hyperopt results to file Save hyperopt results to file
Store one line per epoch.
While not a valid json object - this allows appending easily.
:param epoch: result dictionary for this epoch.
""" """
num_epochs = len(self.epochs) with self.results_file.open('a') as f:
if num_epochs > self.num_epochs_saved: rapidjson.dump(epoch, f, default=str, number_mode=rapidjson.NM_NATIVE)
logger.debug(f"Saving {num_epochs} {plural(num_epochs, 'epoch')}.") f.write(os.linesep)
dump(self.epochs, self.results_file)
self.num_epochs_saved = num_epochs self.num_epochs_saved += 1
logger.debug(f"{self.num_epochs_saved} {plural(self.num_epochs_saved, 'epoch')} " logger.debug(f"{self.num_epochs_saved} {plural(self.num_epochs_saved, 'epoch')} "
f"saved to '{self.results_file}'.") f"saved to '{self.results_file}'.")
# Store hyperopt filename # Store hyperopt filename
@ -442,25 +444,21 @@ class Hyperopt:
if is_best: if is_best:
self.current_best_loss = val['loss'] self.current_best_loss = val['loss']
self.epochs.append(val) self.current_best_epoch = val
# Save results after each best epoch and every 100 epochs self._save_result(val)
if is_best or current % 100 == 0:
self._save_results()
pbar.update(current) pbar.update(current)
except KeyboardInterrupt: except KeyboardInterrupt:
print('User interrupted..') print('User interrupted..')
self._save_results()
logger.info(f"{self.num_epochs_saved} {plural(self.num_epochs_saved, 'epoch')} " logger.info(f"{self.num_epochs_saved} {plural(self.num_epochs_saved, 'epoch')} "
f"saved to '{self.results_file}'.") f"saved to '{self.results_file}'.")
if self.epochs: if self.current_best_epoch:
sorted_epochs = sorted(self.epochs, key=itemgetter('loss')) HyperoptTools.print_epoch_details(self.current_best_epoch, self.total_epochs,
best_epoch = sorted_epochs[0] self.print_json)
HyperoptTools.print_epoch_details(best_epoch, self.total_epochs, self.print_json)
else: else:
# This is printed when Ctrl+C is pressed quickly, before first epochs have # This is printed when Ctrl+C is pressed quickly, before first epochs have
# a chance to be evaluated. # a chance to be evaluated.

View File

@ -386,7 +386,8 @@ def test_roi_table_generation(hyperopt) -> None:
def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None: def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
@ -425,9 +426,9 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
assert dumper.called # Should be called for historical candle data
# Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 1
assert dumper.call_count == 2 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell") assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy") assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades") assert hasattr(hyperopt, "max_open_trades")
@ -714,7 +715,8 @@ def test_clean_hyperopt(mocker, hyperopt_conf, caplog):
def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None: def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
@ -765,13 +767,14 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
':{},"stoploss":null,"trailing_stop":null}' ':{},"stoploss":null,"trailing_stop":null}'
) )
assert result_str in out # noqa: E501 assert result_str in out # noqa: E501
assert dumper.called # Should be called for historical candle data
# Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 1
assert dumper.call_count == 2 assert dumper2.call_count == 1
def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None: def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None))) MagicMock(return_value=(MagicMock(), None)))
@ -813,13 +816,14 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert '{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi":{},"stoploss":null}' in out # noqa: E501 assert '{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi":{},"stoploss":null}' in out # noqa: E501
assert dumper.called # Should be called for historical candle data
# Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 1
assert dumper.call_count == 2 assert dumper2.call_count == 1
def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None: def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None))) MagicMock(return_value=(MagicMock(), None)))
@ -860,13 +864,14 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert '{"minimal_roi":{},"stoploss":null}' in out assert '{"minimal_roi":{},"stoploss":null}' in out
assert dumper.called
# Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 1
assert dumper.call_count == 2 assert dumper2.call_count == 1
def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> None: def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None))) MagicMock(return_value=(MagicMock(), None)))
@ -908,9 +913,9 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
assert dumper.called assert dumper.call_count == 1
# Should be called twice, once for historical candle data, once to save evaluations assert dumper2.call_count == 1
assert dumper.call_count == 2
assert hasattr(hyperopt.backtesting.strategy, "advise_sell") assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy") assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades") assert hasattr(hyperopt, "max_open_trades")
@ -946,7 +951,8 @@ def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None:
def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None: def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None))) MagicMock(return_value=(MagicMock(), None)))
@ -989,8 +995,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
assert dumper.called assert dumper.called
# Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 1
assert dumper.call_count == 2 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell") assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy") assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades") assert hasattr(hyperopt, "max_open_trades")
@ -999,7 +1005,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None: def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None))) MagicMock(return_value=(MagicMock(), None)))
@ -1042,8 +1049,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
assert dumper.called assert dumper.called
# Should be called twice, once for historical candle data, once to save evaluations assert dumper.call_count == 1
assert dumper.call_count == 2 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell") assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy") assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades") assert hasattr(hyperopt, "max_open_trades")