diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 7960a1f5c..8ae3922ca 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -306,6 +306,14 @@ class Arguments(object): dest='print_all', default=False ) + parser.add_argument( + '--random-state', + help='Set random state to some positive integer for reproducible hyperopt results.', + dest='hyperopt_random_state', + default=None, + type=Arguments.check_int_positive, + metavar='INT', + ) def _build_subcommands(self) -> None: """ @@ -376,6 +384,18 @@ class Arguments(object): return TimeRange(stype[0], stype[1], start, stop) raise Exception('Incorrect syntax for timerange "%s"' % text) + @staticmethod + def check_int_positive(value) -> int: + try: + uint = int(value) + if uint <= 0: + raise ValueError + except ValueError: + raise argparse.ArgumentTypeError( + f"{value} is invalid for this parameter, should be a positive integer value" + ) + return uint + def scripts_options(self) -> None: """ Parses given arguments for scripts. diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index 284c91260..ca3b81279 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -201,24 +201,20 @@ class Configuration(object): :return: configuration as dictionary """ - # If -i/--ticker-interval is used we override the configuration parameter - # (that will override the strategy configuration) + # This will override the strategy configuration if 'ticker_interval' in self.args and self.args.ticker_interval: config.update({'ticker_interval': self.args.ticker_interval}) logger.info('Parameter -i/--ticker-interval detected ...') logger.info('Using ticker_interval: %s ...', config.get('ticker_interval')) - # If -l/--live is used we add it to the configuration if 'live' in self.args and self.args.live: config.update({'live': True}) logger.info('Parameter -l/--live detected ...') - # If --enable-position-stacking is used we add it to the configuration if 'position_stacking' in self.args and self.args.position_stacking: config.update({'position_stacking': True}) logger.info('Parameter --enable-position-stacking detected ...') - # If --disable-max-market-positions or --max_open_trades is used we update configuration if 'use_max_market_positions' in self.args and not self.args.use_max_market_positions: config.update({'use_max_market_positions': False}) logger.info('Parameter --disable-max-market-positions detected ...') @@ -230,25 +226,21 @@ class Configuration(object): else: logger.info('Using max_open_trades: %s ...', config.get('max_open_trades')) - # If --stake_amount is used we update configuration if 'stake_amount' in self.args and self.args.stake_amount: config.update({'stake_amount': self.args.stake_amount}) logger.info('Parameter --stake_amount detected, overriding stake_amount to: %s ...', config.get('stake_amount')) - # If --timerange is used we add it to the configuration if 'timerange' in self.args and self.args.timerange: config.update({'timerange': self.args.timerange}) logger.info('Parameter --timerange detected: %s ...', self.args.timerange) - # If --datadir is used we add it to the configuration if 'datadir' in self.args and self.args.datadir: config.update({'datadir': self._create_datadir(config, self.args.datadir)}) else: config.update({'datadir': self._create_datadir(config, None)}) logger.info('Using data folder: %s ...', config.get('datadir')) - # If -r/--refresh-pairs-cached is used we add it to the configuration if 'refresh_pairs' in self.args and self.args.refresh_pairs: config.update({'refresh_pairs': True}) logger.info('Parameter -r/--refresh-pairs-cached detected ...') @@ -261,12 +253,10 @@ class Configuration(object): config.update({'ticker_interval': self.args.ticker_interval}) logger.info('Overriding ticker interval with Command line argument') - # If --export is used we add it to the configuration if 'export' in self.args and self.args.export: config.update({'export': self.args.export}) logger.info('Parameter --export detected: %s ...', self.args.export) - # If --export-filename is used we add it to the configuration if 'export' in config and 'exportfilename' in self.args and self.args.exportfilename: config.update({'exportfilename': self.args.exportfilename}) logger.info('Storing backtest results to %s ...', self.args.exportfilename) @@ -279,12 +269,10 @@ class Configuration(object): :return: configuration as dictionary """ - # If --timerange is used we add it to the configuration if 'timerange' in self.args and self.args.timerange: config.update({'timerange': self.args.timerange}) logger.info('Parameter --timerange detected: %s ...', self.args.timerange) - # If --timerange is used we add it to the configuration if 'stoploss_range' in self.args and self.args.stoploss_range: txt_range = eval(self.args.stoploss_range) config['edge'].update({'stoploss_range_min': txt_range[0]}) @@ -292,7 +280,6 @@ class Configuration(object): config['edge'].update({'stoploss_range_step': txt_range[2]}) logger.info('Parameter --stoplosses detected: %s ...', self.args.stoploss_range) - # If -r/--refresh-pairs-cached is used we add it to the configuration if 'refresh_pairs' in self.args and self.args.refresh_pairs: config.update({'refresh_pairs': True}) logger.info('Parameter -r/--refresh-pairs-cached detected ...') @@ -309,13 +296,11 @@ class Configuration(object): # Add the hyperopt file to use config.update({'hyperopt': self.args.hyperopt}) - # If --epochs is used we add it to the configuration if 'epochs' in self.args and self.args.epochs: config.update({'epochs': self.args.epochs}) logger.info('Parameter --epochs detected ...') logger.info('Will run Hyperopt with for %s epochs ...', config.get('epochs')) - # If --spaces is used we add it to the configuration if 'spaces' in self.args and self.args.spaces: config.update({'spaces': self.args.spaces}) logger.info('Parameter -s/--spaces detected: %s', config.get('spaces')) @@ -324,11 +309,15 @@ class Configuration(object): config.update({'print_all': self.args.print_all}) logger.info('Parameter --print-all detected: %s', config.get('print_all')) - # If -r/--refresh-pairs-cached is used we add it to the configuration if 'refresh_pairs' in self.args and self.args.refresh_pairs: config.update({'refresh_pairs': True}) logger.info('Parameter -r/--refresh-pairs-cached detected ...') + if 'hyperopt_random_state' in self.args and self.args.hyperopt_random_state is not None: + config.update({'hyperopt_random_state': self.args.hyperopt_random_state}) + logger.info("Parameter --random-state detected: %s", + config.get('hyperopt_random_state')) + return config def _validate_config_schema(self, conf: Dict[str, Any]) -> Dict[str, Any]: diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 5756d075b..2f819773f 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -236,7 +236,8 @@ class Hyperopt(Backtesting): base_estimator="ET", acq_optimizer="auto", n_initial_points=30, - acq_optimizer_kwargs={'n_jobs': cpu_count} + acq_optimizer_kwargs={'n_jobs': cpu_count}, + random_state=self.config.get('hyperopt_random_state', None) ) def run_optimizer_parallel(self, parallel, asked) -> List: