From: Jérôme Benoit Date: Sun, 10 Aug 2025 20:11:29 +0000 (+0200) Subject: perf(qav3): fine tune pivots labeling optuna search space X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=ec2f80d7e6516a43526adc47fedb8a95cd7d5511;p=freqai-strategies.git perf(qav3): fine tune pivots labeling optuna search space Signed-off-by: Jérôme Benoit --- diff --git a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py index 6924951..dca9aef 100644 --- a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py +++ b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py @@ -23,10 +23,9 @@ from Utils import ( calculate_n_extrema, fit_regressor, format_number, + get_min_max_label_period_candles, get_optuna_callbacks, get_optuna_study_model_parameters, - largest_divisor, - round_to_nearest_int, soft_extremum, zigzag, ) @@ -61,7 +60,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel): https://github.com/sponsors/robcaulk """ - version = "3.7.111" + version = "3.7.112" @cached_property def _optuna_config(self) -> dict[str, Any]: @@ -736,7 +735,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel): if np.any(np_weights < 0): raise ValueError("label_weights values must be non-negative") label_weights_sum = np.sum(np_weights) - if np.isclose(label_weights_sum, 0): + if np.isclose(label_weights_sum, 0.0): raise ValueError("label_weights sum cannot be zero") np_weights = np_weights / label_weights_sum knn_kwargs = {} @@ -894,7 +893,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel): finite_max_val = np.max(finite_col) finite_range_val = finite_max_val - finite_min_val - if np.isclose(finite_range_val, 0): + if np.isclose(finite_range_val, 0.0): if np.any(is_pos_inf_mask) and np.any(is_neg_inf_mask): normalized_matrix[is_finite_mask, i] = 0.5 elif np.any(is_pos_inf_mask): @@ -1341,33 +1340,17 @@ def label_objective( fit_live_predictions_candles: int, candles_step: int, ) -> tuple[float, int]: - fit_live_predictions_candles_largest_divisor = largest_divisor( - fit_live_predictions_candles, candles_step - ) - if fit_live_predictions_candles_largest_divisor is None: - raise ValueError( - f"Could not find a suitable largest divisor for {fit_live_predictions_candles} with step {candles_step}, please change your fit_live_predictions_candles or candles_step parameters" - ) - min_label_period_candles: int = round_to_nearest_int( - max( - fit_live_predictions_candles - // fit_live_predictions_candles_largest_divisor, - candles_step, - 12, - ), - candles_step, - ) - max_label_period_candles: int = round_to_nearest_int( - max(fit_live_predictions_candles // 24, min_label_period_candles, 22), - candles_step, + min_label_period_candles, max_label_period_candles, candles_step = ( + get_min_max_label_period_candles(candles_step) ) + label_period_candles = trial.suggest_int( "label_period_candles", min_label_period_candles, max_label_period_candles, step=candles_step, ) - label_natr_ratio = trial.suggest_float("label_natr_ratio", 2.0, 44.0, step=0.01) + label_natr_ratio = trial.suggest_float("label_natr_ratio", 2.0, 48.0, step=0.01) label_period_cycles = fit_live_predictions_candles / label_period_candles df = df.iloc[-(max(2, int(label_period_cycles)) * label_period_candles) :] diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index fc45b8f..b06da77 100644 --- a/quickadapter/user_data/strategies/QuickAdapterV3.py +++ b/quickadapter/user_data/strategies/QuickAdapterV3.py @@ -65,7 +65,7 @@ class QuickAdapterV3(IStrategy): INTERFACE_VERSION = 3 def version(self) -> str: - return "3.3.151" + return "3.3.154" timeframe = "5m" @@ -641,7 +641,7 @@ class QuickAdapterV3(IStrategy): median_weight = calculate_weight(median_quantile) total_weight = entry_weight + current_weight + median_weight - if np.isclose(total_weight, 0): + if np.isclose(total_weight, 0.0): return None entry_weight /= total_weight current_weight /= total_weight diff --git a/quickadapter/user_data/strategies/Utils.py b/quickadapter/user_data/strategies/Utils.py index 40bbc85..7b9b623 100644 --- a/quickadapter/user_data/strategies/Utils.py +++ b/quickadapter/user_data/strategies/Utils.py @@ -930,7 +930,7 @@ def soft_extremum(series: pd.Series, alpha: float) -> float: np_array = series.to_numpy() if np_array.size == 0: return np.nan - if np.isclose(alpha, 0): + if np.isclose(alpha, 0.0): return np.mean(np_array) scaled_np_array = alpha * np_array max_scaled_np_array = np.max(scaled_np_array) @@ -944,15 +944,66 @@ def soft_extremum(series: pd.Series, alpha: float) -> float: return numerator / denominator +@lru_cache(maxsize=8) +def get_min_max_label_period_candles( + candles_step: int, + min_label_period_candles: int = 8, + max_label_period_candles: int = 48, +) -> tuple[int, int, int]: + if min_label_period_candles > max_label_period_candles: + min_label_period_candles, max_label_period_candles = ( + max_label_period_candles, + min_label_period_candles, + ) + + if candles_step <= (max_label_period_candles - min_label_period_candles): + low = ceil_to_step(min_label_period_candles, candles_step) + high = floor_to_step(max_label_period_candles, candles_step) + if low > high: + low, high, candles_step = ( + min_label_period_candles, + max_label_period_candles, + 1, + ) + else: + low, high, candles_step = min_label_period_candles, max_label_period_candles, 1 + + return low, high, candles_step + + @lru_cache(maxsize=128) -def round_to_nearest_int(value: float, step: int) -> int: +def round_to_step(value: float | int, step: int) -> int: """ Round a value to the nearest multiple of a given step. :param value: The value to round. - :param step: The step size to round to (must be non-zero). + :param step: The step size to round to (must be a positive integer). :return: The rounded value. - :raises ValueError: If step is zero. + :raises ValueError: If step is not a positive integer or value is not finite. """ + if not np.isfinite(value): + raise ValueError("value must be finite") + if not isinstance(step, int) or step <= 0: + raise ValueError("step must be a positive integer") + return int(round(float(value) / step) * step) + + +@lru_cache(maxsize=128) +def ceil_to_step(value: float | int, step: int) -> int: + if not isinstance(step, int) or step <= 0: + raise ValueError("step must be a positive integer") + if isinstance(value, (int, np.integer)): + return int(-(-int(value) // step) * step) + if not np.isfinite(value): + raise ValueError("value must be finite") + return int(math.ceil(float(value) / step) * step) + + +@lru_cache(maxsize=128) +def floor_to_step(value: float | int, step: int) -> int: if not isinstance(step, int) or step <= 0: raise ValueError("step must be a positive integer") - return int(round(value / step) * step) + if isinstance(value, (int, np.integer)): + return int((int(value) // step) * step) + if not np.isfinite(value): + raise ValueError("value must be finite") + return int(math.floor(float(value) / step) * step)