From: Jérôme Benoit Date: Mon, 22 Jun 2026 02:12:50 +0000 (+0200) Subject: refactor(quickadapter): post-merge harmonization follow-up (#99) X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=653cb07ec06c57510b70b5e7421a87bd19a17053;p=freqai-strategies.git refactor(quickadapter): post-merge harmonization follow-up (#99) 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 `[]` 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). --- diff --git a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py index 8e7d7b2..17f29c0 100644 --- a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py +++ b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py @@ -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}: " diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index 98f0a56..9ec6e08 100644 --- a/quickadapter/user_data/strategies/QuickAdapterV3.py +++ b/quickadapter/user_data/strategies/QuickAdapterV3.py @@ -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 diff --git a/quickadapter/user_data/strategies/Utils.py b/quickadapter/user_data/strategies/Utils.py index 5b5d5cb..6cd5a88 100644 --- a/quickadapter/user_data/strategies/Utils.py +++ b/quickadapter/user_data/strategies/Utils.py @@ -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.