Complete Backtesting and Hyperopt unit tests
This commit is contained in:
parent
f4ec073099
commit
6ef7b7d93d
@ -30,7 +30,7 @@ class Analyze(object):
|
|||||||
Init Analyze
|
Init Analyze
|
||||||
:param config: Bot configuration (use the one from Configuration())
|
:param config: Bot configuration (use the one from Configuration())
|
||||||
"""
|
"""
|
||||||
self.logger = Logger(name=__name__).get_logger()
|
self.logger = Logger(name=__name__, level=config.get('loglevel')).get_logger()
|
||||||
|
|
||||||
self.config = config
|
self.config = config
|
||||||
self.strategy = Strategy(self.config)
|
self.strategy = Strategy(self.config)
|
||||||
|
@ -19,7 +19,8 @@ class Configuration(object):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, args: List[str]) -> None:
|
def __init__(self, args: List[str]) -> None:
|
||||||
self.args = args
|
self.args = args
|
||||||
self.logger = Logger(name=__name__).get_logger()
|
self.logging = Logger(name=__name__)
|
||||||
|
self.logger = self.logging.get_logger()
|
||||||
self.config = self._load_config()
|
self.config = self._load_config()
|
||||||
self.show_info()
|
self.show_info()
|
||||||
|
|
||||||
@ -35,16 +36,24 @@ class Configuration(object):
|
|||||||
config.update({'strategy': self.args.strategy})
|
config.update({'strategy': self.args.strategy})
|
||||||
|
|
||||||
# Add dynamic_whitelist if found
|
# Add dynamic_whitelist if found
|
||||||
if self.args.dynamic_whitelist:
|
if 'dynamic_whitelist' in self.args and self.args.dynamic_whitelist:
|
||||||
config.update({'dynamic_whitelist': self.args.dynamic_whitelist})
|
config.update({'dynamic_whitelist': self.args.dynamic_whitelist})
|
||||||
|
|
||||||
# Add dry_run_db if found and the bot in dry run
|
# Add dry_run_db if found and the bot in dry run
|
||||||
if self.args.dry_run_db and config.get('dry_run', False):
|
if self.args.dry_run_db and config.get('dry_run', False):
|
||||||
config.update({'dry_run_db': True})
|
config.update({'dry_run_db': True})
|
||||||
|
|
||||||
# Load Backtesting / Hyperopt
|
# Log level
|
||||||
|
if 'loglevel' in self.args and self.args.loglevel:
|
||||||
|
config.update({'loglevel': self.args.loglevel})
|
||||||
|
self.logging.set_level(self.args.loglevel)
|
||||||
|
|
||||||
|
# Load Backtesting
|
||||||
config = self._load_backtesting_config(config)
|
config = self._load_backtesting_config(config)
|
||||||
|
|
||||||
|
# Load Hyperopt
|
||||||
|
config = self._load_hyperopt_config(config)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def _load_config_file(self, path: str) -> Dict[str, Any]:
|
def _load_config_file(self, path: str) -> Dict[str, Any]:
|
||||||
@ -64,7 +73,7 @@ class Configuration(object):
|
|||||||
|
|
||||||
def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Extract information for sys.argv and load Backtesting and Hyperopt configuration
|
Extract information for sys.argv and load Backtesting configuration
|
||||||
:return: configuration as dictionary
|
:return: configuration as dictionary
|
||||||
"""
|
"""
|
||||||
# If -i/--ticker-interval is used we override the configuration parameter
|
# If -i/--ticker-interval is used we override the configuration parameter
|
||||||
@ -107,6 +116,24 @@ class Configuration(object):
|
|||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
def _load_hyperopt_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Extract information for sys.argv and load Hyperopt configuration
|
||||||
|
:return: configuration as dictionary
|
||||||
|
"""
|
||||||
|
# If --realistic-simulation is used we add it to the configuration
|
||||||
|
if 'epochs' in self.args and self.args.epochs:
|
||||||
|
config.update({'epochs': self.args.epochs})
|
||||||
|
self.logger.info('Parameter --epochs detected ...')
|
||||||
|
self.logger.info('Will run Hyperopt with for %s epochs ...', config.get('epochs'))
|
||||||
|
|
||||||
|
# If --mongodb is used we add it to the configuration
|
||||||
|
if 'mongodb' in self.args and self.args.mongodb:
|
||||||
|
config.update({'mongodb': self.args.mongodb})
|
||||||
|
self.logger.info('Parameter --use-mongodb detected ...')
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
def _validate_config(self, conf: Dict[str, Any]) -> Dict[str, Any]:
|
def _validate_config(self, conf: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Validate the configuration follow the Config Schema
|
Validate the configuration follow the Config Schema
|
||||||
|
@ -19,9 +19,12 @@ class Logger(object):
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self.name = name
|
self.name = name
|
||||||
self.level = level
|
|
||||||
self.logger = None
|
self.logger = None
|
||||||
|
|
||||||
|
if level is None:
|
||||||
|
level = logging.INFO
|
||||||
|
self.level = level
|
||||||
|
|
||||||
self._init_logger()
|
self._init_logger()
|
||||||
|
|
||||||
def _init_logger(self) -> None:
|
def _init_logger(self) -> None:
|
||||||
|
@ -5,7 +5,6 @@ This module contains the backtesting logic
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Dict, Tuple, Any
|
from typing import Dict, Tuple, Any
|
||||||
import logging
|
|
||||||
import arrow
|
import arrow
|
||||||
from pandas import DataFrame, Series
|
from pandas import DataFrame, Series
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
@ -20,6 +19,7 @@ from freqtrade.logger import Logger
|
|||||||
from freqtrade.misc import file_dump_json
|
from freqtrade.misc import file_dump_json
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
|
|
||||||
|
from memory_profiler import profile
|
||||||
|
|
||||||
class Backtesting(object):
|
class Backtesting(object):
|
||||||
"""
|
"""
|
||||||
@ -30,7 +30,9 @@ class Backtesting(object):
|
|||||||
backtesting.start()
|
backtesting.start()
|
||||||
"""
|
"""
|
||||||
def __init__(self, config: Dict[str, Any]) -> None:
|
def __init__(self, config: Dict[str, Any]) -> None:
|
||||||
self.logging = Logger(name=__name__)
|
|
||||||
|
# Init the logger
|
||||||
|
self.logging = Logger(name=__name__, level=config['loglevel'])
|
||||||
self.logger = self.logging.get_logger()
|
self.logger = self.logging.get_logger()
|
||||||
|
|
||||||
self.config = config
|
self.config = config
|
||||||
@ -219,6 +221,7 @@ class Backtesting(object):
|
|||||||
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration']
|
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration']
|
||||||
return DataFrame.from_records(trades, columns=labels)
|
return DataFrame.from_records(trades, columns=labels)
|
||||||
|
|
||||||
|
@profile(precision=10)
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
"""
|
"""
|
||||||
Run a backtesting end-to-end
|
Run a backtesting end-to-end
|
||||||
@ -246,10 +249,14 @@ class Backtesting(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
max_open_trades = self.config.get('max_open_trades', 0)
|
max_open_trades = self.config.get('max_open_trades', 0)
|
||||||
|
|
||||||
preprocessed = self.tickerdata_to_dataframe(data)
|
preprocessed = self.tickerdata_to_dataframe(data)
|
||||||
|
|
||||||
# Print timeframe
|
# Print timeframe
|
||||||
min_date, max_date = self.get_timeframe(preprocessed)
|
min_date, max_date = self.get_timeframe(preprocessed)
|
||||||
|
|
||||||
|
import pprint
|
||||||
|
pprint.pprint(min_date)
|
||||||
|
pprint.pprint(max_date)
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
'Measuring data from %s up to %s (%s days)..',
|
'Measuring data from %s up to %s (%s days)..',
|
||||||
min_date.isoformat(),
|
min_date.isoformat(),
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ import math
|
|||||||
from typing import List
|
from typing import List
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
from arrow import Arrow
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from freqtrade import optimize
|
from freqtrade import optimize
|
||||||
from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration
|
from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration
|
||||||
@ -255,6 +256,25 @@ def test_backtesting_init(default_conf) -> None:
|
|||||||
assert callable(backtesting.populate_sell_trend)
|
assert callable(backtesting.populate_sell_trend)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tickerdata_to_dataframe(default_conf) -> None:
|
||||||
|
"""
|
||||||
|
Test Backtesting.tickerdata_to_dataframe() method
|
||||||
|
"""
|
||||||
|
|
||||||
|
timerange = ((None, 'line'), None, -100)
|
||||||
|
tick = optimize.load_tickerdata_file(None, 'BTC_UNITEST', 1, timerange=timerange)
|
||||||
|
tickerlist = {'BTC_UNITEST': tick}
|
||||||
|
|
||||||
|
backtesting = _BACKTESTING
|
||||||
|
data = backtesting.tickerdata_to_dataframe(tickerlist)
|
||||||
|
assert len(data['BTC_UNITEST']) == 100
|
||||||
|
|
||||||
|
# Load Analyze to compare the result between Backtesting function and Analyze are the same
|
||||||
|
analyze = Analyze(default_conf)
|
||||||
|
data2 = analyze.tickerdata_to_dataframe(tickerlist)
|
||||||
|
assert data['BTC_UNITEST'].equals(data2['BTC_UNITEST'])
|
||||||
|
|
||||||
|
|
||||||
def test_get_timeframe() -> None:
|
def test_get_timeframe() -> None:
|
||||||
"""
|
"""
|
||||||
Test Backtesting.get_timeframe() method
|
Test Backtesting.get_timeframe() method
|
||||||
@ -308,8 +328,18 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
|||||||
"""
|
"""
|
||||||
Test Backtesting.start() method
|
Test Backtesting.start() method
|
||||||
"""
|
"""
|
||||||
mocker.patch.multiple('freqtrade.optimize', load_data=mocked_load_data)
|
def get_timeframe(input1, input2):
|
||||||
mocker.patch('freqtrade.exchange.get_ticker_history', MagicMock)
|
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock())
|
||||||
|
mocker.patch('freqtrade.optimize.load_data', mocked_load_data)
|
||||||
|
mocker.patch('freqtrade.exchange.get_ticker_history')
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.optimize.backtesting.Backtesting',
|
||||||
|
backtest=MagicMock(),
|
||||||
|
_generate_text_table=MagicMock(return_value='1'),
|
||||||
|
get_timeframe=get_timeframe,
|
||||||
|
)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['exchange']['pair_whitelist'] = ['BTC_UNITEST']
|
conf['exchange']['pair_whitelist'] = ['BTC_UNITEST']
|
||||||
|
@ -1,117 +1,108 @@
|
|||||||
# pragma pylint: disable=missing-docstring,W0212,C0103
|
# pragma pylint: disable=missing-docstring,W0212,C0103
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
from freqtrade.optimize.hyperopt import calculate_loss, TARGET_TRADES, EXPECTED_MAX_PROFIT, start, \
|
#from freqtrade.optimize.hyperopt import EXPECTED_MAX_PROFIT, start, \
|
||||||
log_results, save_trials, read_trials, generate_roi_table
|
# log_results, save_trials, read_trials, generate_roi_table
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from freqtrade.optimize.hyperopt import Hyperopt, start
|
||||||
|
import freqtrade.tests.conftest as tt # test tools
|
||||||
|
|
||||||
|
|
||||||
def test_loss_calculation_prefer_correct_trade_count():
|
# Avoid to reinit the same object again and again
|
||||||
correct = calculate_loss(1, TARGET_TRADES, 20)
|
_HYPEROPT = Hyperopt(tt.default_conf())
|
||||||
over = calculate_loss(1, TARGET_TRADES + 100, 20)
|
|
||||||
under = calculate_loss(1, TARGET_TRADES - 100, 20)
|
|
||||||
assert over > correct
|
|
||||||
assert under > correct
|
|
||||||
|
|
||||||
|
|
||||||
def test_loss_calculation_prefer_shorter_trades():
|
# Functions for recurrent object patching
|
||||||
shorter = calculate_loss(1, 100, 20)
|
def create_trials(mocker) -> None:
|
||||||
longer = calculate_loss(1, 100, 30)
|
|
||||||
assert shorter < longer
|
|
||||||
|
|
||||||
|
|
||||||
def test_loss_calculation_has_limited_profit():
|
|
||||||
correct = calculate_loss(EXPECTED_MAX_PROFIT, TARGET_TRADES, 20)
|
|
||||||
over = calculate_loss(EXPECTED_MAX_PROFIT * 2, TARGET_TRADES, 20)
|
|
||||||
under = calculate_loss(EXPECTED_MAX_PROFIT / 2, TARGET_TRADES, 20)
|
|
||||||
assert over == correct
|
|
||||||
assert under > correct
|
|
||||||
|
|
||||||
|
|
||||||
def create_trials(mocker):
|
|
||||||
"""
|
"""
|
||||||
When creating trials, mock the hyperopt Trials so that *by default*
|
When creating trials, mock the hyperopt Trials so that *by default*
|
||||||
- we don't create any pickle'd files in the filesystem
|
- we don't create any pickle'd files in the filesystem
|
||||||
- we might have a pickle'd file so make sure that we return
|
- we might have a pickle'd file so make sure that we return
|
||||||
false when looking for it
|
false when looking for it
|
||||||
"""
|
"""
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.TRIALS_FILE',
|
_HYPEROPT.trials_file = os.path.join('freqtrade', 'tests', 'optimize','ut_trials.pickle')
|
||||||
return_value='freqtrade/tests/optimize/ut_trials.pickle')
|
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.os.path.exists',
|
mocker.patch('freqtrade.optimize.hyperopt.os.path.exists', return_value=False)
|
||||||
return_value=False)
|
mocker.patch('freqtrade.optimize.hyperopt.os.remove', return_value=True)
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.save_trials',
|
mocker.patch('freqtrade.optimize.hyperopt.pickle.dump', return_value=None)
|
||||||
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,
|
{
|
||||||
'result': 'foo',
|
'loss': 1,
|
||||||
'status': 'ok'
|
'result': 'foo',
|
||||||
}],
|
'status': 'ok'
|
||||||
|
}
|
||||||
|
],
|
||||||
best_trial={'misc': {'vals': {'adx': 999}}}
|
best_trial={'misc': {'vals': {'adx': 999}}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_start_calls_fmin(mocker):
|
# Unit tests
|
||||||
trials = create_trials(mocker)
|
def test_loss_calculation_prefer_correct_trade_count() -> None:
|
||||||
mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
|
"""
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.TRIALS', return_value=trials)
|
Test Hyperopt.calculate_loss()
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.sorted',
|
"""
|
||||||
return_value=trials.results)
|
hyperopt = _HYPEROPT
|
||||||
mocker.patch('freqtrade.optimize.preprocess')
|
|
||||||
mocker.patch('freqtrade.optimize.load_data')
|
|
||||||
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
|
||||||
|
|
||||||
args = mocker.Mock(epochs=1, config='config.json.example', mongodb=False,
|
correct = hyperopt.calculate_loss(1, hyperopt.target_trades, 20)
|
||||||
timerange=None)
|
over = hyperopt.calculate_loss(1, hyperopt.target_trades + 100, 20)
|
||||||
start(args)
|
under = hyperopt.calculate_loss(1, hyperopt.target_trades - 100, 20)
|
||||||
|
assert over > correct
|
||||||
mock_fmin.assert_called_once()
|
assert under > correct
|
||||||
|
|
||||||
|
|
||||||
def test_start_uses_mongotrials(mocker):
|
def test_loss_calculation_prefer_shorter_trades() -> None:
|
||||||
mock_mongotrials = mocker.patch('freqtrade.optimize.hyperopt.MongoTrials',
|
"""
|
||||||
return_value=create_trials(mocker))
|
Test Hyperopt.calculate_loss()
|
||||||
mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
|
"""
|
||||||
mocker.patch('freqtrade.optimize.load_data')
|
hyperopt = _HYPEROPT
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
|
||||||
|
|
||||||
args = mocker.Mock(epochs=1, config='config.json.example', mongodb=True,
|
shorter = hyperopt.calculate_loss(1, 100, 20)
|
||||||
timerange=None)
|
longer = hyperopt.calculate_loss(1, 100, 30)
|
||||||
start(args)
|
assert shorter < longer
|
||||||
|
|
||||||
mock_mongotrials.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
def test_log_results_if_loss_improves(mocker):
|
def test_loss_calculation_has_limited_profit() -> None:
|
||||||
logger = mocker.patch('freqtrade.optimize.hyperopt.logger.info')
|
hyperopt = _HYPEROPT
|
||||||
global CURRENT_BEST_LOSS
|
|
||||||
CURRENT_BEST_LOSS = 2
|
|
||||||
log_results({
|
|
||||||
'loss': 1,
|
|
||||||
'current_tries': 1,
|
|
||||||
'total_tries': 2,
|
|
||||||
'result': 'foo'
|
|
||||||
})
|
|
||||||
|
|
||||||
logger.assert_called_once()
|
correct = hyperopt.calculate_loss(hyperopt.expected_max_profit, hyperopt.target_trades, 20)
|
||||||
|
over = hyperopt.calculate_loss(hyperopt.expected_max_profit * 2, hyperopt.target_trades, 20)
|
||||||
|
under = hyperopt.calculate_loss(hyperopt.expected_max_profit / 2, hyperopt.target_trades, 20)
|
||||||
|
assert over == correct
|
||||||
|
assert under > correct
|
||||||
|
|
||||||
|
|
||||||
def test_no_log_if_loss_does_not_improve(mocker):
|
def test_log_results_if_loss_improves(caplog) -> None:
|
||||||
logger = mocker.patch('freqtrade.optimize.hyperopt.logger.info')
|
hyperopt = _HYPEROPT
|
||||||
global CURRENT_BEST_LOSS
|
hyperopt.current_best_loss = 2
|
||||||
CURRENT_BEST_LOSS = 2
|
hyperopt.log_results(
|
||||||
log_results({
|
{
|
||||||
'loss': 3,
|
'loss': 1,
|
||||||
})
|
'current_tries': 1,
|
||||||
|
'total_tries': 2,
|
||||||
assert not logger.called
|
'result': 'foo'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert tt.log_has(' 1/2: foo. Loss 1.00000', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_fmin_best_results(mocker, caplog):
|
def test_no_log_if_loss_does_not_improve(caplog) -> None:
|
||||||
caplog.set_level(logging.INFO)
|
hyperopt = _HYPEROPT
|
||||||
|
hyperopt.current_best_loss = 2
|
||||||
|
hyperopt.log_results(
|
||||||
|
{
|
||||||
|
'loss': 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert caplog.record_tuples == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_fmin_best_results(mocker, default_conf, caplog) -> None:
|
||||||
fmin_result = {
|
fmin_result = {
|
||||||
"macd_below_zero": 0,
|
"macd_below_zero": 0,
|
||||||
"adx": 1,
|
"adx": 1,
|
||||||
@ -136,38 +127,65 @@ def test_fmin_best_results(mocker, caplog):
|
|||||||
"roi_p3": 3,
|
"roi_p3": 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker))
|
conf = deepcopy(default_conf)
|
||||||
mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
|
conf.update({'config': 'config.json.example'})
|
||||||
mocker.patch('freqtrade.optimize.load_data')
|
conf.update({'epochs': 1})
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
|
conf.update({'timerange': None})
|
||||||
|
|
||||||
args = mocker.Mock(epochs=1, config='config.json.example',
|
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||||
timerange=None)
|
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
|
||||||
start(args)
|
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||||
|
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
|
||||||
|
|
||||||
|
hyperopt = Hyperopt(conf)
|
||||||
|
hyperopt.trials = create_trials(mocker)
|
||||||
|
hyperopt.tickerdata_to_dataframe = MagicMock()
|
||||||
|
|
||||||
|
hyperopt.start()
|
||||||
|
|
||||||
exists = [
|
exists = [
|
||||||
'Best parameters',
|
'Best parameters:',
|
||||||
'"adx": {\n "enabled": true,\n "value": 15.0\n },',
|
'"adx": {\n "enabled": true,\n "value": 15.0\n },',
|
||||||
|
'"fastd": {\n "enabled": true,\n "value": 40.0\n },',
|
||||||
'"green_candle": {\n "enabled": true\n },',
|
'"green_candle": {\n "enabled": true\n },',
|
||||||
|
'"macd_below_zero": {\n "enabled": false\n },',
|
||||||
'"mfi": {\n "enabled": false\n },',
|
'"mfi": {\n "enabled": false\n },',
|
||||||
|
'"over_sar": {\n "enabled": false\n },',
|
||||||
|
'"roi_p1": 1.0,',
|
||||||
|
'"roi_p2": 2.0,',
|
||||||
|
'"roi_p3": 3.0,',
|
||||||
|
'"roi_t1": 1.0,',
|
||||||
|
'"roi_t2": 2.0,',
|
||||||
|
'"roi_t3": 3.0,',
|
||||||
|
'"rsi": {\n "enabled": true,\n "value": 37.0\n },',
|
||||||
|
'"stoploss": -0.1,',
|
||||||
'"trigger": {\n "type": "faststoch10"\n },',
|
'"trigger": {\n "type": "faststoch10"\n },',
|
||||||
'"stoploss": -0.1',
|
'"uptrend_long_ema": {\n "enabled": true\n },',
|
||||||
|
'"uptrend_short_ema": {\n "enabled": false\n },',
|
||||||
|
'"uptrend_sma": {\n "enabled": false\n }',
|
||||||
|
'ROI table:\n{\'0\': 6.0, \'3.0\': 3.0, \'5.0\': 1.0, \'6.0\': 0}',
|
||||||
|
'Best Result:\nfoo'
|
||||||
]
|
]
|
||||||
|
|
||||||
for line in exists:
|
for line in exists:
|
||||||
assert line in caplog.text
|
assert line in caplog.text
|
||||||
|
|
||||||
|
|
||||||
def test_fmin_throw_value_error(mocker, caplog):
|
def test_fmin_throw_value_error(mocker, default_conf, caplog) -> None:
|
||||||
caplog.set_level(logging.INFO)
|
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker))
|
|
||||||
mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
|
|
||||||
mocker.patch('freqtrade.optimize.load_data')
|
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', side_effect=ValueError())
|
mocker.patch('freqtrade.optimize.hyperopt.fmin', side_effect=ValueError())
|
||||||
|
|
||||||
args = mocker.Mock(epochs=1, config='config.json.example',
|
conf = deepcopy(default_conf)
|
||||||
timerange=None)
|
conf.update({'config': 'config.json.example'})
|
||||||
start(args)
|
conf.update({'epochs': 1})
|
||||||
|
conf.update({'timerange': None})
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||||
|
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
|
||||||
|
|
||||||
|
hyperopt = Hyperopt(conf)
|
||||||
|
hyperopt.trials = create_trials(mocker)
|
||||||
|
hyperopt.tickerdata_to_dataframe = MagicMock()
|
||||||
|
|
||||||
|
hyperopt.start()
|
||||||
|
|
||||||
exists = [
|
exists = [
|
||||||
'Best Result:',
|
'Best Result:',
|
||||||
@ -179,68 +197,80 @@ def test_fmin_throw_value_error(mocker, caplog):
|
|||||||
assert line in caplog.text
|
assert line in caplog.text
|
||||||
|
|
||||||
|
|
||||||
def test_resuming_previous_hyperopt_results_succeeds(mocker):
|
def test_resuming_previous_hyperopt_results_succeeds(mocker, default_conf) -> None:
|
||||||
import freqtrade.optimize.hyperopt as hyperopt
|
|
||||||
trials = create_trials(mocker)
|
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,
|
|
||||||
timerange=None)
|
|
||||||
|
|
||||||
start(args)
|
conf = deepcopy(default_conf)
|
||||||
|
conf.update({'config': 'config.json.example'})
|
||||||
|
conf.update({'epochs': 1})
|
||||||
|
conf.update({'mongodb': False})
|
||||||
|
conf.update({'timerange': None})
|
||||||
|
|
||||||
|
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.Hyperopt.read_trials',
|
||||||
|
return_value=trials
|
||||||
|
)
|
||||||
|
mock_save = mocker.patch(
|
||||||
|
'freqtrade.optimize.hyperopt.Hyperopt.save_trials',
|
||||||
|
return_value=None
|
||||||
|
)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||||
|
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
|
||||||
|
|
||||||
|
hyperopt = Hyperopt(conf)
|
||||||
|
hyperopt.trials = trials
|
||||||
|
hyperopt.tickerdata_to_dataframe = MagicMock()
|
||||||
|
|
||||||
|
hyperopt.start()
|
||||||
|
|
||||||
mock_read.assert_called_once()
|
mock_read.assert_called_once()
|
||||||
mock_save.assert_called_once()
|
mock_save.assert_called_once()
|
||||||
|
|
||||||
current_tries = hyperopt._CURRENT_TRIES
|
current_tries = hyperopt.current_tries
|
||||||
total_tries = hyperopt.TOTAL_TRIES
|
total_tries = hyperopt.total_tries
|
||||||
|
|
||||||
assert current_tries == len(trials.results)
|
assert current_tries == len(trials.results)
|
||||||
assert total_tries == (current_tries + len(trials.results))
|
assert total_tries == (current_tries + len(trials.results))
|
||||||
|
|
||||||
|
|
||||||
def test_save_trials_saves_trials(mocker):
|
def test_save_trials_saves_trials(mocker, caplog) -> None:
|
||||||
|
create_trials(mocker)
|
||||||
|
mock_dump = mocker.patch('freqtrade.optimize.hyperopt.pickle.dump', return_value=None)
|
||||||
|
|
||||||
|
hyperopt = _HYPEROPT
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.open', return_value=hyperopt.trials_file)
|
||||||
|
|
||||||
|
hyperopt.save_trials()
|
||||||
|
|
||||||
|
assert tt.log_has(
|
||||||
|
'Saving Trials to \'freqtrade/tests/optimize/ut_trials.pickle\'',
|
||||||
|
caplog.record_tuples
|
||||||
|
)
|
||||||
|
mock_dump.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_trials_returns_trials_file(mocker, default_conf, caplog) -> None:
|
||||||
trials = create_trials(mocker)
|
trials = create_trials(mocker)
|
||||||
mock_dump = mocker.patch('freqtrade.optimize.hyperopt.pickle.dump',
|
mock_load = mocker.patch('freqtrade.optimize.hyperopt.pickle.load', return_value=trials)
|
||||||
return_value=None)
|
mock_open = mocker.patch('freqtrade.optimize.hyperopt.open', return_value=mock_load)
|
||||||
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)
|
hyperopt = _HYPEROPT
|
||||||
|
hyperopt_trial = hyperopt.read_trials()
|
||||||
|
assert tt.log_has(
|
||||||
def test_read_trials_returns_trials_file(mocker):
|
'Reading Trials from \'freqtrade/tests/optimize/ut_trials.pickle\'',
|
||||||
trials = create_trials(mocker)
|
caplog.record_tuples
|
||||||
mock_load = mocker.patch('freqtrade.optimize.hyperopt.pickle.load',
|
)
|
||||||
return_value=trials)
|
assert hyperopt_trial == trials
|
||||||
mock_open = mocker.patch('freqtrade.optimize.hyperopt.open',
|
|
||||||
return_value=mock_load)
|
|
||||||
|
|
||||||
assert read_trials() == trials
|
|
||||||
mock_open.assert_called_once()
|
mock_open.assert_called_once()
|
||||||
mock_load.assert_called_once()
|
mock_load.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
def test_roi_table_generation():
|
def test_roi_table_generation() -> None:
|
||||||
params = {
|
params = {
|
||||||
'roi_t1': 5,
|
'roi_t1': 5,
|
||||||
'roi_t2': 10,
|
'roi_t2': 10,
|
||||||
@ -249,4 +279,49 @@ def test_roi_table_generation():
|
|||||||
'roi_p2': 2,
|
'roi_p2': 2,
|
||||||
'roi_p3': 3,
|
'roi_p3': 3,
|
||||||
}
|
}
|
||||||
assert generate_roi_table(params) == {'0': 6, '15': 3, '25': 1, '30': 0}
|
|
||||||
|
hyperopt = _HYPEROPT
|
||||||
|
assert hyperopt.generate_roi_table(params) == {'0': 6, '15': 3, '25': 1, '30': 0}
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_calls_fmin(mocker, default_conf) -> None:
|
||||||
|
trials = create_trials(mocker)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||||
|
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||||
|
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf.update({'config': 'config.json.example'})
|
||||||
|
conf.update({'epochs': 1})
|
||||||
|
conf.update({'mongodb': False})
|
||||||
|
conf.update({'timerange': None})
|
||||||
|
|
||||||
|
hyperopt = Hyperopt(conf)
|
||||||
|
hyperopt.trials = trials
|
||||||
|
hyperopt.tickerdata_to_dataframe = MagicMock()
|
||||||
|
|
||||||
|
hyperopt.start()
|
||||||
|
mock_fmin.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_uses_mongotrials(mocker, default_conf) -> None:
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||||
|
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||||
|
mock_mongotrials = mocker.patch(
|
||||||
|
'freqtrade.optimize.hyperopt.MongoTrials',
|
||||||
|
return_value=create_trials(mocker)
|
||||||
|
)
|
||||||
|
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf.update({'config': 'config.json.example'})
|
||||||
|
conf.update({'epochs': 1})
|
||||||
|
conf.update({'mongodb': True})
|
||||||
|
conf.update({'timerange': None})
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||||
|
|
||||||
|
hyperopt = Hyperopt(conf)
|
||||||
|
hyperopt.tickerdata_to_dataframe = MagicMock()
|
||||||
|
|
||||||
|
hyperopt.start()
|
||||||
|
mock_mongotrials.assert_called_once()
|
||||||
|
mock_fmin.assert_called_once()
|
||||||
|
@ -6,7 +6,6 @@ import logging
|
|||||||
import uuid
|
import uuid
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from freqtrade import optimize
|
from freqtrade import optimize
|
||||||
from freqtrade.analyze import Analyze
|
|
||||||
from freqtrade.optimize.__init__ import make_testdata_path, download_pairs,\
|
from freqtrade.optimize.__init__ import make_testdata_path, download_pairs,\
|
||||||
download_backtesting_testdata, load_tickerdata_file, trim_tickerlist
|
download_backtesting_testdata, load_tickerdata_file, trim_tickerlist
|
||||||
from freqtrade.misc import file_dump_json
|
from freqtrade.misc import file_dump_json
|
||||||
@ -220,16 +219,6 @@ def test_init(default_conf, mocker) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_tickerdata_to_dataframe(default_conf) -> None:
|
|
||||||
analyze = Analyze(default_conf)
|
|
||||||
|
|
||||||
timerange = ((None, 'line'), None, -100)
|
|
||||||
tick = load_tickerdata_file(None, 'BTC_UNITEST', 1, timerange=timerange)
|
|
||||||
tickerlist = {'BTC_UNITEST': tick}
|
|
||||||
data = analyze.tickerdata_to_dataframe(tickerlist)
|
|
||||||
assert len(data['BTC_UNITEST']) == 100
|
|
||||||
|
|
||||||
|
|
||||||
def test_trim_tickerlist() -> None:
|
def test_trim_tickerlist() -> None:
|
||||||
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
||||||
ticker_list = json.load(data_file)
|
ticker_list = json.load(data_file)
|
||||||
|
@ -10,8 +10,9 @@ import logging
|
|||||||
import arrow
|
import arrow
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
import freqtrade.tests.conftest as tt # test tools
|
|
||||||
from freqtrade.analyze import Analyze, SignalType
|
from freqtrade.analyze import Analyze, SignalType
|
||||||
|
from freqtrade.optimize.__init__ import load_tickerdata_file
|
||||||
|
import freqtrade.tests.conftest as tt # test tools
|
||||||
|
|
||||||
|
|
||||||
# Avoid to reinit the same object again and again
|
# Avoid to reinit the same object again and again
|
||||||
@ -173,3 +174,16 @@ def test_parse_ticker_dataframe(ticker_history, ticker_history_without_bv):
|
|||||||
# Test file without BV data
|
# Test file without BV data
|
||||||
dataframe = Analyze.parse_ticker_dataframe(ticker_history_without_bv)
|
dataframe = Analyze.parse_ticker_dataframe(ticker_history_without_bv)
|
||||||
assert dataframe.columns.tolist() == columns
|
assert dataframe.columns.tolist() == columns
|
||||||
|
|
||||||
|
|
||||||
|
def test_tickerdata_to_dataframe(default_conf) -> None:
|
||||||
|
"""
|
||||||
|
Test Analyze.tickerdata_to_dataframe() method
|
||||||
|
"""
|
||||||
|
analyze = Analyze(default_conf)
|
||||||
|
|
||||||
|
timerange = ((None, 'line'), None, -100)
|
||||||
|
tick = load_tickerdata_file(None, 'BTC_UNITEST', 1, timerange=timerange)
|
||||||
|
tickerlist = {'BTC_UNITEST': tick}
|
||||||
|
data = analyze.tickerdata_to_dataframe(tickerlist)
|
||||||
|
assert len(data['BTC_UNITEST']) == 100
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
|
|
||||||
import pandas
|
import pandas
|
||||||
import freqtrade.optimize
|
from freqtrade.optimize import load_data
|
||||||
from freqtrade import analyze
|
from freqtrade.analyze import Analyze, SignalType
|
||||||
|
|
||||||
_pairs = ['BTC_ETH']
|
_pairs = ['BTC_ETH']
|
||||||
|
|
||||||
|
|
||||||
def load_dataframe_pair(pairs):
|
def load_dataframe_pair(pairs):
|
||||||
ld = freqtrade.optimize.load_data(None, ticker_interval=5, pairs=pairs)
|
ld = load_data(None, ticker_interval=5, pairs=pairs)
|
||||||
assert isinstance(ld, dict)
|
assert isinstance(ld, dict)
|
||||||
assert isinstance(pairs[0], str)
|
assert isinstance(pairs[0], str)
|
||||||
dataframe = ld[pairs[0]]
|
dataframe = ld[pairs[0]]
|
||||||
|
|
||||||
|
analyze = Analyze({'strategy': 'default_strategy'})
|
||||||
dataframe = analyze.analyze_ticker(dataframe)
|
dataframe = analyze.analyze_ticker(dataframe)
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user