Merge pull request #351 from gcarq/feat/hyperopt-resume
resume hyperopt run
This commit is contained in:
commit
7cdbd550c8
2
.gitignore
vendored
2
.gitignore
vendored
@ -85,3 +85,5 @@ target/
|
|||||||
.venv
|
.venv
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
hyperopt_trials.pickle
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
import pickle
|
||||||
|
import signal
|
||||||
|
import os
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from math import exp
|
from math import exp
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
@ -27,7 +30,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# set TARGET_TRADES to suit your number concurrent trades so its realistic to 20days of data
|
# set TARGET_TRADES to suit your number concurrent trades so its realistic to 20days of data
|
||||||
TARGET_TRADES = 1100
|
TARGET_TRADES = 1100
|
||||||
TOTAL_TRIES = None
|
TOTAL_TRIES = 0
|
||||||
_CURRENT_TRIES = 0
|
_CURRENT_TRIES = 0
|
||||||
CURRENT_BEST_LOSS = 100
|
CURRENT_BEST_LOSS = 100
|
||||||
|
|
||||||
@ -43,6 +46,10 @@ EXPECTED_MAX_PROFIT = 3.85
|
|||||||
PROCESSED = None # optimize.preprocess(optimize.load_data())
|
PROCESSED = None # optimize.preprocess(optimize.load_data())
|
||||||
OPTIMIZE_CONFIG = hyperopt_optimize_conf()
|
OPTIMIZE_CONFIG = hyperopt_optimize_conf()
|
||||||
|
|
||||||
|
# Hyperopt Trials
|
||||||
|
TRIALS_FILE = os.path.join('freqtrade', 'optimize', 'hyperopt_trials.pickle')
|
||||||
|
TRIALS = Trials()
|
||||||
|
|
||||||
# Monkey patch config
|
# Monkey patch config
|
||||||
from freqtrade import main # noqa
|
from freqtrade import main # noqa
|
||||||
main._CONF = OPTIMIZE_CONFIG
|
main._CONF = OPTIMIZE_CONFIG
|
||||||
@ -99,6 +106,26 @@ SPACE = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def save_trials(trials, trials_path=TRIALS_FILE):
|
||||||
|
"""Save hyperopt trials to file"""
|
||||||
|
logger.info('Saving Trials to \'{}\''.format(trials_path))
|
||||||
|
pickle.dump(trials, open(trials_path, 'wb'))
|
||||||
|
|
||||||
|
|
||||||
|
def read_trials(trials_path=TRIALS_FILE):
|
||||||
|
"""Read hyperopt trials file"""
|
||||||
|
logger.info('Reading Trials from \'{}\''.format(trials_path))
|
||||||
|
trials = pickle.load(open(trials_path, 'rb'))
|
||||||
|
os.remove(trials_path)
|
||||||
|
return trials
|
||||||
|
|
||||||
|
|
||||||
|
def log_trials_result(trials):
|
||||||
|
vals = json.dumps(trials.best_trial['misc']['vals'], indent=4)
|
||||||
|
results = trials.best_trial['result']['result']
|
||||||
|
logger.info('Best result:\n%s\nwith values:\n%s', results, vals)
|
||||||
|
|
||||||
|
|
||||||
def log_results(results):
|
def log_results(results):
|
||||||
""" log results if it is better than any previous evaluation """
|
""" log results if it is better than any previous evaluation """
|
||||||
global CURRENT_BEST_LOSS
|
global CURRENT_BEST_LOSS
|
||||||
@ -216,7 +243,8 @@ def buy_strategy_generator(params):
|
|||||||
|
|
||||||
|
|
||||||
def start(args):
|
def start(args):
|
||||||
global TOTAL_TRIES, PROCESSED, SPACE
|
global TOTAL_TRIES, PROCESSED, SPACE, TRIALS, _CURRENT_TRIES
|
||||||
|
|
||||||
TOTAL_TRIES = args.epochs
|
TOTAL_TRIES = args.epochs
|
||||||
|
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
exchange._API = Bittrex({'key': '', 'secret': ''})
|
||||||
@ -238,9 +266,19 @@ def start(args):
|
|||||||
logger.info('Start scripts/start-mongodb.sh and start-hyperopt-worker.sh manually!')
|
logger.info('Start scripts/start-mongodb.sh and start-hyperopt-worker.sh manually!')
|
||||||
|
|
||||||
db_name = 'freqtrade_hyperopt'
|
db_name = 'freqtrade_hyperopt'
|
||||||
trials = MongoTrials('mongo://127.0.0.1:1234/{}/jobs'.format(db_name), exp_key='exp1')
|
TRIALS = MongoTrials('mongo://127.0.0.1:1234/{}/jobs'.format(db_name), exp_key='exp1')
|
||||||
else:
|
else:
|
||||||
trials = Trials()
|
logger.info('Preparing Trials..')
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
# read trials file if we have one
|
||||||
|
if os.path.exists(TRIALS_FILE):
|
||||||
|
TRIALS = read_trials()
|
||||||
|
|
||||||
|
_CURRENT_TRIES = len(TRIALS.results)
|
||||||
|
TOTAL_TRIES = TOTAL_TRIES + _CURRENT_TRIES
|
||||||
|
logger.info(
|
||||||
|
'Continuing with trials. Current: {}, Total: {}'
|
||||||
|
.format(_CURRENT_TRIES, TOTAL_TRIES))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
best_parameters = fmin(
|
best_parameters = fmin(
|
||||||
@ -248,10 +286,10 @@ def start(args):
|
|||||||
space=SPACE,
|
space=SPACE,
|
||||||
algo=tpe.suggest,
|
algo=tpe.suggest,
|
||||||
max_evals=TOTAL_TRIES,
|
max_evals=TOTAL_TRIES,
|
||||||
trials=trials
|
trials=TRIALS
|
||||||
)
|
)
|
||||||
|
|
||||||
results = sorted(trials.results, key=itemgetter('loss'))
|
results = sorted(TRIALS.results, key=itemgetter('loss'))
|
||||||
best_result = results[0]['result']
|
best_result = results[0]['result']
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -265,3 +303,15 @@ def start(args):
|
|||||||
|
|
||||||
logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4))
|
logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4))
|
||||||
logger.info('Best Result:\n%s', best_result)
|
logger.info('Best Result:\n%s', best_result)
|
||||||
|
|
||||||
|
# Store trials result to file to resume next time
|
||||||
|
save_trials(TRIALS)
|
||||||
|
|
||||||
|
|
||||||
|
def signal_handler(sig, frame):
|
||||||
|
"""Hyperopt SIGINT handler"""
|
||||||
|
logger.info('Hyperopt received {}'.format(signal.Signals(sig).name))
|
||||||
|
|
||||||
|
save_trials(TRIALS)
|
||||||
|
log_trials_result(TRIALS)
|
||||||
|
sys.exit(0)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
# pragma pylint: disable=missing-docstring,W0212,C0103
|
# pragma pylint: disable=missing-docstring,W0212,C0103
|
||||||
|
|
||||||
from freqtrade.optimize.hyperopt import calculate_loss, TARGET_TRADES, EXPECTED_MAX_PROFIT, start, \
|
from freqtrade.optimize.hyperopt import calculate_loss, TARGET_TRADES, EXPECTED_MAX_PROFIT, start, \
|
||||||
log_results
|
log_results, save_trials, read_trials
|
||||||
|
|
||||||
|
|
||||||
def test_loss_calculation_prefer_correct_trade_count():
|
def test_loss_calculation_prefer_correct_trade_count():
|
||||||
@ -27,16 +26,37 @@ def test_loss_calculation_has_limited_profit():
|
|||||||
|
|
||||||
|
|
||||||
def create_trials(mocker):
|
def create_trials(mocker):
|
||||||
|
"""
|
||||||
|
When creating trials, mock the hyperopt Trials so that *by default*
|
||||||
|
- we don't create any pickle'd files in the filesystem
|
||||||
|
- we might have a pickle'd file so make sure that we return
|
||||||
|
false when looking for it
|
||||||
|
"""
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.TRIALS_FILE',
|
||||||
|
return_value='freqtrade/tests/optimize/ut_trials.pickle')
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.os.path.exists',
|
||||||
|
return_value=False)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.save_trials',
|
||||||
|
return_value=None)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.read_trials',
|
||||||
|
return_value=None)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.os.remove',
|
||||||
|
return_value=True)
|
||||||
return mocker.Mock(
|
return mocker.Mock(
|
||||||
results=[{
|
results=[{
|
||||||
'loss': 1,
|
'loss': 1,
|
||||||
'result': 'foo'
|
'result': 'foo',
|
||||||
}]
|
'status': 'ok'
|
||||||
|
}],
|
||||||
|
best_trial={'misc': {'vals': {'adx': 999}}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_start_calls_fmin(mocker):
|
def test_start_calls_fmin(mocker):
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.Trials', return_value=create_trials(mocker))
|
trials = create_trials(mocker)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.TRIALS', return_value=trials)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.sorted',
|
||||||
|
return_value=trials.results)
|
||||||
mocker.patch('freqtrade.optimize.preprocess')
|
mocker.patch('freqtrade.optimize.preprocess')
|
||||||
mocker.patch('freqtrade.optimize.load_data')
|
mocker.patch('freqtrade.optimize.load_data')
|
||||||
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||||
@ -141,3 +161,63 @@ def test_fmin_throw_value_error(mocker, caplog):
|
|||||||
|
|
||||||
for line in exists:
|
for line in exists:
|
||||||
assert line in caplog.text
|
assert line in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_resuming_previous_hyperopt_results_succeeds(mocker):
|
||||||
|
import freqtrade.optimize.hyperopt as hyperopt
|
||||||
|
trials = create_trials(mocker)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.TRIALS',
|
||||||
|
return_value=trials)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.os.path.exists',
|
||||||
|
return_value=True)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.len',
|
||||||
|
return_value=len(trials.results))
|
||||||
|
mock_read = mocker.patch('freqtrade.optimize.hyperopt.read_trials',
|
||||||
|
return_value=trials)
|
||||||
|
mock_save = mocker.patch('freqtrade.optimize.hyperopt.save_trials',
|
||||||
|
return_value=None)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.sorted',
|
||||||
|
return_value=trials.results)
|
||||||
|
mocker.patch('freqtrade.optimize.preprocess')
|
||||||
|
mocker.patch('freqtrade.optimize.load_data')
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.fmin',
|
||||||
|
return_value={})
|
||||||
|
args = mocker.Mock(epochs=1,
|
||||||
|
config='config.json.example',
|
||||||
|
mongodb=False)
|
||||||
|
|
||||||
|
start(args)
|
||||||
|
|
||||||
|
mock_read.assert_called_once()
|
||||||
|
mock_save.assert_called_once()
|
||||||
|
|
||||||
|
current_tries = hyperopt._CURRENT_TRIES
|
||||||
|
total_tries = hyperopt.TOTAL_TRIES
|
||||||
|
|
||||||
|
assert current_tries == len(trials.results)
|
||||||
|
assert total_tries == (current_tries + len(trials.results))
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_trials_saves_trials(mocker):
|
||||||
|
trials = create_trials(mocker)
|
||||||
|
mock_dump = mocker.patch('freqtrade.optimize.hyperopt.pickle.dump',
|
||||||
|
return_value=None)
|
||||||
|
trials_path = mocker.patch('freqtrade.optimize.hyperopt.TRIALS_FILE',
|
||||||
|
return_value='ut_trials.pickle')
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.open',
|
||||||
|
return_value=trials_path)
|
||||||
|
save_trials(trials, trials_path)
|
||||||
|
|
||||||
|
mock_dump.assert_called_once_with(trials, trials_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_trials_returns_trials_file(mocker):
|
||||||
|
trials = create_trials(mocker)
|
||||||
|
mock_load = mocker.patch('freqtrade.optimize.hyperopt.pickle.load',
|
||||||
|
return_value=trials)
|
||||||
|
mock_open = mocker.patch('freqtrade.optimize.hyperopt.open',
|
||||||
|
return_value=mock_load)
|
||||||
|
|
||||||
|
assert read_trials() == trials
|
||||||
|
mock_open.assert_called_once()
|
||||||
|
mock_load.assert_called_once()
|
||||||
|
Loading…
Reference in New Issue
Block a user