# list of additional paths to ignore in this project.
# Same syntax as gitignore, so you can use * and **.
+# Important: quote patterns that start with `*`, otherwise YAML treats them as aliases.
+# Example:
+# ignored_paths:
+# - "examples/**"
+# - ".worktrees/**"
+# - "**/bin/**"
+# - "**/obj/**"
# Note: global ignored_paths from serena_config.yml are also applied additively.
ignored_paths: []
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
fixed_tools: []
-# list of languages for which language servers are started (LSP backend only); choose from:
-# ada al angular ansible bash
-# bsl clojure cpp cpp_ccls crystal
-# csharp csharp_omnisharp cue dart elixir
-# elm erlang fortran fsharp gdscript
-# go groovy haskell haxe hlsl
-# html java json julia kotlin
-# latex lean4 lua luau markdown
-# matlab msl nix ocaml pascal
-# perl php php_phpactor php_phpantom powershell
-# python python_jedi python_pyrefly python_ty r
-# rego ruby ruby_solargraph rust scala
-# scss solidity svelte swift systemverilog
-# terraform toml typescript typescript_vts vue
-# yaml zig
-# (This list may be outdated; generated with scripts/print_language_list.py;
-# For the current list, see values of Language enum here:
-# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py)
-# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
-# Note:
-# - For C, use cpp
-# - For JavaScript, use typescript
-# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
-# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
-# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
-# - For Free Pascal/Lazarus, use pascal
-# Special requirements:
-# Some languages require additional setup/installations.
-# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
-# When using multiple languages, the first language server that supports a given file will be used for that file.
-# The first language is the default language and the respective language server will be used as a fallback.
-# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
-languages:
-- python
-
# time budget (seconds) per tool call for the retrieval of additional symbol information
# such as docstrings or parameter information.
# This overrides the corresponding setting in the global configuration; see the documentation there.
# - "./subproject2"
ls_workspace_folders:
- .
+
+# list of language servers to start when using the LSP backend; choose from:
+# ada al angular ansible bash
+# bsl clojure cpp cpp_ccls crystal
+# csharp csharp_omnisharp cue dart elixir
+# elm erlang fortran fsharp gdscript
+# go groovy haskell haxe hlsl
+# html java json julia kotlin
+# latex lean4 lua luau markdown
+# matlab msl nix ocaml pascal
+# perl php php_phpactor php_phpantom powershell
+# python python_jedi python_pyrefly python_ty r
+# rego ruby ruby_solargraph rust scala
+# scss solidity svelte swift systemverilog
+# terraform toml typescript typescript_vts vue
+# yaml zig
+# (This list may be outdated; generated with scripts/print_language_list.py;
+# For the current list, see values of Language enum here:
+# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py)
+# For some languages, there are several alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
+# Note:
+# - For C, use cpp
+# - For JavaScript, use typescript
+# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
+# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
+# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
+# - For Free Pascal/Lazarus, use pascal
+# Special requirements:
+# Some language servers require additional setup/installations.
+# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
+# When using multiple language servers, the first language server that supports a given file will be used for that file.
+# The first language server is the default language and the respective language server will be used as a fallback.
+# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
+language_servers:
+- python
| freqai.label_prediction.outlier_quantile | 0.999 | float (0,1) | Quantile threshold for predictions outlier filtering. |
| freqai.label_prediction.keep_fraction | 0.0075 | float (0,1] | Fraction of extrema used for thresholds. 1 uses all, lower values keep only most significant. Applies to `rank_extrema` and `rank_peaks`; ignored for `partition`. |
| _Optuna / HPO_ | | | |
-| freqai.optuna_hyperopt.enabled | false | bool | Enables HPO. |
+| freqai.optuna_hyperopt.enabled | false | bool | Enables regressor and dynamic label HPO. |
| freqai.optuna_hyperopt.sampler | `tpe` | enum {`tpe`,`auto`} | HPO sampler algorithm for `hp` namespace. `tpe` uses [TPESampler](https://optuna.readthedocs.io/en/stable/reference/samplers/generated/optuna.samplers.TPESampler.html) with multivariate, group, and constant_liar (when multiple workers), `auto` uses [AutoSampler](https://hub.optuna.org/samplers/auto_sampler). |
| freqai.optuna_hyperopt.label_sampler | `auto` | enum {`auto`,`tpe`,`nsgaii`,`nsgaiii`} | HPO sampler algorithm for multi-objective `label` namespace. `nsgaii` uses [NSGAIISampler](https://optuna.readthedocs.io/en/stable/reference/samplers/generated/optuna.samplers.NSGAIISampler.html), `nsgaiii` uses [NSGAIIISampler](https://optuna.readthedocs.io/en/stable/reference/samplers/generated/optuna.samplers.NSGAIIISampler.html). |
| freqai.optuna_hyperopt.storage | `file` | enum {`file`,`sqlite`} | HPO storage backend. |
-| freqai.optuna_hyperopt.continuous | true | bool | Continuous HPO. |
-| freqai.optuna_hyperopt.warm_start | true | bool | Warm start HPO with previous best value(s). |
+| freqai.optuna_hyperopt.continuous | true | bool | Continuous HPO. Forced for both namespaces in backtest and hyperopt, resetting the study on each optimization. |
+| freqai.optuna_hyperopt.warm_start | true | bool | Warm start HPO with previous best value(s). Persisted values are loaded and saved only in live and dry-run modes; non-live runs reuse only values produced earlier in the same run. |
| freqai.optuna_hyperopt.n_startup_trials | 15 | int >= 0 | HPO startup trials. |
| freqai.optuna_hyperopt.n_trials | 50 | int >= 1 | Maximum HPO trials. |
| freqai.optuna_hyperopt.n_jobs | CPU threads / 4 | int >= 1 | Parallel HPO workers. |
import sklearn
from datasieve.pipeline import Pipeline
from datasieve.transforms import SKLearnWrapper
+from freqtrade.enums import TRADE_MODES
from freqtrade.exceptions import DependencyException
from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
default_label_period_candles, default_label_natr_multiplier = (
self._label_defaults
)
+ # self.live is unset until IFreqaiModel.start(), so derive trade-mode
+ # from the configured runmode here.
+ trade_mode = self.config.get("runmode") in TRADE_MODES
for pair in self.pairs:
self._optuna_hp_value[pair] = -1
self._optuna_label_values[pair] = [
-1
] * QuickAdapterRegressorV3._OPTUNA_LABEL_N_OBJECTIVES
self._optuna_hp_params[pair] = (
- self.optuna_load_best_params(pair, _OPTUNA_NAMESPACES.hp) or {}
- )
- self._optuna_label_params[pair] = self.optuna_load_best_params(
- pair, _OPTUNA_NAMESPACES.label
- ) or {
+ self.optuna_load_best_params(pair, _OPTUNA_NAMESPACES.hp)
+ if trade_mode
+ else None
+ ) or {}
+ configured_label_params = {
"label_period_candles": self.ft_params.get(
"label_period_candles",
default_label_period_candles,
)
),
}
+ self._optuna_label_params[pair] = (
+ self.optuna_load_best_params(pair, _OPTUNA_NAMESPACES.label)
+ if trade_mode
+ else None
+ ) or configured_label_params
self.set_optuna_label_candle(pair)
self._optuna_label_candles[pair] = 0
self.optuna_throttle_callback(
pair=pair,
namespace=_OPTUNA_NAMESPACES.label,
- callback=lambda: self.optuna_optimize(
- pair=pair,
- namespace=_OPTUNA_NAMESPACES.label,
- objective=lambda trial: label_objective(
- trial,
- self.data_provider.get_pair_dataframe(
- pair=pair, timeframe=self.config.get("timeframe")
- ),
- fit_live_predictions_candles,
- self._optuna_config.get(
- "label_candles_step",
- QuickAdapterRegressorV3.OPTUNA_LABEL_CANDLES_STEP_DEFAULT,
- ),
- min_label_period_candles=self._min_label_period_candles,
- max_label_period_candles=self._max_label_period_candles,
- min_label_natr_multiplier=self._min_label_natr_multiplier,
- max_label_natr_multiplier=self._max_label_natr_multiplier,
- ),
- directions=list(QuickAdapterRegressorV3._OPTUNA_LABEL_DIRECTIONS),
+ callback=lambda: self._optimize_labels_as_of_prediction_time(
+ dk,
+ pair,
+ fit_live_predictions_candles,
),
)
hp_rmse if hp_rmse is not None else np.inf
)
+ def _label_hpo_dataframe_as_of_prediction_time(
+ self,
+ dk: FreqaiDataKitchen,
+ pair: str,
+ ) -> pd.DataFrame:
+ """Return pair OHLCV bounded to the current FreqAI prediction time.
+
+ In backtests, ``historic_predictions`` holds the preceding prediction
+ window and ``dk.full_df`` is the full feature frame; the current
+ prediction time is the first ``dk.full_df`` timestamp strictly after the
+ last recorded prediction. Live and dry-run modes rely on the already
+ point-in-time DataProvider frame. This explicit backtest bound is
+ required because DataProvider otherwise exposes the complete historical
+ timerange.
+ """
+ pair_dataframe = self.data_provider.get_pair_dataframe(
+ pair=pair, timeframe=self.config.get("timeframe")
+ )
+ if self.live or pair_dataframe.empty:
+ return pair_dataframe
+
+ history_dates = ensure_datetime_series(
+ self.dd.historic_predictions[pair]["date"]
+ )
+ if history_dates.empty:
+ logger.debug(
+ "[%s] Label HPO skipped: no prior predictions to bound the current FreqAI prediction time",
+ pair,
+ )
+ return pair_dataframe.iloc[:0]
+
+ pair_dates = ensure_datetime_series(pair_dataframe["date"])
+ full_dates = ensure_datetime_series(dk.full_df["date"])
+ current_dates = full_dates.loc[full_dates > history_dates.max()]
+ if current_dates.empty:
+ logger.debug(
+ "[%s] Label HPO skipped: current FreqAI prediction time is unavailable",
+ pair,
+ )
+ return pair_dataframe.iloc[:0]
+
+ return pair_dataframe.loc[pair_dates <= current_dates.min()]
+
+ def _optimize_labels_as_of_prediction_time(
+ self,
+ dk: FreqaiDataKitchen,
+ pair: str,
+ fit_live_predictions_candles: int,
+ ) -> Optional[optuna.study.Study]:
+ label_dataframe = self._label_hpo_dataframe_as_of_prediction_time(dk, pair)
+ if label_dataframe.empty:
+ return None
+ return self.optuna_optimize(
+ pair=pair,
+ namespace=_OPTUNA_NAMESPACES.label,
+ objective=lambda trial: label_objective(
+ trial,
+ label_dataframe,
+ fit_live_predictions_candles,
+ self._optuna_config.get(
+ "label_candles_step",
+ QuickAdapterRegressorV3.OPTUNA_LABEL_CANDLES_STEP_DEFAULT,
+ ),
+ min_label_period_candles=self._min_label_period_candles,
+ max_label_period_candles=self._max_label_period_candles,
+ min_label_natr_multiplier=self._min_label_natr_multiplier,
+ max_label_natr_multiplier=self._max_label_natr_multiplier,
+ ),
+ directions=list(QuickAdapterRegressorV3._OPTUNA_LABEL_DIRECTIONS),
+ )
+
@staticmethod
def optuna_validate_value(value: Any) -> Optional[float]:
return value if isinstance(value, (int, float)) and np.isfinite(value) else None
logger.warning(
f"[{pair}] Optuna {namespace} {objective_type} objective hyperopt best params found has invalid optimization target value(s)"
)
- self.optuna_save_best_params(pair, namespace)
+ if self.live:
+ self.optuna_save_best_params(pair, namespace)
return study
@staticmethod
)
def optuna_create_storage(self, pair: str) -> optuna.storages.BaseStorage:
+ if not self.live:
+ return optuna.storages.InMemoryStorage()
+
storage_dir = self.full_path
storage_filename = f"optuna-{pair.split('/')[0]}"
storage_backend = self._optuna_config.get("storage")
)
return None
- continuous = self._optuna_config.get("continuous")
+ # Non-live HPO is point-in-time: reset the study each optimization and
+ # never persist best params. Warm start still seeds it from the previous
+ # cutoff's in-memory best, which stays causal as it predates the current
+ # cutoff.
+ continuous = self._optuna_config.get("continuous") or not self.live
if continuous:
QuickAdapterRegressorV3.optuna_delete_study(
pair, namespace, study_name, storage
) -> None:
try:
optuna.delete_study(study_name=study_name, storage=storage)
+ except KeyError as e:
+ # A missing study is a benign no-op: non-live runs use a fresh
+ # InMemoryStorage and the first live/dry-run optimization per pair
+ # has none yet. optuna reports it as KeyError; other failures reach
+ # the warning branch below.
+ logger.debug(
+ f"[{pair}] Optuna {namespace} study {study_name} absent; nothing to delete: {e!r}"
+ )
except Exception as e:
logger.warning(
f"[{pair}] Optuna {namespace} study {study_name} deletion failed: {e!r}",