Merge pull request #10919 from freqtrade/refactor/hyperopt

Restore hyperopt logging, refactor hyperopt layout
This commit is contained in:
Matthias 2024-11-12 18:13:02 +01:00 committed by GitHub
commit 4e5ae0af84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 650 additions and 438 deletions

View File

@ -913,6 +913,31 @@ Your epochs should therefore be aligned to the possible values - or you should b
After you run Hyperopt for the desired amount of epochs, you can later list all results for analysis, select only best or profitable once, and show the details for any of the epochs previously evaluated. This can be done with the `hyperopt-list` and `hyperopt-show` sub-commands. The usage of these sub-commands is described in the [Utils](utils.md#list-hyperopt-results) chapter. After you run Hyperopt for the desired amount of epochs, you can later list all results for analysis, select only best or profitable once, and show the details for any of the epochs previously evaluated. This can be done with the `hyperopt-list` and `hyperopt-show` sub-commands. The usage of these sub-commands is described in the [Utils](utils.md#list-hyperopt-results) chapter.
## Output debug messages from your strategy
If you want to output debug messages from your strategy, you can use the `logging` module. By default, Freqtrade will output all messages with a level of `INFO` or higher.
``` python
import logging
logger = logging.getLogger(__name__)
class MyAwesomeStrategy(IStrategy):
...
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
logger.info("This is a debug message")
...
```
!!! Note "using print"
Messages printed via `print()` will not be shown in the hyperopt output unless parallelism is disabled (`-j 1`).
It is recommended to use the `logging` module instead.
## Validate backtesting results ## Validate backtesting results
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected. Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
@ -920,6 +945,7 @@ Once the optimized strategy has been implemented into your strategy, you should
To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt for Backtesting. To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt for Backtesting.
### Why do my backtest results not match my hyperopt results? ### Why do my backtest results not match my hyperopt results?
Should results not match, check the following factors: Should results not match, check the following factors:
* You may have added parameters to hyperopt in `populate_indicators()` where they will be calculated only once **for all epochs**. If you are, for example, trying to optimise multiple SMA timeperiod values, the hyperoptable timeperiod parameter should be placed in `populate_entry_trend()` which is calculated every epoch. See [Optimizing an indicator parameter](https://www.freqtrade.io/en/stable/hyperopt/#optimizing-an-indicator-parameter). * You may have added parameters to hyperopt in `populate_indicators()` where they will be calculated only once **for all epochs**. If you are, for example, trying to optimise multiple SMA timeperiod values, the hyperoptable timeperiod parameter should be placed in `populate_entry_trend()` which is calculated every epoch. See [Optimizing an indicator parameter](https://www.freqtrade.io/en/stable/hyperopt/#optimizing-an-indicator-parameter).

View File

@ -15,7 +15,7 @@ def start_hyperopt_list(args: dict[str, Any]) -> None:
""" """
from freqtrade.configuration import setup_utils_configuration from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.btanalysis import get_latest_hyperopt_file from freqtrade.data.btanalysis import get_latest_hyperopt_file
from freqtrade.optimize.hyperopt_output import HyperoptOutput from freqtrade.optimize.hyperopt.hyperopt_output import HyperoptOutput
from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.hyperopt_tools import HyperoptTools
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)

View File

@ -0,0 +1,3 @@
# flake8: noqa: F401
from freqtrade.optimize.hyperopt.hyperopt import Hyperopt
from freqtrade.optimize.hyperopt_loss.hyperopt_loss_interface import IHyperOptLoss

View File

@ -0,0 +1,352 @@
# pragma pylint: disable=too-many-instance-attributes, pointless-string-statement
"""
This module contains the hyperopt logic
"""
import logging
import random
import sys
from datetime import datetime
from math import ceil
from multiprocessing import Manager
from pathlib import Path
from typing import Any
import rapidjson
from joblib import Parallel, cpu_count, delayed, wrap_non_picklable_objects
from joblib.externals import cloudpickle
from rich.console import Console
from freqtrade.constants import FTHYPT_FILEVERSION, LAST_BT_RESULT_FN, Config
from freqtrade.enums import HyperoptState
from freqtrade.exceptions import OperationalException
from freqtrade.misc import file_dump_json, plural
from freqtrade.optimize.hyperopt.hyperopt_logger import logging_mp_handle, logging_mp_setup
from freqtrade.optimize.hyperopt.hyperopt_optimizer import HyperOptimizer
from freqtrade.optimize.hyperopt.hyperopt_output import HyperoptOutput
from freqtrade.optimize.hyperopt_tools import (
HyperoptStateContainer,
HyperoptTools,
hyperopt_serializer,
)
from freqtrade.util import get_progress_tracker
logger = logging.getLogger(__name__)
INITIAL_POINTS = 30
# Keep no more than SKOPT_MODEL_QUEUE_SIZE models
# in the skopt model queue, to optimize memory consumption
SKOPT_MODEL_QUEUE_SIZE = 10
log_queue: Any
class Hyperopt:
"""
Hyperopt class, this class contains all the logic to run a hyperopt simulation
To start a hyperopt run:
hyperopt = Hyperopt(config)
hyperopt.start()
"""
def __init__(self, config: Config) -> None:
self._hyper_out: HyperoptOutput = HyperoptOutput(streaming=True)
self.config = config
self.analyze_per_epoch = self.config.get("analyze_per_epoch", False)
HyperoptStateContainer.set_state(HyperoptState.STARTUP)
if self.config.get("hyperopt"):
raise OperationalException(
"Using separate Hyperopt files has been removed in 2021.9. Please convert "
"your existing Hyperopt file to the new Hyperoptable strategy interface"
)
time_now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
strategy = str(self.config["strategy"])
self.results_file: Path = (
self.config["user_data_dir"]
/ "hyperopt_results"
/ f"strategy_{strategy}_{time_now}.fthypt"
)
self.data_pickle_file = (
self.config["user_data_dir"] / "hyperopt_results" / "hyperopt_tickerdata.pkl"
)
self.total_epochs = config.get("epochs", 0)
self.current_best_loss = 100
self.clean_hyperopt()
self.num_epochs_saved = 0
self.current_best_epoch: dict[str, Any] | None = None
if HyperoptTools.has_space(self.config, "sell"):
# Make sure use_exit_signal is enabled
self.config["use_exit_signal"] = True
self.print_all = self.config.get("print_all", False)
self.hyperopt_table_header = 0
self.print_colorized = self.config.get("print_colorized", False)
self.print_json = self.config.get("print_json", False)
self.hyperopter = HyperOptimizer(self.config)
@staticmethod
def get_lock_filename(config: Config) -> str:
return str(config["user_data_dir"] / "hyperopt.lock")
def clean_hyperopt(self) -> None:
"""
Remove hyperopt pickle files to restart hyperopt.
"""
for f in [self.data_pickle_file, self.results_file]:
p = Path(f)
if p.is_file():
logger.info(f"Removing `{p}`.")
p.unlink()
def hyperopt_pickle_magic(self, bases) -> None:
"""
Hyperopt magic to allow strategy inheritance across files.
For this to properly work, we need to register the module of the imported class
to pickle as value.
"""
for modules in bases:
if modules.__name__ != "IStrategy":
cloudpickle.register_pickle_by_value(sys.modules[modules.__module__])
self.hyperopt_pickle_magic(modules.__bases__)
def _save_result(self, epoch: dict) -> None:
"""
Save hyperopt results to file
Store one line per epoch.
While not a valid json object - this allows appending easily.
:param epoch: result dictionary for this epoch.
"""
epoch[FTHYPT_FILEVERSION] = 2
with self.results_file.open("a") as f:
rapidjson.dump(
epoch,
f,
default=hyperopt_serializer,
number_mode=rapidjson.NM_NATIVE | rapidjson.NM_NAN,
)
f.write("\n")
self.num_epochs_saved += 1
logger.debug(
f"{self.num_epochs_saved} {plural(self.num_epochs_saved, 'epoch')} "
f"saved to '{self.results_file}'."
)
# Store hyperopt filename
latest_filename = Path.joinpath(self.results_file.parent, LAST_BT_RESULT_FN)
file_dump_json(latest_filename, {"latest_hyperopt": str(self.results_file.name)}, log=False)
def print_results(self, results: dict[str, Any]) -> None:
"""
Log results if it is better than any previous evaluation
TODO: this should be moved to HyperoptTools too
"""
is_best = results["is_best"]
if self.print_all or is_best:
self._hyper_out.add_data(
self.config,
[results],
self.total_epochs,
self.print_all,
)
def run_optimizer_parallel(self, parallel: Parallel, asked: list[list]) -> list[dict[str, Any]]:
"""Start optimizer in a parallel way"""
def optimizer_wrapper(*args, **kwargs):
# global log queue. This must happen in the file that initializes Parallel
logging_mp_setup(
log_queue, logging.INFO if self.config["verbosity"] < 1 else logging.DEBUG
)
return self.hyperopter.generate_optimizer(*args, **kwargs)
return parallel(delayed(wrap_non_picklable_objects(optimizer_wrapper))(v) for v in asked)
def _set_random_state(self, random_state: int | None) -> int:
return random_state or random.randint(1, 2**16 - 1) # noqa: S311
def get_asked_points(self, n_points: int) -> tuple[list[list[Any]], list[bool]]:
"""
Enforce points returned from `self.opt.ask` have not been already evaluated
Steps:
1. Try to get points using `self.opt.ask` first
2. Discard the points that have already been evaluated
3. Retry using `self.opt.ask` up to 3 times
4. If still some points are missing in respect to `n_points`, random sample some points
5. Repeat until at least `n_points` points in the `asked_non_tried` list
6. Return a list with length truncated at `n_points`
"""
def unique_list(a_list):
new_list = []
for item in a_list:
if item not in new_list:
new_list.append(item)
return new_list
i = 0
asked_non_tried: list[list[Any]] = []
is_random_non_tried: list[bool] = []
while i < 5 and len(asked_non_tried) < n_points:
if i < 3:
self.opt.cache_ = {}
asked = unique_list(self.opt.ask(n_points=n_points * 5 if i > 0 else n_points))
is_random = [False for _ in range(len(asked))]
else:
asked = unique_list(self.opt.space.rvs(n_samples=n_points * 5))
is_random = [True for _ in range(len(asked))]
is_random_non_tried += [
rand
for x, rand in zip(asked, is_random, strict=False)
if x not in self.opt.Xi and x not in asked_non_tried
]
asked_non_tried += [
x for x in asked if x not in self.opt.Xi and x not in asked_non_tried
]
i += 1
if asked_non_tried:
return (
asked_non_tried[: min(len(asked_non_tried), n_points)],
is_random_non_tried[: min(len(asked_non_tried), n_points)],
)
else:
return self.opt.ask(n_points=n_points), [False for _ in range(n_points)]
def evaluate_result(self, val: dict[str, Any], current: int, is_random: bool):
"""
Evaluate results returned from generate_optimizer
"""
val["current_epoch"] = current
val["is_initial_point"] = current <= INITIAL_POINTS
logger.debug("Optimizer epoch evaluated: %s", val)
is_best = HyperoptTools.is_best_loss(val, self.current_best_loss)
# This value is assigned here and not in the optimization method
# to keep proper order in the list of results. That's because
# evaluations can take different time. Here they are aligned in the
# order they will be shown to the user.
val["is_best"] = is_best
val["is_random"] = is_random
self.print_results(val)
if is_best:
self.current_best_loss = val["loss"]
self.current_best_epoch = val
self._save_result(val)
def _setup_logging_mp_workaround(self) -> None:
"""
Workaround for logging in child processes.
local_queue must be a global in the file that initializes Parallel.
"""
global log_queue
m = Manager()
log_queue = m.Queue()
def start(self) -> None:
self.random_state = self._set_random_state(self.config.get("hyperopt_random_state"))
logger.info(f"Using optimizer random state: {self.random_state}")
self.hyperopt_table_header = -1
self.hyperopter.prepare_hyperopt()
cpus = cpu_count()
logger.info(f"Found {cpus} CPU cores. Let's make them scream!")
config_jobs = self.config.get("hyperopt_jobs", -1)
logger.info(f"Number of parallel jobs set as: {config_jobs}")
self.opt = self.hyperopter.get_optimizer(
config_jobs, self.random_state, INITIAL_POINTS, SKOPT_MODEL_QUEUE_SIZE
)
self._setup_logging_mp_workaround()
try:
with Parallel(n_jobs=config_jobs) as parallel:
jobs = parallel._effective_n_jobs()
logger.info(f"Effective number of parallel workers used: {jobs}")
console = Console(
color_system="auto" if self.print_colorized else None,
)
# Define progressbar
with get_progress_tracker(
console=console,
cust_callables=[self._hyper_out],
) as pbar:
task = pbar.add_task("Epochs", total=self.total_epochs)
start = 0
if self.analyze_per_epoch:
# First analysis not in parallel mode when using --analyze-per-epoch.
# This allows dataprovider to load it's informative cache.
asked, is_random = self.get_asked_points(n_points=1)
f_val0 = self.hyperopter.generate_optimizer(asked[0])
self.opt.tell(asked, [f_val0["loss"]])
self.evaluate_result(f_val0, 1, is_random[0])
pbar.update(task, advance=1)
start += 1
evals = ceil((self.total_epochs - start) / jobs)
for i in range(evals):
# Correct the number of epochs to be processed for the last
# iteration (should not exceed self.total_epochs in total)
n_rest = (i + 1) * jobs - (self.total_epochs - start)
current_jobs = jobs - n_rest if n_rest > 0 else jobs
asked, is_random = self.get_asked_points(n_points=current_jobs)
f_val = self.run_optimizer_parallel(parallel, asked)
self.opt.tell(asked, [v["loss"] for v in f_val])
for j, val in enumerate(f_val):
# Use human-friendly indexes here (starting from 1)
current = i * jobs + j + 1 + start
self.evaluate_result(val, current, is_random[j])
pbar.update(task, advance=1)
logging_mp_handle(log_queue)
except KeyboardInterrupt:
print("User interrupted..")
logger.info(
f"{self.num_epochs_saved} {plural(self.num_epochs_saved, 'epoch')} "
f"saved to '{self.results_file}'."
)
if self.current_best_epoch:
HyperoptTools.try_export_params(
self.config,
self.hyperopter.get_strategy_name(),
self.current_best_epoch,
)
HyperoptTools.show_epoch_details(
self.current_best_epoch, self.total_epochs, self.print_json
)
elif self.num_epochs_saved > 0:
print(
f"No good result found for given optimization function in {self.num_epochs_saved} "
f"{plural(self.num_epochs_saved, 'epoch')}."
)
else:
# This is printed when Ctrl+C is pressed quickly, before first epochs have
# a chance to be evaluated.
print("No epochs evaluated yet, no best result.")

View File

@ -14,7 +14,7 @@ from freqtrade.exceptions import OperationalException
with suppress(ImportError): with suppress(ImportError):
from skopt.space import Dimension from skopt.space import Dimension
from freqtrade.optimize.hyperopt_interface import EstimatorType, IHyperOpt from freqtrade.optimize.hyperopt.hyperopt_interface import EstimatorType, IHyperOpt
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -0,0 +1,40 @@
import logging
from logging.handlers import QueueHandler
from multiprocessing import Queue, current_process
from queue import Empty
logger = logging.getLogger(__name__)
def logging_mp_setup(log_queue: Queue, verbosity: int):
"""
Setup logging in a child process.
Must be called in the child process before logging.
log_queue MUST be passed to the child process via inheritance
Which essentially means that the log_queue must be a global, created in the same
file as Parallel is initialized.
"""
current_proc = current_process().name
if current_proc != "MainProcess":
h = QueueHandler(log_queue)
root = logging.getLogger()
root.setLevel(verbosity)
root.addHandler(h)
def logging_mp_handle(q: Queue):
"""
Handle logging from a child process.
Must be called in the parent process to handle log messages from the child process.
"""
try:
while True:
record = q.get(block=False)
if record is None:
break
logger.handle(record)
except Empty:
pass

View File

@ -1,45 +1,33 @@
# pragma pylint: disable=too-many-instance-attributes, pointless-string-statement
""" """
This module contains the hyperopt logic This module contains the hyperopt optimizer class, which needs to be pickled
and will be sent to the hyperopt worker processes.
""" """
import logging import logging
import random
import sys import sys
import warnings import warnings
from datetime import datetime, timezone from datetime import datetime, timezone
from math import ceil
from pathlib import Path
from typing import Any from typing import Any
import rapidjson from joblib import dump, load
from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects
from joblib.externals import cloudpickle from joblib.externals import cloudpickle
from pandas import DataFrame from pandas import DataFrame
from rich.console import Console
from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN, Config from freqtrade.constants import DATETIME_PRINT_FORMAT, Config
from freqtrade.data.converter import trim_dataframes from freqtrade.data.converter import trim_dataframes
from freqtrade.data.history import get_timerange from freqtrade.data.history import get_timerange
from freqtrade.data.metrics import calculate_market_change from freqtrade.data.metrics import calculate_market_change
from freqtrade.enums import HyperoptState from freqtrade.enums import HyperoptState
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import deep_merge_dicts, file_dump_json, plural from freqtrade.misc import deep_merge_dicts
from freqtrade.optimize.backtesting import Backtesting from freqtrade.optimize.backtesting import Backtesting
# Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules # Import IHyperOptLoss to allow unpickling classes from these modules
from freqtrade.optimize.hyperopt_auto import HyperOptAuto from freqtrade.optimize.hyperopt.hyperopt_auto import HyperOptAuto
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss from freqtrade.optimize.hyperopt_loss.hyperopt_loss_interface import IHyperOptLoss
from freqtrade.optimize.hyperopt_output import HyperoptOutput from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer, HyperoptTools
from freqtrade.optimize.hyperopt_tools import (
HyperoptStateContainer,
HyperoptTools,
hyperopt_serializer,
)
from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.optimize.optimize_reports import generate_strategy_stats
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
from freqtrade.util import get_progress_tracker
# Suppress scikit-learn FutureWarnings from skopt # Suppress scikit-learn FutureWarnings from skopt
@ -51,22 +39,13 @@ with warnings.catch_warnings():
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
INITIAL_POINTS = 30
# Keep no more than SKOPT_MODEL_QUEUE_SIZE models
# in the skopt model queue, to optimize memory consumption
SKOPT_MODEL_QUEUE_SIZE = 10
MAX_LOSS = 100000 # just a big enough number to be bad result in loss optimization MAX_LOSS = 100000 # just a big enough number to be bad result in loss optimization
class Hyperopt: class HyperOptimizer:
""" """
Hyperopt class, this class contains all the logic to run a hyperopt simulation HyperoptOptimizer class
This class is sent to the hyperopt worker processes.
To start a hyperopt run:
hyperopt = Hyperopt(config)
hyperopt.start()
""" """
def __init__(self, config: Config) -> None: def __init__(self, config: Config) -> None:
@ -79,8 +58,6 @@ class Hyperopt:
self.max_open_trades_space: list[Dimension] = [] self.max_open_trades_space: list[Dimension] = []
self.dimensions: list[Dimension] = [] self.dimensions: list[Dimension] = []
self._hyper_out: HyperoptOutput = HyperoptOutput(streaming=True)
self.config = config self.config = config
self.min_date: datetime self.min_date: datetime
self.max_date: datetime self.max_date: datetime
@ -89,7 +66,6 @@ class Hyperopt:
self.pairlist = self.backtesting.pairlists.whitelist self.pairlist = self.backtesting.pairlists.whitelist
self.custom_hyperopt: HyperOptAuto self.custom_hyperopt: HyperOptAuto
self.analyze_per_epoch = self.config.get("analyze_per_epoch", False) self.analyze_per_epoch = self.config.get("analyze_per_epoch", False)
HyperoptStateContainer.set_state(HyperoptState.STARTUP)
if not self.config.get("hyperopt"): if not self.config.get("hyperopt"):
self.custom_hyperopt = HyperOptAuto(self.config) self.custom_hyperopt = HyperOptAuto(self.config)
@ -107,48 +83,35 @@ class Hyperopt:
self.config self.config
) )
self.calculate_loss = self.custom_hyperoptloss.hyperopt_loss_function self.calculate_loss = self.custom_hyperoptloss.hyperopt_loss_function
time_now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
strategy = str(self.config["strategy"])
self.results_file: Path = (
self.config["user_data_dir"]
/ "hyperopt_results"
/ f"strategy_{strategy}_{time_now}.fthypt"
)
self.data_pickle_file = ( self.data_pickle_file = (
self.config["user_data_dir"] / "hyperopt_results" / "hyperopt_tickerdata.pkl" self.config["user_data_dir"] / "hyperopt_results" / "hyperopt_tickerdata.pkl"
) )
self.total_epochs = config.get("epochs", 0)
self.current_best_loss = 100
self.clean_hyperopt()
self.market_change = 0.0 self.market_change = 0.0
self.num_epochs_saved = 0
self.current_best_epoch: dict[str, Any] | None = None
if HyperoptTools.has_space(self.config, "sell"): if HyperoptTools.has_space(self.config, "sell"):
# Make sure use_exit_signal is enabled # Make sure use_exit_signal is enabled
self.config["use_exit_signal"] = True self.config["use_exit_signal"] = True
self.print_all = self.config.get("print_all", False) def prepare_hyperopt(self) -> None:
self.hyperopt_table_header = 0 # Initialize spaces ...
self.print_colorized = self.config.get("print_colorized", False) self.init_spaces()
self.print_json = self.config.get("print_json", False)
@staticmethod self.prepare_hyperopt_data()
def get_lock_filename(config: Config) -> str:
return str(config["user_data_dir"] / "hyperopt.lock")
def clean_hyperopt(self) -> None: # We don't need exchange instance anymore while running hyperopt
""" self.backtesting.exchange.close()
Remove hyperopt pickle files to restart hyperopt. self.backtesting.exchange._api = None
""" self.backtesting.exchange._api_async = None
for f in [self.data_pickle_file, self.results_file]: self.backtesting.exchange.loop = None # type: ignore
p = Path(f) self.backtesting.exchange._loop_lock = None # type: ignore
if p.is_file(): self.backtesting.exchange._cache_lock = None # type: ignore
logger.info(f"Removing `{p}`.") # self.backtesting.exchange = None # type: ignore
p.unlink() self.backtesting.pairlists = None # type: ignore
def get_strategy_name(self) -> str:
return self.backtesting.strategy.get_strategy_name()
def hyperopt_pickle_magic(self, bases) -> None: def hyperopt_pickle_magic(self, bases) -> None:
""" """
@ -173,32 +136,6 @@ class Hyperopt:
# and the values are taken from the list of parameters. # and the values are taken from the list of parameters.
return {d.name: v for d, v in zip(dimensions, raw_params, strict=False)} return {d.name: v for d, v in zip(dimensions, raw_params, strict=False)}
def _save_result(self, epoch: dict) -> None:
"""
Save hyperopt results to file
Store one line per epoch.
While not a valid json object - this allows appending easily.
:param epoch: result dictionary for this epoch.
"""
epoch[FTHYPT_FILEVERSION] = 2
with self.results_file.open("a") as f:
rapidjson.dump(
epoch,
f,
default=hyperopt_serializer,
number_mode=rapidjson.NM_NATIVE | rapidjson.NM_NAN,
)
f.write("\n")
self.num_epochs_saved += 1
logger.debug(
f"{self.num_epochs_saved} {plural(self.num_epochs_saved, 'epoch')} "
f"saved to '{self.results_file}'."
)
# Store hyperopt filename
latest_filename = Path.joinpath(self.results_file.parent, LAST_BT_RESULT_FN)
file_dump_json(latest_filename, {"latest_hyperopt": str(self.results_file.name)}, log=False)
def _get_params_details(self, params: dict) -> dict: def _get_params_details(self, params: dict) -> dict:
""" """
Return the params for each space Return the params for each space
@ -251,21 +188,6 @@ class Hyperopt:
result["max_open_trades"] = {"max_open_trades": strategy.max_open_trades} result["max_open_trades"] = {"max_open_trades": strategy.max_open_trades}
return result return result
def print_results(self, results: dict[str, Any]) -> None:
"""
Log results if it is better than any previous evaluation
TODO: this should be moved to HyperoptTools too
"""
is_best = results["is_best"]
if self.print_all or is_best:
self._hyper_out.add_data(
self.config,
[results],
self.total_epochs,
self.print_all,
)
def init_spaces(self): def init_spaces(self):
""" """
Assign the dimensions in the hyperoptimization space. Assign the dimensions in the hyperoptimization space.
@ -452,7 +374,14 @@ class Hyperopt:
"total_profit": total_profit, "total_profit": total_profit,
} }
def get_optimizer(self, dimensions: list[Dimension], cpu_count) -> Optimizer: def get_optimizer(
self,
cpu_count: int,
random_state: int,
initial_points: int,
model_queue_size: int,
) -> Optimizer:
dimensions = self.dimensions
estimator = self.custom_hyperopt.generate_estimator(dimensions=dimensions) estimator = self.custom_hyperopt.generate_estimator(dimensions=dimensions)
acq_optimizer = "sampling" acq_optimizer = "sampling"
@ -467,21 +396,12 @@ class Hyperopt:
dimensions, dimensions,
base_estimator=estimator, base_estimator=estimator,
acq_optimizer=acq_optimizer, acq_optimizer=acq_optimizer,
n_initial_points=INITIAL_POINTS, n_initial_points=initial_points,
acq_optimizer_kwargs={"n_jobs": cpu_count}, acq_optimizer_kwargs={"n_jobs": cpu_count},
random_state=self.random_state, random_state=random_state,
model_queue_size=SKOPT_MODEL_QUEUE_SIZE, model_queue_size=model_queue_size,
) )
def run_optimizer_parallel(self, parallel: Parallel, asked: list[list]) -> list[dict[str, Any]]:
"""Start optimizer in a parallel way"""
return parallel(
delayed(wrap_non_picklable_objects(self.generate_optimizer))(v) for v in asked
)
def _set_random_state(self, random_state: int | None) -> int:
return random_state or random.randint(1, 2**16 - 1) # noqa: S311
def advise_and_trim(self, data: dict[str, DataFrame]) -> dict[str, DataFrame]: def advise_and_trim(self, data: dict[str, DataFrame]) -> dict[str, DataFrame]:
preprocessed = self.backtesting.strategy.advise_all_indicators(data) preprocessed = self.backtesting.strategy.advise_all_indicators(data)
@ -517,173 +437,3 @@ class Hyperopt:
dump(preprocessed, self.data_pickle_file) dump(preprocessed, self.data_pickle_file)
else: else:
dump(data, self.data_pickle_file) dump(data, self.data_pickle_file)
def get_asked_points(self, n_points: int) -> tuple[list[list[Any]], list[bool]]:
"""
Enforce points returned from `self.opt.ask` have not been already evaluated
Steps:
1. Try to get points using `self.opt.ask` first
2. Discard the points that have already been evaluated
3. Retry using `self.opt.ask` up to 3 times
4. If still some points are missing in respect to `n_points`, random sample some points
5. Repeat until at least `n_points` points in the `asked_non_tried` list
6. Return a list with length truncated at `n_points`
"""
def unique_list(a_list):
new_list = []
for item in a_list:
if item not in new_list:
new_list.append(item)
return new_list
i = 0
asked_non_tried: list[list[Any]] = []
is_random_non_tried: list[bool] = []
while i < 5 and len(asked_non_tried) < n_points:
if i < 3:
self.opt.cache_ = {}
asked = unique_list(self.opt.ask(n_points=n_points * 5 if i > 0 else n_points))
is_random = [False for _ in range(len(asked))]
else:
asked = unique_list(self.opt.space.rvs(n_samples=n_points * 5))
is_random = [True for _ in range(len(asked))]
is_random_non_tried += [
rand
for x, rand in zip(asked, is_random, strict=False)
if x not in self.opt.Xi and x not in asked_non_tried
]
asked_non_tried += [
x for x in asked if x not in self.opt.Xi and x not in asked_non_tried
]
i += 1
if asked_non_tried:
return (
asked_non_tried[: min(len(asked_non_tried), n_points)],
is_random_non_tried[: min(len(asked_non_tried), n_points)],
)
else:
return self.opt.ask(n_points=n_points), [False for _ in range(n_points)]
def evaluate_result(self, val: dict[str, Any], current: int, is_random: bool):
"""
Evaluate results returned from generate_optimizer
"""
val["current_epoch"] = current
val["is_initial_point"] = current <= INITIAL_POINTS
logger.debug("Optimizer epoch evaluated: %s", val)
is_best = HyperoptTools.is_best_loss(val, self.current_best_loss)
# This value is assigned here and not in the optimization method
# to keep proper order in the list of results. That's because
# evaluations can take different time. Here they are aligned in the
# order they will be shown to the user.
val["is_best"] = is_best
val["is_random"] = is_random
self.print_results(val)
if is_best:
self.current_best_loss = val["loss"]
self.current_best_epoch = val
self._save_result(val)
def start(self) -> None:
self.random_state = self._set_random_state(self.config.get("hyperopt_random_state"))
logger.info(f"Using optimizer random state: {self.random_state}")
self.hyperopt_table_header = -1
# Initialize spaces ...
self.init_spaces()
self.prepare_hyperopt_data()
# We don't need exchange instance anymore while running hyperopt
self.backtesting.exchange.close()
self.backtesting.exchange._api = None
self.backtesting.exchange._api_async = None
self.backtesting.exchange.loop = None # type: ignore
self.backtesting.exchange._loop_lock = None # type: ignore
self.backtesting.exchange._cache_lock = None # type: ignore
# self.backtesting.exchange = None # type: ignore
self.backtesting.pairlists = None # type: ignore
cpus = cpu_count()
logger.info(f"Found {cpus} CPU cores. Let's make them scream!")
config_jobs = self.config.get("hyperopt_jobs", -1)
logger.info(f"Number of parallel jobs set as: {config_jobs}")
self.opt = self.get_optimizer(self.dimensions, config_jobs)
try:
with Parallel(n_jobs=config_jobs) as parallel:
jobs = parallel._effective_n_jobs()
logger.info(f"Effective number of parallel workers used: {jobs}")
console = Console(
color_system="auto" if self.print_colorized else None,
)
# Define progressbar
with get_progress_tracker(
console=console,
cust_callables=[self._hyper_out],
) as pbar:
task = pbar.add_task("Epochs", total=self.total_epochs)
start = 0
if self.analyze_per_epoch:
# First analysis not in parallel mode when using --analyze-per-epoch.
# This allows dataprovider to load it's informative cache.
asked, is_random = self.get_asked_points(n_points=1)
f_val0 = self.generate_optimizer(asked[0])
self.opt.tell(asked, [f_val0["loss"]])
self.evaluate_result(f_val0, 1, is_random[0])
pbar.update(task, advance=1)
start += 1
evals = ceil((self.total_epochs - start) / jobs)
for i in range(evals):
# Correct the number of epochs to be processed for the last
# iteration (should not exceed self.total_epochs in total)
n_rest = (i + 1) * jobs - (self.total_epochs - start)
current_jobs = jobs - n_rest if n_rest > 0 else jobs
asked, is_random = self.get_asked_points(n_points=current_jobs)
f_val = self.run_optimizer_parallel(parallel, asked)
self.opt.tell(asked, [v["loss"] for v in f_val])
for j, val in enumerate(f_val):
# Use human-friendly indexes here (starting from 1)
current = i * jobs + j + 1 + start
self.evaluate_result(val, current, is_random[j])
pbar.update(task, advance=1)
except KeyboardInterrupt:
print("User interrupted..")
logger.info(
f"{self.num_epochs_saved} {plural(self.num_epochs_saved, 'epoch')} "
f"saved to '{self.results_file}'."
)
if self.current_best_epoch:
HyperoptTools.try_export_params(
self.config, self.backtesting.strategy.get_strategy_name(), self.current_best_epoch
)
HyperoptTools.show_epoch_details(
self.current_best_epoch, self.total_epochs, self.print_json
)
elif self.num_epochs_saved > 0:
print(
f"No good result found for given optimization function in {self.num_epochs_saved} "
f"{plural(self.num_epochs_saved, 'epoch')}."
)
else:
# This is printed when Ctrl+C is pressed quickly, before first epochs have
# a chance to be evaluated.
print("No epochs evaluated yet, no best result.")

View File

@ -9,7 +9,7 @@ from pathlib import Path
from freqtrade.constants import HYPEROPT_LOSS_BUILTIN, USERPATH_HYPEROPTS, Config from freqtrade.constants import HYPEROPT_LOSS_BUILTIN, USERPATH_HYPEROPTS, Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss from freqtrade.optimize.hyperopt_loss.hyperopt_loss_interface import IHyperOptLoss
from freqtrade.resolvers import IResolver from freqtrade.resolvers import IResolver

View File

@ -14,7 +14,7 @@ from freqtrade.data.history import load_data
from freqtrade.enums import ExitType, RunMode from freqtrade.enums import ExitType, RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt import Hyperopt from freqtrade.optimize.hyperopt import Hyperopt
from freqtrade.optimize.hyperopt_auto import HyperOptAuto from freqtrade.optimize.hyperopt.hyperopt_auto import HyperOptAuto
from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.hyperopt_tools import HyperoptTools
from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.optimize.optimize_reports import generate_strategy_stats
from freqtrade.optimize.space import SKDecimal from freqtrade.optimize.space import SKDecimal
@ -222,7 +222,7 @@ def test_start_no_data(mocker, hyperopt_conf, tmp_path) -> None:
patched_configuration_load_config_file(mocker, hyperopt_conf) patched_configuration_load_config_file(mocker, hyperopt_conf)
mocker.patch("freqtrade.data.history.load_pair_history", MagicMock(return_value=pd.DataFrame)) mocker.patch("freqtrade.data.history.load_pair_history", MagicMock(return_value=pd.DataFrame))
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.get_timerange", "freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))), MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
) )
@ -315,12 +315,17 @@ def test_roi_table_generation(hyperopt) -> None:
"roi_p3": 3, "roi_p3": 3,
} }
assert hyperopt.custom_hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0} assert hyperopt.hyperopter.custom_hyperopt.generate_roi_table(params) == {
0: 6,
15: 3,
25: 1,
30: 0,
}
def test_params_no_optimize_details(hyperopt) -> None: def test_params_no_optimize_details(hyperopt) -> None:
hyperopt.config["spaces"] = ["buy"] hyperopt.hyperopter.config["spaces"] = ["buy"]
res = hyperopt._get_no_optimize_details() res = hyperopt.hyperopter._get_no_optimize_details()
assert isinstance(res, dict) assert isinstance(res, dict)
assert "trailing" in res assert "trailing" in res
assert res["trailing"]["trailing_stop"] is False assert res["trailing"]["trailing_stop"] is False
@ -333,21 +338,23 @@ def test_params_no_optimize_details(hyperopt) -> None:
def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None: def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump") dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result") dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5) mocker.patch(
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json") "freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
)
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
mocker.patch( mocker.patch(
"freqtrade.optimize.backtesting.Backtesting.load_bt_data", "freqtrade.optimize.backtesting.Backtesting.load_bt_data",
MagicMock(return_value=(MagicMock(), None)), MagicMock(return_value=(MagicMock(), None)),
) )
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.get_timerange", "freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))), MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
) )
# Dummy-reduce points to ensure scikit-learn is forced to generate new values # Dummy-reduce points to ensure scikit-learn is forced to generate new values
mocker.patch("freqtrade.optimize.hyperopt.INITIAL_POINTS", 2) mocker.patch("freqtrade.optimize.hyperopt.hyperopt.INITIAL_POINTS", 2)
parallel = mocker.patch( parallel = mocker.patch(
"freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel", "freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel",
@ -367,8 +374,8 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
del hyperopt_conf["timeframe"] del hyperopt_conf["timeframe"]
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
hyperopt.start() hyperopt.start()
@ -379,10 +386,12 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
# Should be called for historical candle data # Should be called for historical candle data
assert dumper.call_count == 1 assert dumper.call_count == 1
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_exit") assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_entry")
assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"] assert (
assert hasattr(hyperopt.backtesting, "_position_stacking") hyperopt.hyperopter.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"]
)
assert hasattr(hyperopt.hyperopter.backtesting, "_position_stacking")
def test_hyperopt_format_results(hyperopt): def test_hyperopt_format_results(hyperopt):
@ -461,7 +470,7 @@ def test_hyperopt_format_results(hyperopt):
def test_populate_indicators(hyperopt, testdatadir) -> None: def test_populate_indicators(hyperopt, testdatadir) -> None:
data = load_data(testdatadir, "1m", ["UNITTEST/BTC"], fill_up_missing=True) data = load_data(testdatadir, "1m", ["UNITTEST/BTC"], fill_up_missing=True)
dataframes = hyperopt.backtesting.strategy.advise_all_indicators(data) dataframes = hyperopt.hyperopter.backtesting.strategy.advise_all_indicators(data)
dataframe = dataframes["UNITTEST/BTC"] dataframe = dataframes["UNITTEST/BTC"]
# Check if some indicators are generated. We will not test all of them # Check if some indicators are generated. We will not test all of them
@ -521,15 +530,20 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
"final_balance": 1000, "final_balance": 1000,
} }
mocker.patch("freqtrade.optimize.hyperopt.Backtesting.backtest", return_value=backtest_result)
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.get_timerange", "freqtrade.optimize.hyperopt.hyperopt_optimizer.Backtesting.backtest",
return_value=backtest_result,
)
mocker.patch(
"freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
return_value=(dt_utc(2017, 12, 10), dt_utc(2017, 12, 13)), return_value=(dt_utc(2017, 12, 10), dt_utc(2017, 12, 13)),
) )
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch.object(Path, "open") mocker.patch.object(Path, "open")
mocker.patch("freqtrade.configuration.config_validation.validate_config_schema") mocker.patch("freqtrade.configuration.config_validation.validate_config_schema")
mocker.patch("freqtrade.optimize.hyperopt.load", return_value={"XRP/BTC": None}) mocker.patch(
"freqtrade.optimize.hyperopt.hyperopt_optimizer.load", return_value={"XRP/BTC": None}
)
optimizer_param = { optimizer_param = {
"buy_plusdi": 0.02, "buy_plusdi": 0.02,
@ -589,10 +603,12 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
} }
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.min_date = dt_utc(2017, 12, 10) hyperopt.hyperopter.min_date = dt_utc(2017, 12, 10)
hyperopt.max_date = dt_utc(2017, 12, 13) hyperopt.hyperopter.max_date = dt_utc(2017, 12, 13)
hyperopt.init_spaces() hyperopt.hyperopter.init_spaces()
generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values())) generate_optimizer_value = hyperopt.hyperopter.generate_optimizer(
list(optimizer_param.values())
)
assert generate_optimizer_value == response_expected assert generate_optimizer_value == response_expected
@ -603,8 +619,8 @@ def test_clean_hyperopt(mocker, hyperopt_conf, caplog):
"freqtrade.strategy.hyper.HyperStrategyMixin.load_params_from_file", "freqtrade.strategy.hyper.HyperStrategyMixin.load_params_from_file",
MagicMock(return_value={}), MagicMock(return_value={}),
) )
mocker.patch("freqtrade.optimize.hyperopt.Path.is_file", MagicMock(return_value=True)) mocker.patch("freqtrade.optimize.hyperopt.hyperopt.Path.is_file", MagicMock(return_value=True))
unlinkmock = mocker.patch("freqtrade.optimize.hyperopt.Path.unlink", MagicMock()) unlinkmock = mocker.patch("freqtrade.optimize.hyperopt.hyperopt.Path.unlink", MagicMock())
h = Hyperopt(hyperopt_conf) h = Hyperopt(hyperopt_conf)
assert unlinkmock.call_count == 2 assert unlinkmock.call_count == 2
@ -612,17 +628,19 @@ def test_clean_hyperopt(mocker, hyperopt_conf, caplog):
def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None: def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump") dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result") dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json") mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5) mocker.patch(
"freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
)
mocker.patch( mocker.patch(
"freqtrade.optimize.backtesting.Backtesting.load_bt_data", "freqtrade.optimize.backtesting.Backtesting.load_bt_data",
MagicMock(return_value=(MagicMock(), None)), MagicMock(return_value=(MagicMock(), None)),
) )
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.get_timerange", "freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))), MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
) )
@ -658,8 +676,8 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
) )
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
hyperopt.start() hyperopt.start()
@ -677,16 +695,18 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None: def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump") dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result") dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json") mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5) mocker.patch(
"freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
)
mocker.patch( mocker.patch(
"freqtrade.optimize.backtesting.Backtesting.load_bt_data", "freqtrade.optimize.backtesting.Backtesting.load_bt_data",
MagicMock(return_value=(MagicMock(), None)), MagicMock(return_value=(MagicMock(), None)),
) )
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.get_timerange", "freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))), MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
) )
@ -714,8 +734,8 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
hyperopt_conf.update({"print_json": True}) hyperopt_conf.update({"print_json": True})
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
hyperopt.start() hyperopt.start()
@ -732,16 +752,18 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None: def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump") dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result") dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5) mocker.patch(
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json") "freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
)
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
mocker.patch( mocker.patch(
"freqtrade.optimize.backtesting.Backtesting.load_bt_data", "freqtrade.optimize.backtesting.Backtesting.load_bt_data",
MagicMock(return_value=(MagicMock(), None)), MagicMock(return_value=(MagicMock(), None)),
) )
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.get_timerange", "freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))), MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
) )
@ -770,8 +792,8 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
) )
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
hyperopt.start() hyperopt.start()
@ -785,16 +807,18 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> None: def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump") dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result") dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5) mocker.patch(
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json") "freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
)
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
mocker.patch( mocker.patch(
"freqtrade.optimize.backtesting.Backtesting.load_bt_data", "freqtrade.optimize.backtesting.Backtesting.load_bt_data",
MagicMock(return_value=(MagicMock(), None)), MagicMock(return_value=(MagicMock(), None)),
) )
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.get_timerange", "freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))), MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
) )
@ -816,8 +840,8 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
hyperopt_conf.update({"spaces": "roi stoploss"}) hyperopt_conf.update({"spaces": "roi stoploss"})
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
hyperopt.start() hyperopt.start()
@ -828,21 +852,23 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
assert dumper.call_count == 1 assert dumper.call_count == 1
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_exit") assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_entry")
assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"] assert (
assert hasattr(hyperopt.backtesting, "_position_stacking") hyperopt.hyperopter.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"]
)
assert hasattr(hyperopt.hyperopter.backtesting, "_position_stacking")
def test_simplified_interface_all_failed(mocker, hyperopt_conf, caplog) -> None: def test_simplified_interface_all_failed(mocker, hyperopt_conf, caplog) -> None:
mocker.patch("freqtrade.optimize.hyperopt.dump", MagicMock()) mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump", MagicMock())
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json") mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
mocker.patch( mocker.patch(
"freqtrade.optimize.backtesting.Backtesting.load_bt_data", "freqtrade.optimize.backtesting.Backtesting.load_bt_data",
MagicMock(return_value=(MagicMock(), None)), MagicMock(return_value=(MagicMock(), None)),
) )
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.get_timerange", "freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))), MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
) )
@ -855,34 +881,37 @@ def test_simplified_interface_all_failed(mocker, hyperopt_conf, caplog) -> None:
) )
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt_auto.HyperOptAuto._generate_indicator_space", return_value=[] "freqtrade.optimize.hyperopt.hyperopt_auto.HyperOptAuto._generate_indicator_space",
return_value=[],
) )
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
with pytest.raises(OperationalException, match=r"The 'protection' space is included into *"): with pytest.raises(OperationalException, match=r"The 'protection' space is included into *"):
hyperopt.init_spaces() hyperopt.hyperopter.init_spaces()
hyperopt.config["hyperopt_ignore_missing_space"] = True hyperopt.config["hyperopt_ignore_missing_space"] = True
caplog.clear() caplog.clear()
hyperopt.init_spaces() hyperopt.hyperopter.init_spaces()
assert log_has_re(r"The 'protection' space is included into *", caplog) assert log_has_re(r"The 'protection' space is included into *", caplog)
assert hyperopt.protection_space == [] assert hyperopt.hyperopter.protection_space == []
def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None: def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump") dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result") dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5) mocker.patch(
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json") "freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
)
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
mocker.patch( mocker.patch(
"freqtrade.optimize.backtesting.Backtesting.load_bt_data", "freqtrade.optimize.backtesting.Backtesting.load_bt_data",
MagicMock(return_value=(MagicMock(), None)), MagicMock(return_value=(MagicMock(), None)),
) )
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.get_timerange", "freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))), MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
) )
@ -904,8 +933,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
hyperopt_conf.update({"spaces": "buy"}) hyperopt_conf.update({"spaces": "buy"})
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
hyperopt.start() hyperopt.start()
@ -916,23 +945,27 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
assert dumper.called assert dumper.called
assert dumper.call_count == 1 assert dumper.call_count == 1
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_exit") assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_entry")
assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"] assert (
assert hasattr(hyperopt.backtesting, "_position_stacking") hyperopt.hyperopter.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"]
)
assert hasattr(hyperopt.hyperopter.backtesting, "_position_stacking")
def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None: def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
dumper = mocker.patch("freqtrade.optimize.hyperopt.dump") dumper = mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump")
dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result") dumper2 = mocker.patch("freqtrade.optimize.hyperopt.Hyperopt._save_result")
mocker.patch("freqtrade.optimize.hyperopt.calculate_market_change", return_value=1.5) mocker.patch(
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json") "freqtrade.optimize.hyperopt.hyperopt_optimizer.calculate_market_change", return_value=1.5
)
mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
mocker.patch( mocker.patch(
"freqtrade.optimize.backtesting.Backtesting.load_bt_data", "freqtrade.optimize.backtesting.Backtesting.load_bt_data",
MagicMock(return_value=(MagicMock(), None)), MagicMock(return_value=(MagicMock(), None)),
) )
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.get_timerange", "freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))), MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
) )
@ -958,8 +991,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
) )
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
hyperopt.start() hyperopt.start()
@ -970,10 +1003,12 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
assert dumper.called assert dumper.called
assert dumper.call_count == 1 assert dumper.call_count == 1
assert dumper2.call_count == 1 assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_exit") assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_exit")
assert hasattr(hyperopt.backtesting.strategy, "advise_entry") assert hasattr(hyperopt.hyperopter.backtesting.strategy, "advise_entry")
assert hyperopt.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"] assert (
assert hasattr(hyperopt.backtesting, "_position_stacking") hyperopt.hyperopter.backtesting.strategy.max_open_trades == hyperopt_conf["max_open_trades"]
)
assert hasattr(hyperopt.hyperopter.backtesting, "_position_stacking")
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -985,18 +1020,19 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
], ],
) )
def test_simplified_interface_failed(mocker, hyperopt_conf, space) -> None: def test_simplified_interface_failed(mocker, hyperopt_conf, space) -> None:
mocker.patch("freqtrade.optimize.hyperopt.dump", MagicMock()) mocker.patch("freqtrade.optimize.hyperopt.hyperopt_optimizer.dump", MagicMock())
mocker.patch("freqtrade.optimize.hyperopt.file_dump_json") mocker.patch("freqtrade.optimize.hyperopt.hyperopt.file_dump_json")
mocker.patch( mocker.patch(
"freqtrade.optimize.backtesting.Backtesting.load_bt_data", "freqtrade.optimize.backtesting.Backtesting.load_bt_data",
MagicMock(return_value=(MagicMock(), None)), MagicMock(return_value=(MagicMock(), None)),
) )
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.get_timerange", "freqtrade.optimize.hyperopt.hyperopt_optimizer.get_timerange",
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))), MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13))),
) )
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt_auto.HyperOptAuto._generate_indicator_space", return_value=[] "freqtrade.optimize.hyperopt.hyperopt_auto.HyperOptAuto._generate_indicator_space",
return_value=[],
) )
patch_exchange(mocker) patch_exchange(mocker)
@ -1004,8 +1040,8 @@ def test_simplified_interface_failed(mocker, hyperopt_conf, space) -> None:
hyperopt_conf.update({"spaces": space}) hyperopt_conf.update({"spaces": space})
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock() hyperopt.hyperopter.backtesting.strategy.advise_all_indicators = MagicMock()
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={}) hyperopt.hyperopter.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
with pytest.raises(OperationalException, match=f"The '{space}' space is included into *"): with pytest.raises(OperationalException, match=f"The '{space}' space is included into *"):
hyperopt.start() hyperopt.start()
@ -1015,7 +1051,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmp_path, fee) -> None
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch(f"{EXMS}.get_fee", fee) mocker.patch(f"{EXMS}.get_fee", fee)
# Dummy-reduce points to ensure scikit-learn is forced to generate new values # Dummy-reduce points to ensure scikit-learn is forced to generate new values
mocker.patch("freqtrade.optimize.hyperopt.INITIAL_POINTS", 2) mocker.patch("freqtrade.optimize.hyperopt.hyperopt.INITIAL_POINTS", 2)
(tmp_path / "hyperopt_results").mkdir(parents=True) (tmp_path / "hyperopt_results").mkdir(parents=True)
# No hyperopt needed # No hyperopt needed
hyperopt_conf.update( hyperopt_conf.update(
@ -1027,32 +1063,33 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmp_path, fee) -> None
} }
) )
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0) opt = hyperopt.hyperopter
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) opt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter) assert isinstance(opt.custom_hyperopt, HyperOptAuto)
assert hyperopt.backtesting.strategy.bot_started is True assert isinstance(opt.backtesting.strategy.buy_rsi, IntParameter)
assert hyperopt.backtesting.strategy.bot_loop_started is False assert opt.backtesting.strategy.bot_started is True
assert opt.backtesting.strategy.bot_loop_started is False
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True assert opt.backtesting.strategy.buy_rsi.in_space is True
assert hyperopt.backtesting.strategy.buy_rsi.value == 35 assert opt.backtesting.strategy.buy_rsi.value == 35
assert hyperopt.backtesting.strategy.sell_rsi.value == 74 assert opt.backtesting.strategy.sell_rsi.value == 74
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30 assert opt.backtesting.strategy.protection_cooldown_lookback.value == 30
assert hyperopt.backtesting.strategy.max_open_trades == 1 assert opt.backtesting.strategy.max_open_trades == 1
buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range buy_rsi_range = opt.backtesting.strategy.buy_rsi.range
assert isinstance(buy_rsi_range, range) assert isinstance(buy_rsi_range, range)
# Range from 0 - 50 (inclusive) # Range from 0 - 50 (inclusive)
assert len(list(buy_rsi_range)) == 51 assert len(list(buy_rsi_range)) == 51
hyperopt.start() hyperopt.start()
# All values should've changed. # All values should've changed.
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value != 30 assert opt.backtesting.strategy.protection_cooldown_lookback.value != 30
assert hyperopt.backtesting.strategy.buy_rsi.value != 35 assert opt.backtesting.strategy.buy_rsi.value != 35
assert hyperopt.backtesting.strategy.sell_rsi.value != 74 assert opt.backtesting.strategy.sell_rsi.value != 74
assert hyperopt.backtesting.strategy.max_open_trades != 1 assert opt.backtesting.strategy.max_open_trades != 1
hyperopt.custom_hyperopt.generate_estimator = lambda *args, **kwargs: "ET1" opt.custom_hyperopt.generate_estimator = lambda *args, **kwargs: "ET1"
with pytest.raises(OperationalException, match="Estimator ET1 not supported."): with pytest.raises(OperationalException, match="Estimator ET1 not supported."):
hyperopt.get_optimizer([], 2) opt.get_optimizer(2, 42, 2, 2)
@pytest.mark.filterwarnings("ignore::DeprecationWarning") @pytest.mark.filterwarnings("ignore::DeprecationWarning")
@ -1063,7 +1100,7 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmp_path
mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=get_markets())) mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=get_markets()))
(tmp_path / "hyperopt_results").mkdir(parents=True) (tmp_path / "hyperopt_results").mkdir(parents=True)
# Dummy-reduce points to ensure scikit-learn is forced to generate new values # Dummy-reduce points to ensure scikit-learn is forced to generate new values
mocker.patch("freqtrade.optimize.hyperopt.INITIAL_POINTS", 2) mocker.patch("freqtrade.optimize.hyperopt.hyperopt.INITIAL_POINTS", 2)
# No hyperopt needed # No hyperopt needed
hyperopt_conf.update( hyperopt_conf.update(
{ {
@ -1078,21 +1115,22 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmp_path
} }
) )
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.exchange.get_max_leverage = lambda *x, **xx: 1.0 opt = hyperopt.hyperopter
hyperopt.backtesting.exchange.get_min_pair_stake_amount = lambda *x, **xx: 0.00001 opt.backtesting.exchange.get_max_leverage = lambda *x, **xx: 1.0
hyperopt.backtesting.exchange.get_max_pair_stake_amount = lambda *x, **xx: 100.0 opt.backtesting.exchange.get_min_pair_stake_amount = lambda *x, **xx: 0.00001
hyperopt.backtesting.exchange._markets = get_markets() opt.backtesting.exchange.get_max_pair_stake_amount = lambda *x, **xx: 100.0
opt.backtesting.exchange._markets = get_markets()
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) assert isinstance(opt.custom_hyperopt, HyperOptAuto)
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter) assert isinstance(opt.backtesting.strategy.buy_rsi, IntParameter)
assert hyperopt.backtesting.strategy.bot_started is True assert opt.backtesting.strategy.bot_started is True
assert hyperopt.backtesting.strategy.bot_loop_started is False assert opt.backtesting.strategy.bot_loop_started is False
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True assert opt.backtesting.strategy.buy_rsi.in_space is True
assert hyperopt.backtesting.strategy.buy_rsi.value == 35 assert opt.backtesting.strategy.buy_rsi.value == 35
assert hyperopt.backtesting.strategy.sell_rsi.value == 74 assert opt.backtesting.strategy.sell_rsi.value == 74
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30 assert opt.backtesting.strategy.protection_cooldown_lookback.value == 30
buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range buy_rsi_range = opt.backtesting.strategy.buy_rsi.range
assert isinstance(buy_rsi_range, range) assert isinstance(buy_rsi_range, range)
# Range from 0 - 50 (inclusive) # Range from 0 - 50 (inclusive)
assert len(list(buy_rsi_range)) == 51 assert len(list(buy_rsi_range)) == 51
@ -1116,7 +1154,7 @@ def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmp_path, fe
} }
) )
go = mocker.patch( go = mocker.patch(
"freqtrade.optimize.hyperopt.Hyperopt.generate_optimizer", "freqtrade.optimize.hyperopt.hyperopt_optimizer.HyperOptimizer.generate_optimizer",
return_value={ return_value={
"loss": 0.05, "loss": 0.05,
"results_explanation": "foo result", "results_explanation": "foo result",
@ -1125,17 +1163,18 @@ def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmp_path, fe
}, },
) )
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0) opt = hyperopt.hyperopter
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) opt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter) assert isinstance(opt.custom_hyperopt, HyperOptAuto)
assert hyperopt.backtesting.strategy.bot_loop_started is False assert isinstance(opt.backtesting.strategy.buy_rsi, IntParameter)
assert hyperopt.backtesting.strategy.bot_started is True assert opt.backtesting.strategy.bot_loop_started is False
assert opt.backtesting.strategy.bot_started is True
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True assert opt.backtesting.strategy.buy_rsi.in_space is True
assert hyperopt.backtesting.strategy.buy_rsi.value == 35 assert opt.backtesting.strategy.buy_rsi.value == 35
assert hyperopt.backtesting.strategy.sell_rsi.value == 74 assert opt.backtesting.strategy.sell_rsi.value == 74
assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30 assert opt.backtesting.strategy.protection_cooldown_lookback.value == 30
buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range buy_rsi_range = opt.backtesting.strategy.buy_rsi.range
assert isinstance(buy_rsi_range, range) assert isinstance(buy_rsi_range, range)
# Range from 0 - 50 (inclusive) # Range from 0 - 50 (inclusive)
assert len(list(buy_rsi_range)) == 51 assert len(list(buy_rsi_range)) == 51
@ -1179,17 +1218,17 @@ def test_stake_amount_unlimited_max_open_trades(mocker, hyperopt_conf, tmp_path,
) )
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.Hyperopt._get_params_dict", "freqtrade.optimize.hyperopt.hyperopt_optimizer.HyperOptimizer._get_params_dict",
return_value={"max_open_trades": -1}, return_value={"max_open_trades": -1},
) )
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) assert isinstance(hyperopt.hyperopter.custom_hyperopt, HyperOptAuto)
assert hyperopt.backtesting.strategy.max_open_trades == 1 assert hyperopt.hyperopter.backtesting.strategy.max_open_trades == 1
hyperopt.start() hyperopt.start()
assert hyperopt.backtesting.strategy.max_open_trades == 1 assert hyperopt.hyperopter.backtesting.strategy.max_open_trades == 1
def test_max_open_trades_dump(mocker, hyperopt_conf, tmp_path, fee, capsys) -> None: def test_max_open_trades_dump(mocker, hyperopt_conf, tmp_path, fee, capsys) -> None:
@ -1208,11 +1247,11 @@ def test_max_open_trades_dump(mocker, hyperopt_conf, tmp_path, fee, capsys) -> N
) )
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.Hyperopt._get_params_dict", "freqtrade.optimize.hyperopt.hyperopt_optimizer.HyperOptimizer._get_params_dict",
return_value={"max_open_trades": -1}, return_value={"max_open_trades": -1},
) )
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) assert isinstance(hyperopt.hyperopter.custom_hyperopt, HyperOptAuto)
hyperopt.start() hyperopt.start()
@ -1227,11 +1266,11 @@ def test_max_open_trades_dump(mocker, hyperopt_conf, tmp_path, fee, capsys) -> N
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
mocker.patch( mocker.patch(
"freqtrade.optimize.hyperopt.Hyperopt._get_params_dict", "freqtrade.optimize.hyperopt.hyperopt_optimizer.HyperOptimizer._get_params_dict",
return_value={"max_open_trades": -1}, return_value={"max_open_trades": -1},
) )
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) assert isinstance(hyperopt.hyperopter.custom_hyperopt, HyperOptAuto)
hyperopt.start() hyperopt.start()
@ -1262,9 +1301,9 @@ def test_max_open_trades_consistency(mocker, hyperopt_conf, tmp_path, fee) -> No
) )
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) assert isinstance(hyperopt.hyperopter.custom_hyperopt, HyperOptAuto)
hyperopt.custom_hyperopt.max_open_trades_space = lambda: [ hyperopt.hyperopter.custom_hyperopt.max_open_trades_space = lambda: [
Integer(1, 10, name="max_open_trades") Integer(1, 10, name="max_open_trades")
] ]
@ -1282,11 +1321,13 @@ def test_max_open_trades_consistency(mocker, hyperopt_conf, tmp_path, fee) -> No
return wrapper return wrapper
hyperopt.backtesting.wallets._calculate_unlimited_stake_amount = stake_amount_interceptor( hyperopt.hyperopter.backtesting.wallets._calculate_unlimited_stake_amount = (
hyperopt.backtesting.wallets._calculate_unlimited_stake_amount stake_amount_interceptor(
hyperopt.hyperopter.backtesting.wallets._calculate_unlimited_stake_amount
)
) )
hyperopt.start() hyperopt.start()
assert hyperopt.backtesting.strategy.max_open_trades == 8 assert hyperopt.hyperopter.backtesting.strategy.max_open_trades == 8
assert hyperopt.config["max_open_trades"] == 8 assert hyperopt.config["max_open_trades"] == 8