From: Jérôme Benoit Date: Sun, 23 Feb 2025 17:15:23 +0000 (+0100) Subject: feat(reforcexy): make hyperopt per pair tunable X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=c01a8fb1a8de19eb6f35ba3a2c7e69eff4522a2a;p=freqai-strategies.git feat(reforcexy): make hyperopt per pair tunable Signed-off-by: Jérôme Benoit --- diff --git a/ReforceXY/user_data/config-template.json b/ReforceXY/user_data/config-template.json index 693d638..994fb94 100644 --- a/ReforceXY/user_data/config-template.json +++ b/ReforceXY/user_data/config-template.json @@ -175,10 +175,11 @@ "max_no_improvement_evals": 0, // Maximum consecutive evaluations without a new best model "min_evals": 0, // Number of evaluations before start to count evaluations without improvements "check_envs": true, // Check that an environment follows Gym API - "plot_new_best": false // Enable tensorboard rollout plot upon finding a new best model + "plot_new_best": true // Enable tensorboard rollout plot upon finding a new best model }, "rl_config_optuna": { "enabled": true, // Enable optuna hyperopt + "per_pair": false, // Enable per pair hyperopt "n_trials": 100, "n_startup_trials": 10, "timeout_hours": 0 diff --git a/ReforceXY/user_data/freqaimodels/ReforceXY.py b/ReforceXY/user_data/freqaimodels/ReforceXY.py index d841540..b5d3e84 100644 --- a/ReforceXY/user_data/freqaimodels/ReforceXY.py +++ b/ReforceXY/user_data/freqaimodels/ReforceXY.py @@ -84,6 +84,7 @@ class ReforceXY(BaseReinforcementLearningModel): }, "rl_config_optuna": { "enabled": false, // Enable optuna hyperopt + "per_pair: false, // Enable per pair hyperopt "n_trials": 100, "n_startup_trials": 10, "timeout_hours": 0, @@ -457,17 +458,18 @@ class ReforceXY(BaseReinforcementLearningModel): output = output.rolling(window=self.CONV_WIDTH).apply(_predict) return output - def get_storage(self, pair: str) -> BaseStorage: + def get_storage(self, pair: str | None = None) -> BaseStorage: """ Get the storage for Optuna """ storage_dir = str(self.full_path) + storage_filename = f"optuna-{pair.split('/')[0]}" if pair else "optuna" storage_backend = self.rl_config_optuna.get("storage", "sqlite") if storage_backend == "sqlite": - storage = f"sqlite:///{storage_dir}/optuna-{pair.split('/')[0]}.sqlite" + storage = f"sqlite:///{storage_dir}/{storage_filename}.sqlite" elif storage_backend == "file": storage = JournalStorage( - JournalFileBackend(f"{storage_dir}/optuna-{pair.split('/')[0]}.log") + JournalFileBackend(f"{storage_dir}/{storage_filename}.log") ) return storage @@ -478,8 +480,13 @@ class ReforceXY(BaseReinforcementLearningModel): Runs hyperparameter optimization using Optuna and returns the best hyperparameters found """ - study_name = str(dk.pair) - storage = self.get_storage(dk.pair) + _, identifier = str(self.full_path).rsplit("/", 1) + if self.rl_config_optuna.get("per_pair", False): + study_name = f"{identifier}-{dk.pair}" + storage = self.get_storage(dk.pair) + else: + study_name = identifier + storage = self.get_storage() study: Study = create_study( study_name=study_name, sampler=TPESampler( @@ -514,7 +521,7 @@ class ReforceXY(BaseReinforcementLearningModel): logger.info( "------------ Hyperopt results %s (%.2f secs) ------------", - dk.pair, + study_name, time_spent, ) logger.info( @@ -523,32 +530,49 @@ class ReforceXY(BaseReinforcementLearningModel): logger.info("Best trial params: %s", study.best_trial.params) logger.info("-------------------------------------------------------") - self.save_best_params(dk.pair, study.best_trial.params) + self.save_best_params( + study.best_trial.params, + dk.pair if self.rl_config_optuna.get("per_pair", False) else None, + ) return study.best_trial.params - def save_best_params(self, pair: str, best_params: Dict) -> None: + def save_best_params(self, best_params: Dict, pair: str | None = None) -> None: """ Save the best hyperparameters found during hyperparameter optimization """ - best_params_path = Path( - self.full_path / f"hyperopt-best-params-{pair.split('/')[0]}.json" + best_params_filename = ( + f"hyperopt-best-params-{pair.split('/')[0]}" + if pair + else "hyperopt-best-params" ) - logger.info(f"{pair}: saving best params to %s JSON file", best_params_path) + best_params_path = Path(self.full_path / f"{best_params_filename}.json") + log_msg: str = ( + f"{pair}: saving best params to {best_params_path} JSON file" + if pair + else f"saving best params to {best_params_path} JSON file" + ) + logger.info(log_msg) with best_params_path.open("w", encoding="utf-8") as write_file: json.dump(best_params, write_file, indent=4) - def load_best_params(self, pair: str) -> Dict | None: + def load_best_params(self, pair: str | None = None) -> Dict | None: """ Load the best hyperparameters found and saved during hyperparameter optimization """ - best_params_path = Path( - self.full_path / f"hyperopt-best-params-{pair.split('/')[0]}.json" + best_params_filename = ( + f"hyperopt-best-params-{pair.split('/')[0]}" + if pair + else "hyperopt-best-params.json" + ) + best_params_path = Path(self.full_path / f"{best_params_filename}.json") + log_msg: str = ( + f"{pair}: loading best params from {best_params_path} JSON file" + if pair + else f"loading best params from {best_params_path} JSON file" ) if best_params_path.is_file(): - logger.info( - f"{pair}: loading best params from %s JSON file", best_params_path - ) + logger.info(log_msg) with best_params_path.open("r", encoding="utf-8") as read_file: best_params = json.load(read_file) return best_params @@ -584,9 +608,7 @@ class ReforceXY(BaseReinforcementLearningModel): else: tensorboard_log_path = None - logger.info( - "------------ Hyperopt trial %d %s ------------", trial.number, dk.pair - ) + logger.info("------------ Hyperopt trial %d ------------", trial.number) logger.info("Trial %s params: %s", trial.number, params) model = self.MODELCLASS( diff --git a/quickadapter/user_data/freqaimodels/LightGBMRegressorQuickAdapterV35.py b/quickadapter/user_data/freqaimodels/LightGBMRegressorQuickAdapterV35.py index 7ca72fb..3fd4269 100644 --- a/quickadapter/user_data/freqaimodels/LightGBMRegressorQuickAdapterV35.py +++ b/quickadapter/user_data/freqaimodels/LightGBMRegressorQuickAdapterV35.py @@ -253,13 +253,14 @@ class LightGBMRegressorQuickAdapterV35(BaseRegressionModel): def optuna_storage(self, pair: str) -> optuna.storages.BaseStorage: storage_dir = str(self.full_path) + storage_filename = f"optuna-{pair.split('/')[0]}" storage_backend = self.__optuna_config.get("storage", "file") if storage_backend == "sqlite": - storage = f"sqlite:///{storage_dir}/optuna-{pair.split('/')[0]}.sqlite" + storage = f"sqlite:///{storage_dir}/{storage_filename}.sqlite" elif storage_backend == "file": storage = optuna.storages.JournalStorage( optuna.storages.journal.JournalFileBackend( - f"{storage_dir}/optuna-{pair.split('/')[0]}.log" + f"{storage_dir}/{storage_filename}.log" ) ) return storage @@ -307,8 +308,9 @@ class LightGBMRegressorQuickAdapterV35(BaseRegressionModel): y_test, test_weights, ) -> tuple[Dict | None, float | None]: + _, identifier = str(self.full_path).rsplit("/", 1) study_namespace = "hp" - study_name = f"{study_namespace}-{pair}" + study_name = f"{identifier}-{study_namespace}-{pair}" storage = self.optuna_storage(pair) pruner = optuna.pruners.HyperbandPruner() self.optuna_study_delete(study_name, storage) @@ -378,8 +380,9 @@ class LightGBMRegressorQuickAdapterV35(BaseRegressionModel): test_weights, model_training_parameters, ) -> tuple[Dict | None, float | None]: + _, identifier = str(self.full_path).rsplit("/", 1) study_namespace = "period" - study_name = f"{study_namespace}-{pair}" + study_name = f"{identifier}-{study_namespace}-{pair}" storage = self.optuna_storage(pair) pruner = optuna.pruners.HyperbandPruner() self.optuna_study_delete(study_name, storage) diff --git a/quickadapter/user_data/freqaimodels/XGBoostRegressorQuickAdapterV35.py b/quickadapter/user_data/freqaimodels/XGBoostRegressorQuickAdapterV35.py index ebcb25b..bbc2b28 100644 --- a/quickadapter/user_data/freqaimodels/XGBoostRegressorQuickAdapterV35.py +++ b/quickadapter/user_data/freqaimodels/XGBoostRegressorQuickAdapterV35.py @@ -254,13 +254,14 @@ class XGBoostRegressorQuickAdapterV35(BaseRegressionModel): def optuna_storage(self, pair: str) -> optuna.storages.BaseStorage: storage_dir = str(self.full_path) + storage_filename = f"optuna-{pair.split('/')[0]}" storage_backend = self.__optuna_config.get("storage", "file") if storage_backend == "sqlite": - storage = f"sqlite:///{storage_dir}/optuna-{pair.split('/')[0]}.sqlite" + storage = f"sqlite:///{storage_dir}/{storage_filename}.sqlite" elif storage_backend == "file": storage = optuna.storages.JournalStorage( optuna.storages.journal.JournalFileBackend( - f"{storage_dir}/optuna-{pair.split('/')[0]}.log" + f"{storage_dir}/{storage_filename}.log" ) ) return storage @@ -308,8 +309,9 @@ class XGBoostRegressorQuickAdapterV35(BaseRegressionModel): y_test, test_weights, ) -> tuple[Dict | None, float | None]: + _, identifier = str(self.full_path).rsplit("/", 1) study_namespace = "hp" - study_name = f"{study_namespace}-{pair}" + study_name = f"{identifier}-{study_namespace}-{pair}" storage = self.optuna_storage(pair) pruner = optuna.pruners.HyperbandPruner() self.optuna_study_delete(study_name, storage) @@ -379,8 +381,9 @@ class XGBoostRegressorQuickAdapterV35(BaseRegressionModel): test_weights, model_training_parameters, ) -> tuple[Dict | None, float | None]: + _, identifier = str(self.full_path).rsplit("/", 1) study_namespace = "period" - study_name = f"{study_namespace}-{pair}" + study_name = f"{identifier}-{study_namespace}-{pair}" storage = self.optuna_storage(pair) pruner = optuna.pruners.HyperbandPruner() self.optuna_study_delete(study_name, storage)