Merge pull request #7258 from freqtrade/feat/hyp_optinal_indicator
Add flag to move hyperopt populate_indicators to epoch
This commit is contained in:
@@ -34,7 +34,7 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
||||
"print_colorized", "print_json", "hyperopt_jobs",
|
||||
"hyperopt_random_state", "hyperopt_min_trades",
|
||||
"hyperopt_loss", "disableparamexport",
|
||||
"hyperopt_ignore_missing_space"]
|
||||
"hyperopt_ignore_missing_space", "analyze_per_epoch"]
|
||||
|
||||
ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"]
|
||||
|
||||
|
@@ -255,6 +255,13 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
nargs='+',
|
||||
default='default',
|
||||
),
|
||||
"analyze_per_epoch": Arg(
|
||||
'--analyze-per-epoch',
|
||||
help='Run populate_indicators once per epoch.',
|
||||
action='store_true',
|
||||
default=False,
|
||||
),
|
||||
|
||||
"print_all": Arg(
|
||||
'--print-all',
|
||||
help='Print all results, not only the best ones.',
|
||||
|
@@ -302,6 +302,9 @@ class Configuration:
|
||||
self._args_to_config(config, argname='spaces',
|
||||
logstring='Parameter -s/--spaces detected: {}')
|
||||
|
||||
self._args_to_config(config, argname='analyze_per_epoch',
|
||||
logstring='Parameter --analyze-per-epoch detected.')
|
||||
|
||||
self._args_to_config(config, argname='print_all',
|
||||
logstring='Parameter --print-all detected ...')
|
||||
|
||||
|
@@ -3,6 +3,7 @@ from freqtrade.enums.backteststate import BacktestState
|
||||
from freqtrade.enums.candletype import CandleType
|
||||
from freqtrade.enums.exitchecktuple import ExitCheckTuple
|
||||
from freqtrade.enums.exittype import ExitType
|
||||
from freqtrade.enums.hyperoptstate import HyperoptState
|
||||
from freqtrade.enums.marginmode import MarginMode
|
||||
from freqtrade.enums.ordertypevalue import OrderTypeValues
|
||||
from freqtrade.enums.rpcmessagetype import RPCMessageType
|
||||
|
12
freqtrade/enums/hyperoptstate.py
Normal file
12
freqtrade/enums/hyperoptstate.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class HyperoptState(Enum):
|
||||
""" Hyperopt states """
|
||||
STARTUP = 1
|
||||
DATALOAD = 2
|
||||
INDICATORS = 3
|
||||
OPTIMIZE = 4
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name.lower()}"
|
@@ -24,13 +24,15 @@ from pandas import DataFrame
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN
|
||||
from freqtrade.data.converter import trim_dataframes
|
||||
from freqtrade.data.history import get_timerange
|
||||
from freqtrade.enums import HyperoptState
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.misc import deep_merge_dicts, file_dump_json, plural
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
# Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules
|
||||
from freqtrade.optimize.hyperopt_auto import HyperOptAuto
|
||||
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss
|
||||
from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer
|
||||
from freqtrade.optimize.hyperopt_tools import (HyperoptStateContainer, HyperoptTools,
|
||||
hyperopt_serializer)
|
||||
from freqtrade.optimize.optimize_reports import generate_strategy_stats
|
||||
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
|
||||
|
||||
@@ -74,10 +76,14 @@ class Hyperopt:
|
||||
self.dimensions: List[Dimension] = []
|
||||
|
||||
self.config = config
|
||||
self.min_date: datetime
|
||||
self.max_date: datetime
|
||||
|
||||
self.backtesting = Backtesting(self.config)
|
||||
self.pairlist = self.backtesting.pairlists.whitelist
|
||||
self.custom_hyperopt: HyperOptAuto
|
||||
self.analyze_per_epoch = self.config.get('analyze_per_epoch', False)
|
||||
HyperoptStateContainer.set_state(HyperoptState.STARTUP)
|
||||
|
||||
if not self.config.get('hyperopt'):
|
||||
self.custom_hyperopt = HyperOptAuto(self.config)
|
||||
@@ -290,6 +296,7 @@ class Hyperopt:
|
||||
Called once per epoch to optimize whatever is configured.
|
||||
Keep this function as optimized as possible!
|
||||
"""
|
||||
HyperoptStateContainer.set_state(HyperoptState.OPTIMIZE)
|
||||
backtest_start_time = datetime.now(timezone.utc)
|
||||
params_dict = self._get_params_dict(self.dimensions, raw_params)
|
||||
|
||||
@@ -321,6 +328,10 @@ class Hyperopt:
|
||||
|
||||
with self.data_pickle_file.open('rb') as f:
|
||||
processed = load(f, mmap_mode='r')
|
||||
if self.analyze_per_epoch:
|
||||
# Data is not yet analyzed, rerun populate_indicators.
|
||||
processed = self.advise_and_trim(processed)
|
||||
|
||||
bt_results = self.backtesting.backtest(
|
||||
processed=processed,
|
||||
start_date=self.min_date,
|
||||
@@ -406,22 +417,33 @@ class Hyperopt:
|
||||
def _set_random_state(self, random_state: Optional[int]) -> int:
|
||||
return random_state or random.randint(1, 2**16 - 1)
|
||||
|
||||
def prepare_hyperopt_data(self) -> None:
|
||||
data, timerange = self.backtesting.load_bt_data()
|
||||
self.backtesting.load_bt_data_detail()
|
||||
logger.info("Dataload complete. Calculating indicators")
|
||||
|
||||
def advise_and_trim(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]:
|
||||
preprocessed = self.backtesting.strategy.advise_all_indicators(data)
|
||||
|
||||
# Trim startup period from analyzed dataframe to get correct dates for output.
|
||||
processed = trim_dataframes(preprocessed, timerange, self.backtesting.required_startup)
|
||||
processed = trim_dataframes(preprocessed, self.timerange, self.backtesting.required_startup)
|
||||
self.min_date, self.max_date = get_timerange(processed)
|
||||
return processed
|
||||
|
||||
logger.info(f'Hyperopting with data from {self.min_date.strftime(DATETIME_PRINT_FORMAT)} '
|
||||
f'up to {self.max_date.strftime(DATETIME_PRINT_FORMAT)} '
|
||||
f'({(self.max_date - self.min_date).days} days)..')
|
||||
# Store non-trimmed data - will be trimmed after signal generation.
|
||||
dump(preprocessed, self.data_pickle_file)
|
||||
def prepare_hyperopt_data(self) -> None:
|
||||
HyperoptStateContainer.set_state(HyperoptState.DATALOAD)
|
||||
data, self.timerange = self.backtesting.load_bt_data()
|
||||
self.backtesting.load_bt_data_detail()
|
||||
logger.info("Dataload complete. Calculating indicators")
|
||||
|
||||
if not self.analyze_per_epoch:
|
||||
HyperoptStateContainer.set_state(HyperoptState.INDICATORS)
|
||||
|
||||
preprocessed = self.advise_and_trim(data)
|
||||
|
||||
logger.info(f'Hyperopting with data from '
|
||||
f'{self.min_date.strftime(DATETIME_PRINT_FORMAT)} '
|
||||
f'up to {self.max_date.strftime(DATETIME_PRINT_FORMAT)} '
|
||||
f'({(self.max_date - self.min_date).days} days)..')
|
||||
# Store non-trimmed data - will be trimmed after signal generation.
|
||||
dump(preprocessed, self.data_pickle_file)
|
||||
else:
|
||||
dump(data, self.data_pickle_file)
|
||||
|
||||
def get_asked_points(self, n_points: int) -> Tuple[List[List[Any]], List[bool]]:
|
||||
"""
|
||||
|
@@ -13,6 +13,7 @@ from colorama import Fore, Style
|
||||
from pandas import isna, json_normalize
|
||||
|
||||
from freqtrade.constants import FTHYPT_FILEVERSION, USERPATH_STRATEGIES
|
||||
from freqtrade.enums import HyperoptState
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.misc import deep_merge_dicts, round_coin_value, round_dict, safe_value_fallback2
|
||||
from freqtrade.optimize.hyperopt_epoch_filters import hyperopt_filter_epochs
|
||||
@@ -32,6 +33,15 @@ def hyperopt_serializer(x):
|
||||
return str(x)
|
||||
|
||||
|
||||
class HyperoptStateContainer():
|
||||
""" Singleton class to track state of hyperopt"""
|
||||
state: HyperoptState = HyperoptState.OPTIMIZE
|
||||
|
||||
@classmethod
|
||||
def set_state(cls, value: HyperoptState):
|
||||
cls.state = value
|
||||
|
||||
|
||||
class HyperoptTools():
|
||||
|
||||
@staticmethod
|
||||
|
@@ -7,6 +7,9 @@ from abc import ABC, abstractmethod
|
||||
from contextlib import suppress
|
||||
from typing import Any, Optional, Sequence, Union
|
||||
|
||||
from freqtrade.enums.hyperoptstate import HyperoptState
|
||||
from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
from skopt.space import Integer, Real, Categorical
|
||||
@@ -57,6 +60,13 @@ class BaseParameter(ABC):
|
||||
Get-space - will be used by Hyperopt to get the hyperopt Space
|
||||
"""
|
||||
|
||||
def can_optimize(self):
|
||||
return (
|
||||
self.in_space
|
||||
and self.optimize
|
||||
and HyperoptStateContainer.state != HyperoptState.OPTIMIZE
|
||||
)
|
||||
|
||||
|
||||
class NumericParameter(BaseParameter):
|
||||
""" Internal parameter used for Numeric purposes """
|
||||
@@ -133,7 +143,7 @@ class IntParameter(NumericParameter):
|
||||
Returns a List with 1 item (`value`) in "non-hyperopt" mode, to avoid
|
||||
calculating 100ds of indicators.
|
||||
"""
|
||||
if self.in_space and self.optimize:
|
||||
if self.can_optimize():
|
||||
# Scikit-optimize ranges are "inclusive", while python's "range" is exclusive
|
||||
return range(self.low, self.high + 1)
|
||||
else:
|
||||
@@ -212,7 +222,7 @@ class DecimalParameter(NumericParameter):
|
||||
Returns a List with 1 item (`value`) in "non-hyperopt" mode, to avoid
|
||||
calculating 100ds of indicators.
|
||||
"""
|
||||
if self.in_space and self.optimize:
|
||||
if self.can_optimize():
|
||||
low = int(self.low * pow(10, self._decimals))
|
||||
high = int(self.high * pow(10, self._decimals)) + 1
|
||||
return [round(n * pow(0.1, self._decimals), self._decimals) for n in range(low, high)]
|
||||
@@ -261,7 +271,7 @@ class CategoricalParameter(BaseParameter):
|
||||
Returns a List with 1 item (`value`) in "non-hyperopt" mode, to avoid
|
||||
calculating 100ds of indicators.
|
||||
"""
|
||||
if self.in_space and self.optimize:
|
||||
if self.can_optimize():
|
||||
return self.opt_range
|
||||
else:
|
||||
return [self.value]
|
||||
|
Reference in New Issue
Block a user