Complete Backtesting and Hyperopt unit tests
This commit is contained in:
parent
f4ec073099
commit
6ef7b7d93d
@ -30,7 +30,7 @@ class Analyze(object):
|
||||
Init Analyze
|
||||
: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.strategy = Strategy(self.config)
|
||||
|
@ -19,7 +19,8 @@ class Configuration(object):
|
||||
"""
|
||||
def __init__(self, args: List[str]) -> None:
|
||||
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.show_info()
|
||||
|
||||
@ -35,16 +36,24 @@ class Configuration(object):
|
||||
config.update({'strategy': self.args.strategy})
|
||||
|
||||
# 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})
|
||||
|
||||
# Add dry_run_db if found and the bot in dry run
|
||||
if self.args.dry_run_db and config.get('dry_run', False):
|
||||
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)
|
||||
|
||||
# Load Hyperopt
|
||||
config = self._load_hyperopt_config(config)
|
||||
|
||||
return config
|
||||
|
||||
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]:
|
||||
"""
|
||||
Extract information for sys.argv and load Backtesting and Hyperopt configuration
|
||||
Extract information for sys.argv and load Backtesting configuration
|
||||
:return: configuration as dictionary
|
||||
"""
|
||||
# If -i/--ticker-interval is used we override the configuration parameter
|
||||
@ -107,6 +116,24 @@ class Configuration(object):
|
||||
|
||||
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]:
|
||||
"""
|
||||
Validate the configuration follow the Config Schema
|
||||
|
@ -19,9 +19,12 @@ class Logger(object):
|
||||
:return: None
|
||||
"""
|
||||
self.name = name
|
||||
self.level = level
|
||||
self.logger = None
|
||||
|
||||
if level is None:
|
||||
level = logging.INFO
|
||||
self.level = level
|
||||
|
||||
self._init_logger()
|
||||
|
||||
def _init_logger(self) -> None:
|
||||
|
@ -5,7 +5,6 @@ This module contains the backtesting logic
|
||||
"""
|
||||
|
||||
from typing import Dict, Tuple, Any
|
||||
import logging
|
||||
import arrow
|
||||
from pandas import DataFrame, Series
|
||||
from tabulate import tabulate
|
||||
@ -20,6 +19,7 @@ from freqtrade.logger import Logger
|
||||
from freqtrade.misc import file_dump_json
|
||||
from freqtrade.persistence import Trade
|
||||
|
||||
from memory_profiler import profile
|
||||
|
||||
class Backtesting(object):
|
||||
"""
|
||||
@ -30,7 +30,9 @@ class Backtesting(object):
|
||||
backtesting.start()
|
||||
"""
|
||||
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.config = config
|
||||
@ -219,6 +221,7 @@ class Backtesting(object):
|
||||
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration']
|
||||
return DataFrame.from_records(trades, columns=labels)
|
||||
|
||||
@profile(precision=10)
|
||||
def start(self) -> None:
|
||||
"""
|
||||
Run a backtesting end-to-end
|
||||
@ -246,10 +249,14 @@ class Backtesting(object):
|
||||
)
|
||||
|
||||
max_open_trades = self.config.get('max_open_trades', 0)
|
||||
|
||||
preprocessed = self.tickerdata_to_dataframe(data)
|
||||
|
||||
# Print timeframe
|
||||
min_date, max_date = self.get_timeframe(preprocessed)
|
||||
|
||||
import pprint
|
||||
pprint.pprint(min_date)
|
||||
pprint.pprint(max_date)
|
||||
self.logger.info(
|
||||
'Measuring data from %s up to %s (%s days)..',
|
||||
min_date.isoformat(),
|
||||
|
@ -1,5 +1,8 @@
|
||||
# pragma pylint: disable=missing-docstring,W0212,W0603
|
||||
# pragma pylint: disable=too-many-instance-attributes, pointless-string-statement
|
||||
|
||||
"""
|
||||
This module contains the hyperopt logic
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
@ -19,49 +22,56 @@ from hyperopt.mongoexp import MongoTrials
|
||||
from pandas import DataFrame
|
||||
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
# Monkey patch config
|
||||
from freqtrade import main # noqa; noqa
|
||||
from freqtrade import exchange, misc, optimize
|
||||
from freqtrade.exchange import Bittrex
|
||||
from freqtrade.misc import load_config
|
||||
from freqtrade.optimize import backtesting
|
||||
from freqtrade.optimize.backtesting import backtest
|
||||
from freqtrade.strategy.strategy import Strategy
|
||||
from freqtrade.configuration import Configuration
|
||||
from freqtrade.optimize import load_data
|
||||
from freqtrade.arguments import Arguments
|
||||
from freqtrade.optimize.backtesting import Backtesting, setup_configuration
|
||||
from freqtrade.logger import Logger
|
||||
from user_data.hyperopt_conf import hyperopt_optimize_conf
|
||||
|
||||
# Remove noisy log messages
|
||||
logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING)
|
||||
logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
class Hyperopt(Backtesting):
|
||||
"""
|
||||
Hyperopt class, this class contains all the logic to run a hyperopt simulation
|
||||
|
||||
# set TARGET_TRADES to suit your number concurrent trades so its realistic to the number of days
|
||||
TARGET_TRADES = 600
|
||||
TOTAL_TRIES = 0
|
||||
_CURRENT_TRIES = 0
|
||||
CURRENT_BEST_LOSS = 100
|
||||
To run a backtest:
|
||||
hyperopt = Hyperopt(config)
|
||||
hyperopt.start()
|
||||
"""
|
||||
def __init__(self, config: Dict[str, Any]) -> None:
|
||||
|
||||
# max average trade duration in minutes
|
||||
# if eval ends with higher value, we consider it a failed eval
|
||||
MAX_ACCEPTED_TRADE_DURATION = 300
|
||||
super().__init__(config)
|
||||
|
||||
# this is expexted avg profit * expected trade count
|
||||
# for example 3.5%, 1100 trades, EXPECTED_MAX_PROFIT = 3.85
|
||||
# check that the reported Σ% values do not exceed this!
|
||||
EXPECTED_MAX_PROFIT = 3.0
|
||||
|
||||
# Configuration and data used by hyperopt
|
||||
PROCESSED = None # optimize.preprocess(optimize.load_data())
|
||||
OPTIMIZE_CONFIG = hyperopt_optimize_conf()
|
||||
|
||||
# Hyperopt Trials
|
||||
TRIALS_FILE = os.path.join('user_data', 'hyperopt_trials.pickle')
|
||||
TRIALS = Trials()
|
||||
|
||||
main._CONF = OPTIMIZE_CONFIG
|
||||
# Rename the logging to display Hyperopt file instead of Backtesting
|
||||
self.logging = Logger(name=__name__, level=config['loglevel'])
|
||||
self.logger = self.logging.get_logger()
|
||||
|
||||
|
||||
def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
||||
# set TARGET_TRADES to suit your number concurrent trades so its realistic
|
||||
# to the number of days
|
||||
self.target_trades = 600
|
||||
self.total_tries = config.get('epochs', 0)
|
||||
self.current_tries = 0
|
||||
self.current_best_loss = 100
|
||||
|
||||
# max average trade duration in minutes
|
||||
# if eval ends with higher value, we consider it a failed eval
|
||||
self.max_accepted_trade_duration = 300
|
||||
|
||||
# this is expexted avg profit * expected trade count
|
||||
# for example 3.5%, 1100 trades, self.expected_max_profit = 3.85
|
||||
# check that the reported Σ% values do not exceed this!
|
||||
self.expected_max_profit = 3.0
|
||||
|
||||
# Configuration and data used by hyperopt
|
||||
self.processed = None
|
||||
|
||||
# Hyperopt Trials
|
||||
self.trials_file = os.path.join('user_data', 'hyperopt_trials.pickle')
|
||||
self.trials = Trials()
|
||||
|
||||
@staticmethod
|
||||
def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
||||
"""
|
||||
Adds several different TA indicators to the given DataFrame
|
||||
"""
|
||||
@ -180,52 +190,61 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
||||
|
||||
return dataframe
|
||||
|
||||
def save_trials(self) -> None:
|
||||
"""
|
||||
Save hyperopt trials to file
|
||||
"""
|
||||
self.logger.info('Saving Trials to \'%s\'', self.trials_file)
|
||||
pickle.dump(self.trials, open(self.trials_file, 'wb'))
|
||||
|
||||
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)
|
||||
def read_trials(self) -> Trials:
|
||||
"""
|
||||
Read hyperopt trials file
|
||||
"""
|
||||
self.logger.info('Reading Trials from \'%s\'', self.trials_file)
|
||||
trials = pickle.load(open(self.trials_file, 'rb'))
|
||||
os.remove(self.trials_file)
|
||||
return trials
|
||||
|
||||
def log_trials_result(self) -> None:
|
||||
"""
|
||||
Display Best hyperopt result
|
||||
"""
|
||||
vals = json.dumps(self.trials.best_trial['misc']['vals'], indent=4)
|
||||
results = self.trials.best_trial['result']['result']
|
||||
self.logger.info('Best result:\n%s\nwith values:\n%s', results, vals)
|
||||
|
||||
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):
|
||||
""" log results if it is better than any previous evaluation """
|
||||
global CURRENT_BEST_LOSS
|
||||
|
||||
if results['loss'] < CURRENT_BEST_LOSS:
|
||||
CURRENT_BEST_LOSS = results['loss']
|
||||
logger.info('{:5d}/{}: {}. Loss {:.5f}'.format(
|
||||
def log_results(self, results) -> None:
|
||||
"""
|
||||
Log results if it is better than any previous evaluation
|
||||
"""
|
||||
if results['loss'] < self.current_best_loss:
|
||||
self.current_best_loss = results['loss']
|
||||
log_msg = '{:5d}/{}: {}. Loss {:.5f}'.format(
|
||||
results['current_tries'],
|
||||
results['total_tries'],
|
||||
results['result'],
|
||||
results['loss']))
|
||||
results['loss']
|
||||
)
|
||||
self.logger.info(log_msg)
|
||||
else:
|
||||
print('.', end='')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def calculate_loss(total_profit: float, trade_count: int, trade_duration: float):
|
||||
""" objective function, returns smaller number for more optimal results """
|
||||
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
|
||||
profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT)
|
||||
duration_loss = 0.4 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1)
|
||||
def calculate_loss(self, total_profit: float, trade_count: int, trade_duration: float) -> float:
|
||||
"""
|
||||
Objective function, returns smaller number for more optimal results
|
||||
"""
|
||||
trade_loss = 1 - 0.25 * exp(-(trade_count - self.target_trades) ** 2 / 10 ** 5.8)
|
||||
profit_loss = max(0, 1 - total_profit / self.expected_max_profit)
|
||||
duration_loss = 0.4 * min(trade_duration / self.max_accepted_trade_duration, 1)
|
||||
return trade_loss + profit_loss + duration_loss
|
||||
|
||||
|
||||
def generate_roi_table(params) -> Dict[str, float]:
|
||||
@staticmethod
|
||||
def generate_roi_table(params) -> Dict[str, float]:
|
||||
"""
|
||||
Generate the ROI table thqt will be used by Hyperopt
|
||||
"""
|
||||
roi_table = {}
|
||||
roi_table["0"] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
|
||||
roi_table[str(params['roi_t3'])] = params['roi_p1'] + params['roi_p2']
|
||||
@ -234,8 +253,11 @@ def generate_roi_table(params) -> Dict[str, float]:
|
||||
|
||||
return roi_table
|
||||
|
||||
|
||||
def roi_space() -> Dict[str, Any]:
|
||||
@staticmethod
|
||||
def roi_space() -> Dict[str, Any]:
|
||||
"""
|
||||
Values to search for each ROI steps
|
||||
"""
|
||||
return {
|
||||
'roi_t1': hp.quniform('roi_t1', 10, 120, 20),
|
||||
'roi_t2': hp.quniform('roi_t2', 10, 60, 15),
|
||||
@ -245,14 +267,17 @@ def roi_space() -> Dict[str, Any]:
|
||||
'roi_p3': hp.quniform('roi_p3', 0.01, 0.20, 0.01),
|
||||
}
|
||||
|
||||
|
||||
def stoploss_space() -> Dict[str, Any]:
|
||||
@staticmethod
|
||||
def stoploss_space() -> Dict[str, Any]:
|
||||
"""
|
||||
Stoploss Value to search
|
||||
"""
|
||||
return {
|
||||
'stoploss': hp.quniform('stoploss', -0.5, -0.02, 0.02),
|
||||
}
|
||||
|
||||
|
||||
def indicator_space() -> Dict[str, Any]:
|
||||
@staticmethod
|
||||
def indicator_space() -> Dict[str, Any]:
|
||||
"""
|
||||
Define your Hyperopt space for searching strategy parameters
|
||||
"""
|
||||
@ -311,12 +336,19 @@ def indicator_space() -> Dict[str, Any]:
|
||||
]),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def hyperopt_space() -> Dict[str, Any]:
|
||||
"""
|
||||
Return the space to use during Hyperopt
|
||||
"""
|
||||
return {
|
||||
**Hyperopt.indicator_space(),
|
||||
**Hyperopt.roi_space(),
|
||||
**Hyperopt.stoploss_space()
|
||||
}
|
||||
|
||||
def hyperopt_space() -> Dict[str, Any]:
|
||||
return {**indicator_space(), **roi_space(), **stoploss_space()}
|
||||
|
||||
|
||||
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
@staticmethod
|
||||
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
"""
|
||||
Define the buy strategy parameters to be used by hyperopt
|
||||
"""
|
||||
@ -389,42 +421,44 @@ def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
|
||||
return populate_buy_trend
|
||||
|
||||
|
||||
def optimizer(params):
|
||||
global _CURRENT_TRIES
|
||||
|
||||
def optimizer(self, params) -> Dict:
|
||||
if 'roi_t1' in params:
|
||||
strategy = Strategy()
|
||||
strategy.minimal_roi = generate_roi_table(params)
|
||||
self.analyze.strategy.minimal_roi = self.generate_roi_table(params)
|
||||
|
||||
backtesting.populate_buy_trend = buy_strategy_generator(params)
|
||||
self.populate_buy_trend = self.buy_strategy_generator(params)
|
||||
|
||||
results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'],
|
||||
'processed': PROCESSED,
|
||||
'stoploss': params['stoploss']})
|
||||
result_explanation = format_results(results)
|
||||
results = self.backtest(
|
||||
{
|
||||
'stake_amount': self.config['stake_amount'],
|
||||
'processed': self.processed,
|
||||
'stoploss': params['stoploss']
|
||||
}
|
||||
)
|
||||
result_explanation = self.format_results(results)
|
||||
|
||||
total_profit = results.profit_percent.sum()
|
||||
trade_count = len(results.index)
|
||||
trade_duration = results.duration.mean() * 5
|
||||
|
||||
if trade_count == 0 or trade_duration > MAX_ACCEPTED_TRADE_DURATION:
|
||||
if trade_count == 0 or trade_duration > self.max_accepted_trade_duration:
|
||||
print('.', end='')
|
||||
return {
|
||||
'status': STATUS_FAIL,
|
||||
'loss': float('inf')
|
||||
}
|
||||
|
||||
loss = calculate_loss(total_profit, trade_count, trade_duration)
|
||||
loss = self.calculate_loss(total_profit, trade_count, trade_duration)
|
||||
|
||||
_CURRENT_TRIES += 1
|
||||
self.current_tries += 1
|
||||
|
||||
log_results({
|
||||
self.log_results(
|
||||
{
|
||||
'loss': loss,
|
||||
'current_tries': _CURRENT_TRIES,
|
||||
'total_tries': TOTAL_TRIES,
|
||||
'current_tries': self.current_tries,
|
||||
'total_tries': self.total_tries,
|
||||
'result': result_explanation,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
'loss': loss,
|
||||
@ -432,8 +466,11 @@ def optimizer(params):
|
||||
'result': result_explanation,
|
||||
}
|
||||
|
||||
|
||||
def format_results(results: DataFrame):
|
||||
@staticmethod
|
||||
def format_results(results: DataFrame) -> str:
|
||||
"""
|
||||
Return the format result in a string
|
||||
"""
|
||||
return ('{:6d} trades. Avg profit {: 5.2f}%. '
|
||||
'Total profit {: 11.8f} BTC ({:.4f}Σ%). Avg duration {:5.1f} mins.').format(
|
||||
len(results.index),
|
||||
@ -443,70 +480,56 @@ def format_results(results: DataFrame):
|
||||
results.duration.mean() * 5,
|
||||
)
|
||||
|
||||
|
||||
def start(args):
|
||||
global TOTAL_TRIES, PROCESSED, TRIALS, _CURRENT_TRIES
|
||||
|
||||
TOTAL_TRIES = args.epochs
|
||||
|
||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
||||
|
||||
# Initialize logger
|
||||
logging.basicConfig(
|
||||
level=args.loglevel,
|
||||
format='\n%(message)s',
|
||||
def start(self):
|
||||
timerange = Arguments.parse_timerange(self.config.get('timerange'))
|
||||
data = load_data(
|
||||
datadir=self.config.get('datadir'),
|
||||
pairs=self.config['exchange']['pair_whitelist'],
|
||||
ticker_interval=self.ticker_interval,
|
||||
timerange=timerange
|
||||
)
|
||||
|
||||
logger.info('Using config: %s ...', args.config)
|
||||
config = load_config(args.config)
|
||||
pairs = config['exchange']['pair_whitelist']
|
||||
self.analyze.populate_indicators = Hyperopt.populate_indicators
|
||||
self.processed = self.tickerdata_to_dataframe(data)
|
||||
|
||||
# If -i/--ticker-interval is use we override the configuration parameter
|
||||
# (that will override the strategy configuration)
|
||||
if args.ticker_interval:
|
||||
config.update({'ticker_interval': args.ticker_interval})
|
||||
|
||||
# init the strategy to use
|
||||
config.update({'strategy': args.strategy})
|
||||
strategy = Strategy()
|
||||
strategy.init(config)
|
||||
|
||||
timerange = misc.parse_timerange(args.timerange)
|
||||
data = optimize.load_data(args.datadir, pairs=pairs,
|
||||
ticker_interval=strategy.ticker_interval,
|
||||
timerange=timerange)
|
||||
optimize.populate_indicators = populate_indicators
|
||||
PROCESSED = optimize.tickerdata_to_dataframe(data)
|
||||
|
||||
if args.mongodb:
|
||||
logger.info('Using mongodb ...')
|
||||
logger.info('Start scripts/start-mongodb.sh and start-hyperopt-worker.sh manually!')
|
||||
if self.config.get('mongodb'):
|
||||
self.logger.info('Using mongodb ...')
|
||||
self.logger.info(
|
||||
'Start scripts/start-mongodb.sh and start-hyperopt-worker.sh manually!'
|
||||
)
|
||||
|
||||
db_name = 'freqtrade_hyperopt'
|
||||
TRIALS = MongoTrials('mongo://127.0.0.1:1234/{}/jobs'.format(db_name), exp_key='exp1')
|
||||
self.trials = MongoTrials(
|
||||
arg='mongo://127.0.0.1:1234/{}/jobs'.format(db_name),
|
||||
exp_key='exp1'
|
||||
)
|
||||
else:
|
||||
logger.info('Preparing Trials..')
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
self.logger.info('Preparing Trials..')
|
||||
signal.signal(signal.SIGINT, self.signal_handler)
|
||||
# read trials file if we have one
|
||||
if os.path.exists(TRIALS_FILE):
|
||||
TRIALS = read_trials()
|
||||
if os.path.exists(self.trials_file):
|
||||
self.trials = self.read_trials()
|
||||
|
||||
_CURRENT_TRIES = len(TRIALS.results)
|
||||
TOTAL_TRIES = TOTAL_TRIES + _CURRENT_TRIES
|
||||
logger.info(
|
||||
self.current_tries = len(self.trials.results)
|
||||
self.total_tries += self.current_tries
|
||||
self.logger.info(
|
||||
'Continuing with trials. Current: {}, Total: {}'
|
||||
.format(_CURRENT_TRIES, TOTAL_TRIES))
|
||||
|
||||
try:
|
||||
best_parameters = fmin(
|
||||
fn=optimizer,
|
||||
space=hyperopt_space(),
|
||||
algo=tpe.suggest,
|
||||
max_evals=TOTAL_TRIES,
|
||||
trials=TRIALS
|
||||
.format(self.current_tries, self.total_tries)
|
||||
)
|
||||
|
||||
results = sorted(TRIALS.results, key=itemgetter('loss'))
|
||||
try:
|
||||
# change the Logging format
|
||||
self.logging.set_format('\n%(message)s')
|
||||
|
||||
best_parameters = fmin(
|
||||
fn=self.optimizer,
|
||||
space=self.hyperopt_space(),
|
||||
algo=tpe.suggest,
|
||||
max_evals=self.total_tries,
|
||||
trials=self.trials
|
||||
)
|
||||
|
||||
results = sorted(self.trials.results, key=itemgetter('loss'))
|
||||
best_result = results[0]['result']
|
||||
|
||||
except ValueError:
|
||||
@ -517,23 +540,56 @@ def start(args):
|
||||
# Improve best parameter logging display
|
||||
if best_parameters:
|
||||
best_parameters = space_eval(
|
||||
hyperopt_space(),
|
||||
self.hyperopt_space(),
|
||||
best_parameters
|
||||
)
|
||||
|
||||
logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4))
|
||||
self.logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4))
|
||||
if 'roi_t1' in best_parameters:
|
||||
logger.info('ROI table:\n%s', generate_roi_table(best_parameters))
|
||||
logger.info('Best Result:\n%s', best_result)
|
||||
self.logger.info('ROI table:\n%s', self.generate_roi_table(best_parameters))
|
||||
|
||||
self.logger.info('Best Result:\n%s', best_result)
|
||||
|
||||
# Store trials result to file to resume next time
|
||||
save_trials(TRIALS)
|
||||
self.save_trials()
|
||||
|
||||
def signal_handler(self, sig, frame):
|
||||
"""
|
||||
Hyperopt SIGINT handler
|
||||
"""
|
||||
self.logger.info('Hyperopt received {}'.format(signal.Signals(sig).name))
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
"""Hyperopt SIGINT handler"""
|
||||
logger.info('Hyperopt received {}'.format(signal.Signals(sig).name))
|
||||
|
||||
save_trials(TRIALS)
|
||||
log_trials_result(TRIALS)
|
||||
self.save_trials()
|
||||
self.log_trials_result()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def start(args) -> None:
|
||||
"""
|
||||
Start Backtesting script
|
||||
:param args: Cli args from Arguments()
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# Remove noisy log messages
|
||||
logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING)
|
||||
logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
|
||||
|
||||
# Initialize logger
|
||||
logger = Logger(name=__name__).get_logger()
|
||||
logger.info('Starting freqtrade in Hyperopt mode')
|
||||
|
||||
# Initialize configuration
|
||||
#config = setup_configuration(args)
|
||||
|
||||
# Monkey patch of the configuration with hyperopt_conf.py
|
||||
configuration = Configuration(args)
|
||||
optimize_config = hyperopt_optimize_conf()
|
||||
config = configuration._load_backtesting_config(optimize_config)
|
||||
config = configuration._load_hyperopt_config(config)
|
||||
config['exchange']['key'] = ''
|
||||
config['exchange']['secret'] = ''
|
||||
|
||||
# Initialize backtesting object
|
||||
hyperopt = Hyperopt(config)
|
||||
hyperopt.start()
|
||||
|
@ -5,6 +5,7 @@ import math
|
||||
from typing import List
|
||||
from copy import deepcopy
|
||||
from unittest.mock import MagicMock
|
||||
from arrow import Arrow
|
||||
import pandas as pd
|
||||
from freqtrade import optimize
|
||||
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)
|
||||
|
||||
|
||||
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:
|
||||
"""
|
||||
Test Backtesting.get_timeframe() method
|
||||
@ -308,8 +328,18 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
||||
"""
|
||||
Test Backtesting.start() method
|
||||
"""
|
||||
mocker.patch.multiple('freqtrade.optimize', load_data=mocked_load_data)
|
||||
mocker.patch('freqtrade.exchange.get_ticker_history', MagicMock)
|
||||
def get_timeframe(input1, input2):
|
||||
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['exchange']['pair_whitelist'] = ['BTC_UNITEST']
|
||||
|
@ -1,117 +1,108 @@
|
||||
# pragma pylint: disable=missing-docstring,W0212,C0103
|
||||
import logging
|
||||
import os
|
||||
import pytest
|
||||
from copy import deepcopy
|
||||
|
||||
from freqtrade.optimize.hyperopt import calculate_loss, TARGET_TRADES, EXPECTED_MAX_PROFIT, start, \
|
||||
log_results, save_trials, read_trials, generate_roi_table
|
||||
#from freqtrade.optimize.hyperopt import EXPECTED_MAX_PROFIT, start, \
|
||||
# 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():
|
||||
correct = calculate_loss(1, TARGET_TRADES, 20)
|
||||
over = calculate_loss(1, TARGET_TRADES + 100, 20)
|
||||
under = calculate_loss(1, TARGET_TRADES - 100, 20)
|
||||
assert over > correct
|
||||
assert under > correct
|
||||
# Avoid to reinit the same object again and again
|
||||
_HYPEROPT = Hyperopt(tt.default_conf())
|
||||
|
||||
|
||||
def test_loss_calculation_prefer_shorter_trades():
|
||||
shorter = calculate_loss(1, 100, 20)
|
||||
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):
|
||||
# Functions for recurrent object patching
|
||||
def create_trials(mocker) -> None:
|
||||
"""
|
||||
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)
|
||||
_HYPEROPT.trials_file = os.path.join('freqtrade', 'tests', 'optimize','ut_trials.pickle')
|
||||
|
||||
mocker.patch('freqtrade.optimize.hyperopt.os.path.exists', return_value=False)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.os.remove', return_value=True)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.pickle.dump', return_value=None)
|
||||
|
||||
return mocker.Mock(
|
||||
results=[{
|
||||
results=[
|
||||
{
|
||||
'loss': 1,
|
||||
'result': 'foo',
|
||||
'status': 'ok'
|
||||
}],
|
||||
}
|
||||
],
|
||||
best_trial={'misc': {'vals': {'adx': 999}}}
|
||||
)
|
||||
|
||||
|
||||
def test_start_calls_fmin(mocker):
|
||||
trials = create_trials(mocker)
|
||||
mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
|
||||
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.load_data')
|
||||
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||
# Unit tests
|
||||
def test_loss_calculation_prefer_correct_trade_count() -> None:
|
||||
"""
|
||||
Test Hyperopt.calculate_loss()
|
||||
"""
|
||||
hyperopt = _HYPEROPT
|
||||
|
||||
args = mocker.Mock(epochs=1, config='config.json.example', mongodb=False,
|
||||
timerange=None)
|
||||
start(args)
|
||||
|
||||
mock_fmin.assert_called_once()
|
||||
correct = hyperopt.calculate_loss(1, hyperopt.target_trades, 20)
|
||||
over = hyperopt.calculate_loss(1, hyperopt.target_trades + 100, 20)
|
||||
under = hyperopt.calculate_loss(1, hyperopt.target_trades - 100, 20)
|
||||
assert over > correct
|
||||
assert under > correct
|
||||
|
||||
|
||||
def test_start_uses_mongotrials(mocker):
|
||||
mock_mongotrials = 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', return_value={})
|
||||
def test_loss_calculation_prefer_shorter_trades() -> None:
|
||||
"""
|
||||
Test Hyperopt.calculate_loss()
|
||||
"""
|
||||
hyperopt = _HYPEROPT
|
||||
|
||||
args = mocker.Mock(epochs=1, config='config.json.example', mongodb=True,
|
||||
timerange=None)
|
||||
start(args)
|
||||
|
||||
mock_mongotrials.assert_called_once()
|
||||
shorter = hyperopt.calculate_loss(1, 100, 20)
|
||||
longer = hyperopt.calculate_loss(1, 100, 30)
|
||||
assert shorter < longer
|
||||
|
||||
|
||||
def test_log_results_if_loss_improves(mocker):
|
||||
logger = mocker.patch('freqtrade.optimize.hyperopt.logger.info')
|
||||
global CURRENT_BEST_LOSS
|
||||
CURRENT_BEST_LOSS = 2
|
||||
log_results({
|
||||
def test_loss_calculation_has_limited_profit() -> None:
|
||||
hyperopt = _HYPEROPT
|
||||
|
||||
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_log_results_if_loss_improves(caplog) -> None:
|
||||
hyperopt = _HYPEROPT
|
||||
hyperopt.current_best_loss = 2
|
||||
hyperopt.log_results(
|
||||
{
|
||||
'loss': 1,
|
||||
'current_tries': 1,
|
||||
'total_tries': 2,
|
||||
'result': 'foo'
|
||||
})
|
||||
|
||||
logger.assert_called_once()
|
||||
}
|
||||
)
|
||||
assert tt.log_has(' 1/2: foo. Loss 1.00000', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_no_log_if_loss_does_not_improve(mocker):
|
||||
logger = mocker.patch('freqtrade.optimize.hyperopt.logger.info')
|
||||
global CURRENT_BEST_LOSS
|
||||
CURRENT_BEST_LOSS = 2
|
||||
log_results({
|
||||
def test_no_log_if_loss_does_not_improve(caplog) -> None:
|
||||
hyperopt = _HYPEROPT
|
||||
hyperopt.current_best_loss = 2
|
||||
hyperopt.log_results(
|
||||
{
|
||||
'loss': 3,
|
||||
})
|
||||
|
||||
assert not logger.called
|
||||
}
|
||||
)
|
||||
assert caplog.record_tuples == []
|
||||
|
||||
|
||||
def test_fmin_best_results(mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
def test_fmin_best_results(mocker, default_conf, caplog) -> None:
|
||||
fmin_result = {
|
||||
"macd_below_zero": 0,
|
||||
"adx": 1,
|
||||
@ -136,38 +127,65 @@ def test_fmin_best_results(mocker, caplog):
|
||||
"roi_p3": 3,
|
||||
}
|
||||
|
||||
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', return_value=fmin_result)
|
||||
conf = deepcopy(default_conf)
|
||||
conf.update({'config': 'config.json.example'})
|
||||
conf.update({'epochs': 1})
|
||||
conf.update({'timerange': None})
|
||||
|
||||
args = mocker.Mock(epochs=1, config='config.json.example',
|
||||
timerange=None)
|
||||
start(args)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
|
||||
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 = [
|
||||
'Best parameters',
|
||||
'Best parameters:',
|
||||
'"adx": {\n "enabled": true,\n "value": 15.0\n },',
|
||||
'"fastd": {\n "enabled": true,\n "value": 40.0\n },',
|
||||
'"green_candle": {\n "enabled": true\n },',
|
||||
'"macd_below_zero": {\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 },',
|
||||
'"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:
|
||||
assert line in caplog.text
|
||||
|
||||
|
||||
def test_fmin_throw_value_error(mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker))
|
||||
mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
|
||||
mocker.patch('freqtrade.optimize.load_data')
|
||||
def test_fmin_throw_value_error(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', side_effect=ValueError())
|
||||
|
||||
args = mocker.Mock(epochs=1, config='config.json.example',
|
||||
timerange=None)
|
||||
start(args)
|
||||
conf = deepcopy(default_conf)
|
||||
conf.update({'config': 'config.json.example'})
|
||||
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 = [
|
||||
'Best Result:',
|
||||
@ -179,68 +197,80 @@ def test_fmin_throw_value_error(mocker, caplog):
|
||||
assert line in caplog.text
|
||||
|
||||
|
||||
def test_resuming_previous_hyperopt_results_succeeds(mocker):
|
||||
import freqtrade.optimize.hyperopt as hyperopt
|
||||
def test_resuming_previous_hyperopt_results_succeeds(mocker, default_conf) -> None:
|
||||
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_save.assert_called_once()
|
||||
|
||||
current_tries = hyperopt._CURRENT_TRIES
|
||||
total_tries = hyperopt.TOTAL_TRIES
|
||||
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):
|
||||
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)
|
||||
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_load = mocker.patch('freqtrade.optimize.hyperopt.pickle.load', return_value=trials)
|
||||
mock_open = mocker.patch('freqtrade.optimize.hyperopt.open', return_value=mock_load)
|
||||
|
||||
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
|
||||
hyperopt = _HYPEROPT
|
||||
hyperopt_trial = hyperopt.read_trials()
|
||||
assert tt.log_has(
|
||||
'Reading Trials from \'freqtrade/tests/optimize/ut_trials.pickle\'',
|
||||
caplog.record_tuples
|
||||
)
|
||||
assert hyperopt_trial == trials
|
||||
mock_open.assert_called_once()
|
||||
mock_load.assert_called_once()
|
||||
|
||||
|
||||
def test_roi_table_generation():
|
||||
def test_roi_table_generation() -> None:
|
||||
params = {
|
||||
'roi_t1': 5,
|
||||
'roi_t2': 10,
|
||||
@ -249,4 +279,49 @@ def test_roi_table_generation():
|
||||
'roi_p2': 2,
|
||||
'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
|
||||
from shutil import copyfile
|
||||
from freqtrade import optimize
|
||||
from freqtrade.analyze import Analyze
|
||||
from freqtrade.optimize.__init__ import make_testdata_path, download_pairs,\
|
||||
download_backtesting_testdata, load_tickerdata_file, trim_tickerlist
|
||||
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:
|
||||
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
||||
ticker_list = json.load(data_file)
|
||||
|
@ -10,8 +10,9 @@ import logging
|
||||
import arrow
|
||||
from pandas import DataFrame
|
||||
|
||||
import freqtrade.tests.conftest as tt # test tools
|
||||
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
|
||||
@ -173,3 +174,16 @@ def test_parse_ticker_dataframe(ticker_history, ticker_history_without_bv):
|
||||
# Test file without BV data
|
||||
dataframe = Analyze.parse_ticker_dataframe(ticker_history_without_bv)
|
||||
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
|
||||
|
||||
import pandas
|
||||
import freqtrade.optimize
|
||||
from freqtrade import analyze
|
||||
from freqtrade.optimize import load_data
|
||||
from freqtrade.analyze import Analyze, SignalType
|
||||
|
||||
_pairs = ['BTC_ETH']
|
||||
|
||||
|
||||
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(pairs[0], str)
|
||||
dataframe = ld[pairs[0]]
|
||||
|
||||
analyze = Analyze({'strategy': 'default_strategy'})
|
||||
dataframe = analyze.analyze_ticker(dataframe)
|
||||
return dataframe
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user