]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
perf(qav3): fine tune pivots labeling optuna search space
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Sun, 10 Aug 2025 20:11:29 +0000 (22:11 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Sun, 10 Aug 2025 20:11:29 +0000 (22:11 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py
quickadapter/user_data/strategies/QuickAdapterV3.py
quickadapter/user_data/strategies/Utils.py

index 69249517bde103cbc909d51f2b58c637f3809aac..dca9aef52eb585ef2dd4ffc4065a559d96b63499 100644 (file)
@@ -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) :]
index fc45b8fbcc7c942288a29d0937a4d5fd2642c159..b06da77aa36c28e45f649c4f1398385d8d7f27eb 100644 (file)
@@ -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
index 40bbc8585e0542c85ae8fe447eaf30952378d645..7b9b623d34e27ae4dfeb7fb736b1ddce38864578 100644 (file)
@@ -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)