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(),
|
||||||
|
@ -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 json
|
||||||
import logging
|
import logging
|
||||||
@ -19,49 +22,56 @@ from hyperopt.mongoexp import MongoTrials
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
# Monkey patch config
|
from freqtrade.configuration import Configuration
|
||||||
from freqtrade import main # noqa; noqa
|
from freqtrade.optimize import load_data
|
||||||
from freqtrade import exchange, misc, optimize
|
from freqtrade.arguments import Arguments
|
||||||
from freqtrade.exchange import Bittrex
|
from freqtrade.optimize.backtesting import Backtesting, setup_configuration
|
||||||
from freqtrade.misc import load_config
|
from freqtrade.logger import Logger
|
||||||
from freqtrade.optimize import backtesting
|
|
||||||
from freqtrade.optimize.backtesting import backtest
|
|
||||||
from freqtrade.strategy.strategy import Strategy
|
|
||||||
from user_data.hyperopt_conf import hyperopt_optimize_conf
|
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
|
To run a backtest:
|
||||||
TARGET_TRADES = 600
|
hyperopt = Hyperopt(config)
|
||||||
TOTAL_TRIES = 0
|
hyperopt.start()
|
||||||
_CURRENT_TRIES = 0
|
"""
|
||||||
CURRENT_BEST_LOSS = 100
|
def __init__(self, config: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
# max average trade duration in minutes
|
super().__init__(config)
|
||||||
# if eval ends with higher value, we consider it a failed eval
|
|
||||||
MAX_ACCEPTED_TRADE_DURATION = 300
|
|
||||||
|
|
||||||
# this is expexted avg profit * expected trade count
|
# Rename the logging to display Hyperopt file instead of Backtesting
|
||||||
# for example 3.5%, 1100 trades, EXPECTED_MAX_PROFIT = 3.85
|
self.logging = Logger(name=__name__, level=config['loglevel'])
|
||||||
# check that the reported Σ% values do not exceed this!
|
self.logger = self.logging.get_logger()
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
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
|
Adds several different TA indicators to the given DataFrame
|
||||||
"""
|
"""
|
||||||
@ -180,52 +190,61 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
|||||||
|
|
||||||
return 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):
|
def read_trials(self) -> Trials:
|
||||||
"""Save hyperopt trials to file"""
|
"""
|
||||||
logger.info('Saving Trials to \'{}\''.format(trials_path))
|
Read hyperopt trials file
|
||||||
pickle.dump(trials, open(trials_path, 'wb'))
|
"""
|
||||||
|
self.logger.info('Reading Trials from \'%s\'', self.trials_file)
|
||||||
|
trials = pickle.load(open(self.trials_file, 'rb'))
|
||||||
def read_trials(trials_path=TRIALS_FILE):
|
os.remove(self.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
|
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):
|
def log_results(self, results) -> None:
|
||||||
vals = json.dumps(trials.best_trial['misc']['vals'], indent=4)
|
"""
|
||||||
results = trials.best_trial['result']['result']
|
Log results if it is better than any previous evaluation
|
||||||
logger.info('Best result:\n%s\nwith values:\n%s', results, vals)
|
"""
|
||||||
|
if results['loss'] < self.current_best_loss:
|
||||||
|
self.current_best_loss = results['loss']
|
||||||
def log_results(results):
|
log_msg = '{:5d}/{}: {}. Loss {:.5f}'.format(
|
||||||
""" 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(
|
|
||||||
results['current_tries'],
|
results['current_tries'],
|
||||||
results['total_tries'],
|
results['total_tries'],
|
||||||
results['result'],
|
results['result'],
|
||||||
results['loss']))
|
results['loss']
|
||||||
|
)
|
||||||
|
self.logger.info(log_msg)
|
||||||
else:
|
else:
|
||||||
print('.', end='')
|
print('.', end='')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def calculate_loss(self, total_profit: float, trade_count: int, trade_duration: float) -> float:
|
||||||
def calculate_loss(total_profit: float, trade_count: int, trade_duration: float):
|
"""
|
||||||
""" objective function, returns smaller number for more optimal results """
|
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)
|
trade_loss = 1 - 0.25 * exp(-(trade_count - self.target_trades) ** 2 / 10 ** 5.8)
|
||||||
duration_loss = 0.4 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1)
|
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
|
return trade_loss + profit_loss + duration_loss
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def generate_roi_table(params) -> Dict[str, float]:
|
def generate_roi_table(params) -> Dict[str, float]:
|
||||||
|
"""
|
||||||
|
Generate the ROI table thqt will be used by Hyperopt
|
||||||
|
"""
|
||||||
roi_table = {}
|
roi_table = {}
|
||||||
roi_table["0"] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
|
roi_table["0"] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
|
||||||
roi_table[str(params['roi_t3'])] = params['roi_p1'] + params['roi_p2']
|
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
|
return roi_table
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def roi_space() -> Dict[str, Any]:
|
def roi_space() -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Values to search for each ROI steps
|
||||||
|
"""
|
||||||
return {
|
return {
|
||||||
'roi_t1': hp.quniform('roi_t1', 10, 120, 20),
|
'roi_t1': hp.quniform('roi_t1', 10, 120, 20),
|
||||||
'roi_t2': hp.quniform('roi_t2', 10, 60, 15),
|
'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),
|
'roi_p3': hp.quniform('roi_p3', 0.01, 0.20, 0.01),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def stoploss_space() -> Dict[str, Any]:
|
def stoploss_space() -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Stoploss Value to search
|
||||||
|
"""
|
||||||
return {
|
return {
|
||||||
'stoploss': hp.quniform('stoploss', -0.5, -0.02, 0.02),
|
'stoploss': hp.quniform('stoploss', -0.5, -0.02, 0.02),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def indicator_space() -> Dict[str, Any]:
|
def indicator_space() -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Define your Hyperopt space for searching strategy parameters
|
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]:
|
@staticmethod
|
||||||
return {**indicator_space(), **roi_space(), **stoploss_space()}
|
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||||
|
|
||||||
|
|
||||||
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
|
||||||
"""
|
"""
|
||||||
Define the buy strategy parameters to be used by hyperopt
|
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
|
return populate_buy_trend
|
||||||
|
|
||||||
|
def optimizer(self, params) -> Dict:
|
||||||
def optimizer(params):
|
|
||||||
global _CURRENT_TRIES
|
|
||||||
|
|
||||||
if 'roi_t1' in params:
|
if 'roi_t1' in params:
|
||||||
strategy = Strategy()
|
self.analyze.strategy.minimal_roi = self.generate_roi_table(params)
|
||||||
strategy.minimal_roi = 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'],
|
results = self.backtest(
|
||||||
'processed': PROCESSED,
|
{
|
||||||
'stoploss': params['stoploss']})
|
'stake_amount': self.config['stake_amount'],
|
||||||
result_explanation = format_results(results)
|
'processed': self.processed,
|
||||||
|
'stoploss': params['stoploss']
|
||||||
|
}
|
||||||
|
)
|
||||||
|
result_explanation = self.format_results(results)
|
||||||
|
|
||||||
total_profit = results.profit_percent.sum()
|
total_profit = results.profit_percent.sum()
|
||||||
trade_count = len(results.index)
|
trade_count = len(results.index)
|
||||||
trade_duration = results.duration.mean() * 5
|
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='')
|
print('.', end='')
|
||||||
return {
|
return {
|
||||||
'status': STATUS_FAIL,
|
'status': STATUS_FAIL,
|
||||||
'loss': float('inf')
|
'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,
|
'loss': loss,
|
||||||
'current_tries': _CURRENT_TRIES,
|
'current_tries': self.current_tries,
|
||||||
'total_tries': TOTAL_TRIES,
|
'total_tries': self.total_tries,
|
||||||
'result': result_explanation,
|
'result': result_explanation,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'loss': loss,
|
'loss': loss,
|
||||||
@ -432,8 +466,11 @@ def optimizer(params):
|
|||||||
'result': result_explanation,
|
'result': result_explanation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def format_results(results: DataFrame):
|
def format_results(results: DataFrame) -> str:
|
||||||
|
"""
|
||||||
|
Return the format result in a string
|
||||||
|
"""
|
||||||
return ('{:6d} trades. Avg profit {: 5.2f}%. '
|
return ('{:6d} trades. Avg profit {: 5.2f}%. '
|
||||||
'Total profit {: 11.8f} BTC ({:.4f}Σ%). Avg duration {:5.1f} mins.').format(
|
'Total profit {: 11.8f} BTC ({:.4f}Σ%). Avg duration {:5.1f} mins.').format(
|
||||||
len(results.index),
|
len(results.index),
|
||||||
@ -443,70 +480,56 @@ def format_results(results: DataFrame):
|
|||||||
results.duration.mean() * 5,
|
results.duration.mean() * 5,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
def start(args):
|
timerange = Arguments.parse_timerange(self.config.get('timerange'))
|
||||||
global TOTAL_TRIES, PROCESSED, TRIALS, _CURRENT_TRIES
|
data = load_data(
|
||||||
|
datadir=self.config.get('datadir'),
|
||||||
TOTAL_TRIES = args.epochs
|
pairs=self.config['exchange']['pair_whitelist'],
|
||||||
|
ticker_interval=self.ticker_interval,
|
||||||
exchange._API = Bittrex({'key': '', 'secret': ''})
|
timerange=timerange
|
||||||
|
|
||||||
# Initialize logger
|
|
||||||
logging.basicConfig(
|
|
||||||
level=args.loglevel,
|
|
||||||
format='\n%(message)s',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info('Using config: %s ...', args.config)
|
self.analyze.populate_indicators = Hyperopt.populate_indicators
|
||||||
config = load_config(args.config)
|
self.processed = self.tickerdata_to_dataframe(data)
|
||||||
pairs = config['exchange']['pair_whitelist']
|
|
||||||
|
|
||||||
# If -i/--ticker-interval is use we override the configuration parameter
|
if self.config.get('mongodb'):
|
||||||
# (that will override the strategy configuration)
|
self.logger.info('Using mongodb ...')
|
||||||
if args.ticker_interval:
|
self.logger.info(
|
||||||
config.update({'ticker_interval': args.ticker_interval})
|
'Start scripts/start-mongodb.sh and start-hyperopt-worker.sh manually!'
|
||||||
|
)
|
||||||
# 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!')
|
|
||||||
|
|
||||||
db_name = 'freqtrade_hyperopt'
|
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:
|
else:
|
||||||
logger.info('Preparing Trials..')
|
self.logger.info('Preparing Trials..')
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, self.signal_handler)
|
||||||
# read trials file if we have one
|
# read trials file if we have one
|
||||||
if os.path.exists(TRIALS_FILE):
|
if os.path.exists(self.trials_file):
|
||||||
TRIALS = read_trials()
|
self.trials = self.read_trials()
|
||||||
|
|
||||||
_CURRENT_TRIES = len(TRIALS.results)
|
self.current_tries = len(self.trials.results)
|
||||||
TOTAL_TRIES = TOTAL_TRIES + _CURRENT_TRIES
|
self.total_tries += self.current_tries
|
||||||
logger.info(
|
self.logger.info(
|
||||||
'Continuing with trials. Current: {}, Total: {}'
|
'Continuing with trials. Current: {}, Total: {}'
|
||||||
.format(_CURRENT_TRIES, TOTAL_TRIES))
|
.format(self.current_tries, self.total_tries)
|
||||||
|
|
||||||
try:
|
|
||||||
best_parameters = fmin(
|
|
||||||
fn=optimizer,
|
|
||||||
space=hyperopt_space(),
|
|
||||||
algo=tpe.suggest,
|
|
||||||
max_evals=TOTAL_TRIES,
|
|
||||||
trials=TRIALS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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']
|
best_result = results[0]['result']
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -517,23 +540,56 @@ def start(args):
|
|||||||
# Improve best parameter logging display
|
# Improve best parameter logging display
|
||||||
if best_parameters:
|
if best_parameters:
|
||||||
best_parameters = space_eval(
|
best_parameters = space_eval(
|
||||||
hyperopt_space(),
|
self.hyperopt_space(),
|
||||||
best_parameters
|
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:
|
if 'roi_t1' in best_parameters:
|
||||||
logger.info('ROI table:\n%s', generate_roi_table(best_parameters))
|
self.logger.info('ROI table:\n%s', self.generate_roi_table(best_parameters))
|
||||||
logger.info('Best Result:\n%s', best_result)
|
|
||||||
|
self.logger.info('Best Result:\n%s', best_result)
|
||||||
|
|
||||||
# Store trials result to file to resume next time
|
# 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):
|
self.save_trials()
|
||||||
"""Hyperopt SIGINT handler"""
|
self.log_trials_result()
|
||||||
logger.info('Hyperopt received {}'.format(signal.Signals(sig).name))
|
|
||||||
|
|
||||||
save_trials(TRIALS)
|
|
||||||
log_trials_result(TRIALS)
|
|
||||||
sys.exit(0)
|
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 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,
|
'loss': 1,
|
||||||
'result': 'foo',
|
'result': 'foo',
|
||||||
'status': 'ok'
|
'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
|
correct = hyperopt.calculate_loss(hyperopt.expected_max_profit, hyperopt.target_trades, 20)
|
||||||
log_results({
|
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,
|
'loss': 1,
|
||||||
'current_tries': 1,
|
'current_tries': 1,
|
||||||
'total_tries': 2,
|
'total_tries': 2,
|
||||||
'result': 'foo'
|
'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):
|
def test_no_log_if_loss_does_not_improve(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': 3,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
assert not logger.called
|
assert caplog.record_tuples == []
|
||||||
|
|
||||||
|
|
||||||
def test_fmin_best_results(mocker, caplog):
|
def test_fmin_best_results(mocker, default_conf, caplog) -> None:
|
||||||
caplog.set_level(logging.INFO)
|
|
||||||
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