]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
refactor(quickadapter): post-merge harmonization follow-up (#99)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Mon, 22 Jun 2026 02:12:50 +0000 (04:12 +0200)
committerGitHub <noreply@github.com>
Mon, 22 Jun 2026 02:12:50 +0000 (04:12 +0200)
Consolidates SAFE + DRY + COSMETIC findings from the 4-axis review of
`add1fb7..d6a718f` (PRs #90, #94, #95, #96, #97 + 2 style + d6a718f).

Migration (PR #94 carry-over to `QuickAdapterV3.py`):
- `_TRADE_DIRECTIONS_SET` and `_ORDER_TYPES_SET` are
  `Final[frozenset[T]]` constants adjacent to the canonical
  `_TRADE_DIRECTIONS` / `_ORDER_TYPES` tuples; the strategy reads
  them directly at all 3 call sites. The
  `@staticmethod @lru_cache(maxsize=None)` set-views are absent at
  HEAD.

Context-prefix harmonization (PR #97 carry-over):
- The 2 `sanitize_and_renormalize` calls in
  `QuickAdapterRegressorV3._apply_pipelines` carry the `[<pair>]`
  prefix (`f"[{pair}] post_feature_pipeline:train"` / `:test`),
  matching the PR #97 convention at the 4 `compose_sample_weights`
  call sites.

Alias removal (PR #96 carry-over):
- `Utils.label_known_at_column_name` is absent at HEAD. The
  underlying column has zero external callers in the repo, and
  PR #96 shifted semantics (absolute index -> per-row offset);
  no back-compat alias is warranted.

DRY (`d6a718f` carry-over):
- The 8-tuple swap blocks in `_make_train_test_split_datasets`
  and `_make_timeseries_split_datasets` use pythonic parallel
  pair-swap (`a, b = b, a` per slot pair); 4 lines per slot pair
  instead of the 18-line 8-tuple parallel assignment.

Dead constant removal:
- `QuickAdapterRegressorV3._AGGREGATE_DISTANCE_METRICS_SET` is
  absent at HEAD; the constant has no caller. The
  `_CLUSTER_DENSITY_DISTANCE_METRICS_SET` block comment lists the
  7 aggregate metrics inline for reference (`harmonic_mean`,
  `geometric_mean`, `arithmetic_mean`, `quadratic_mean`,
  `cubic_mean`, `power_mean`, `weighted_sum`).

Cosmetic:
- `_LABEL_KNOWN_AT_LOOKAHEAD_SUFFIX` placement is adjacent to
  `LABEL_WEIGHT_SUFFIX` (both are label-aux column suffixes).
- `_known_at_lookahead` returns `int64` on both single-series and
  multi-series paths (symmetric dtype contract).
- Schema-version reset log: `f"v{existing_schema_version!r}"`
  renders unambiguously for non-int corrupt values (booleans,
  strings).

quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py
quickadapter/user_data/strategies/QuickAdapterV3.py
quickadapter/user_data/strategies/Utils.py

index 8e7d7b2b341c1ce99ddf1e2215acc97185c21081..17f29c011bb8b86c5901490d257563499256fc77 100644 (file)
@@ -306,21 +306,12 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
     _LABEL_SELECTION_DISTANCE_METRICS_SET: Final[frozenset[str]] = (
         _DISTANCE_METRICS_SET - _PROBABILITY_DISTANCE_METRICS_SET
     )
-    # Aggregate metrics: distance metrics computed by reduction over
-    # objective coordinates rather than SciPy/sklearn pairwise routines
-    # (``harmonic_mean``, ``geometric_mean``, ``arithmetic_mean``,
-    # ``quadratic_mean``, ``cubic_mean``, ``power_mean``, ``weighted_sum``).
-    # Accepted by ``compromise_programming``/``topsis`` via
-    # ``_calculate_trial_distance_to_ideal``; rejected by cluster/density
-    # categories that route to ``pairwise_distances``/``KMeans``/
-    # ``KMedoids``/``NearestNeighbors``.
-    _AGGREGATE_DISTANCE_METRICS_SET: Final[frozenset[str]] = (
-        _DISTANCE_METRICS_SET - _SCIPY_METRICS_SET - _PROBABILITY_DISTANCE_METRICS_SET
-    )
     # SciPy-compatible non-probability metrics. Accepted by cluster/density
     # categories that route to ``pairwise_distances``/``KMeans``/
-    # ``KMedoids``/``NearestNeighbors``; rejected by the aggregate set and
-    # by probability metrics.
+    # ``KMedoids``/``NearestNeighbors``; rejected by aggregate metrics
+    # (``harmonic_mean``, ``geometric_mean``, ``arithmetic_mean``,
+    # ``quadratic_mean``, ``cubic_mean``, ``power_mean``, ``weighted_sum``)
+    # and by probability metrics.
     _CLUSTER_DENSITY_DISTANCE_METRICS_SET: Final[frozenset[str]] = (
         _SCIPY_METRICS_SET - _PROBABILITY_DISTANCE_METRICS_SET
     )
@@ -485,7 +476,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         if not series_list:
             return None
         if len(series_list) == 1:
-            return series_list[0]
+            return series_list[0].astype(np.int64)
         return pd.concat(series_list, axis=1).max(axis=1).astype(np.int64)
 
     @staticmethod
@@ -969,7 +960,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         else:
             # Cluster/density paths route the metric to SciPy/sklearn APIs
             # (pairwise_distances, KMeans, KMedoids, NearestNeighbors) which
-            # reject `_AGGREGATE_DISTANCE_METRICS_SET`; restrict the
+            # reject aggregate metrics computed by reduction; restrict the
             # valid set to SciPy-compatible non-probability metrics.
             valid_metrics = (
                 QuickAdapterRegressorV3._CLUSTER_DENSITY_DISTANCE_METRICS_SET
@@ -1990,22 +1981,13 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                 )
 
         if feat_dict.get("reverse_train_test_order", False):
-            (
-                train_features,
-                test_features,
-                train_labels,
-                test_labels,
-                train_base_weights,
-                test_base_weights,
-                train_label_weights,
-                test_label_weights,
-            ) = (
-                test_features,
-                train_features,
-                test_labels,
-                train_labels,
+            train_features, test_features = test_features, train_features
+            train_labels, test_labels = test_labels, train_labels
+            train_base_weights, test_base_weights = (
                 test_base_weights,
                 train_base_weights,
+            )
+            train_label_weights, test_label_weights = (
                 test_label_weights,
                 train_label_weights,
             )
@@ -2167,7 +2149,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         dd["train_weights"] = sanitize_and_renormalize(
             dd["train_weights"],
             logger=logger,
-            context="post_feature_pipeline:train",
+            context=f"[{pair}] post_feature_pipeline:train",
         )
         dd["train_labels"], _, _ = dk.label_pipeline.fit_transform(dd["train_labels"])
 
@@ -2220,7 +2202,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                 dd["test_weights"] = sanitize_and_renormalize(
                     dd["test_weights"],
                     logger=logger,
-                    context="post_feature_pipeline:test",
+                    context=f"[{pair}] post_feature_pipeline:test",
                 )
                 dd["test_labels"], _, _ = dk.label_pipeline.transform(dd["test_labels"])
 
@@ -2387,22 +2369,13 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                 _log_known_at_none_once(dk.pair, "timeseries_split causal guard")
 
         if feat_dict.get("reverse_train_test_order", False):
-            (
-                train_features,
-                test_features,
-                train_labels,
-                test_labels,
-                train_base_weights,
-                test_base_weights,
-                train_label_weights,
-                test_label_weights,
-            ) = (
-                test_features,
-                train_features,
-                test_labels,
-                train_labels,
+            train_features, test_features = test_features, train_features
+            train_labels, test_labels = test_labels, train_labels
+            train_base_weights, test_base_weights = (
                 test_base_weights,
                 train_base_weights,
+            )
+            train_label_weights, test_label_weights = (
                 test_label_weights,
                 train_label_weights,
             )
@@ -4362,7 +4335,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                     version_repr = (
                         "none"
                         if existing_schema_version is None
-                        else f"v{existing_schema_version}"
+                        else f"v{existing_schema_version!r}"
                     )
                     logger.warning(
                         f"[{pair}] Optuna {namespace} study {study_name}: "
index 98f0a569cfc28f1bb6f945a37db5bf4ef2e24c97..9ec6e08b2340975924b08971acb882b1eff0d6ef 100644 (file)
@@ -109,11 +109,15 @@ class QuickAdapterV3(IStrategy):
     INTERFACE_VERSION = 3
 
     _TRADE_DIRECTIONS: Final[tuple[TradeDirection, ...]] = ("long", "short")
+    _TRADE_DIRECTIONS_SET: Final[frozenset[TradeDirection]] = frozenset(
+        _TRADE_DIRECTIONS
+    )
     _INTERPOLATION_DIRECTIONS: Final[tuple[InterpolationDirection, ...]] = (
         "direct",
         "inverse",
     )
     _ORDER_TYPES: Final[tuple[OrderType, ...]] = ("entry", "exit")
+    _ORDER_TYPES_SET: Final[frozenset[OrderType]] = frozenset(_ORDER_TYPES)
     _TRADING_MODES: Final[tuple[TradingMode, ...]] = ("spot", "margin", "futures")
 
     _CUSTOM_STOPLOSS_NATR_MULTIPLIER_FRACTION: Final[float] = 0.7860
@@ -184,16 +188,6 @@ class QuickAdapterV3(IStrategy):
     def timeframe_minutes(self) -> int:
         return timeframe_to_minutes(self.config.get("timeframe"))
 
-    @staticmethod
-    @lru_cache(maxsize=None)
-    def _trade_directions_set() -> set[TradeDirection]:
-        return set(QuickAdapterV3._TRADE_DIRECTIONS)
-
-    @staticmethod
-    @lru_cache(maxsize=None)
-    def _order_types_set() -> set[OrderType]:
-        return set(QuickAdapterV3._ORDER_TYPES)
-
     @property
     def can_short(self) -> bool:
         return self.is_short_allowed()
@@ -1780,9 +1774,9 @@ class QuickAdapterV3(IStrategy):
         """
         if df.empty:
             return False
-        if side not in QuickAdapterV3._trade_directions_set():
+        if side not in QuickAdapterV3._TRADE_DIRECTIONS_SET:
             return False
-        if order not in QuickAdapterV3._order_types_set():
+        if order not in QuickAdapterV3._ORDER_TYPES_SET:
             return False
         if not isinstance(rate, (int, float)) or not np.isfinite(rate):
             return False
@@ -2209,7 +2203,7 @@ class QuickAdapterV3(IStrategy):
         side: str,
         **kwargs,
     ) -> bool:
-        if side not in QuickAdapterV3._trade_directions_set():
+        if side not in QuickAdapterV3._TRADE_DIRECTIONS_SET:
             return False
         if (
             side == QuickAdapterV3._TRADE_DIRECTIONS[1] and not self.can_short
index 5b5d5cb524a0a2ed4d84044843f75237086a8e48..6cd5a888af956f428553c0bad940198034535a89 100644 (file)
@@ -531,9 +531,9 @@ EXTREMA_DIRECTION_COLUMN: Final[str] = "extrema_direction"
 EXTREMA_DIRECTION_SMOOTHED_COLUMN: Final[str] = "extrema_direction_smoothed"
 EXTREMA_WEIGHT_COLUMN: Final[str] = "extrema_weight"
 EXTREMA_WEIGHT_SMOOTHED_COLUMN: Final[str] = "extrema_weight_smoothed"
-_LABEL_KNOWN_AT_LOOKAHEAD_SUFFIX: Final[str] = "_known_at_lookahead"
 
 LABEL_WEIGHT_SUFFIX: Final[str] = "_weight"
+_LABEL_KNOWN_AT_LOOKAHEAD_SUFFIX: Final[str] = "_known_at_lookahead"
 
 LABEL_COLUMNS: Final[tuple[str, ...]] = (EXTREMA_COLUMN,)
 
@@ -583,9 +583,6 @@ def label_known_at_lookahead_column_name(label_col: str) -> str:
     return _label_aux_column_name(label_col, _LABEL_KNOWN_AT_LOOKAHEAD_SUFFIX)
 
 
-label_known_at_column_name = label_known_at_lookahead_column_name
-
-
 @dataclass
 class LabelData:
     """Output of a label generator.