]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
fix(quickadapter): route reversed train weights through support_policy (#98)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Mon, 22 Jun 2026 01:54:59 +0000 (03:54 +0200)
committerGitHub <noreply@github.com>
Mon, 22 Jun 2026 01:54:59 +0000 (03:54 +0200)
PR #85 added `_compose_train_weights_with_support` (gates training-set weights through `support_policy`) and `_compose_eval_weights` (eval-side, deliberately bypasses `support_policy`). The `reverse_train_test_order` path in `_make_train_test_split_datasets` and `_make_timeseries_split_datasets` swapped slices AT THE FINAL `build_data_dictionary` call -- AFTER weight composition -- so the actual-train slot received weights composed by `_compose_eval_weights` (silent bypass), and the actual-test slot received weights composed by `_compose_train_weights_with_support` (wrong direction, typically a no-op under `support_policy='fallback'` default).

Reachable only under `causal_mode=false` (deprecated; acausal baselines only) since `causal_mode=true` rejects `reverse_train_test_order=true` upfront.

Fix: perform the train/test slice swap BEFORE weight composition so the `train_*` and `test_*` identifiers map to their actual training roles throughout. Both call sites converge to a single `dk.build_data_dictionary` return; context strings in `support_policy` log/raise messages now reflect the true train/test role.

Add an upfront `ValueError` in `_make_train_test_split_datasets` when `test_size=0` AND `reverse_train_test_order=True`, mirroring the existing `causal_mode`/reverse rejection pattern. The `timeseries_split` path already rejects `test_size < 1` upstream of the swap.

Behavior change in the deprecated path: `support_policy='raise'` now correctly raises on actual-train insufficient support; `support_policy='fallback'` now correctly warns.

Reviewed by three parallel Oracle passes (math + algorithmics + scope/reachability; Python state-of-the-art + harmonization + implementation elegance; documentation + terminology + completeness) at design stage and again post-implementation, each citing upstream evidence from `freqtrade/freqai/`.

Follow-up from PR #80 review, deferred during PR #90.

Closes #92.

quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py

index fc408314cc041d3b7ce0d863bd74eb9c8630f744..4188140695533339bfa83bd521fb29507e4b0b4f 100644 (file)
@@ -1883,6 +1883,12 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                 f"Invalid data_split_parameters.test_size value {test_size!r}: "
                 f"must be int or float"
             )
+        if test_size == 0 and feat_dict.get("reverse_train_test_order", False):
+            raise ValueError(
+                "data_split_parameters.test_size=0 is incompatible with "
+                "feature_parameters.reverse_train_test_order=True: the empty "
+                "test slice cannot be promoted to the training slot"
+            )
 
         if test_size != 0:
             if weights.label is None:
@@ -1983,6 +1989,19 @@ 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,
+                test_base_weights, train_base_weights,
+                test_label_weights, train_label_weights,
+            )
+
         train_weights = QuickAdapterRegressorV3._compose_train_weights_with_support(
             train_base_weights,
             train_label_weights,
@@ -1998,15 +2017,6 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         else:
             test_weights = test_base_weights
 
-        if feat_dict.get("reverse_train_test_order", False):
-            return dk.build_data_dictionary(
-                test_features,
-                train_features,
-                test_labels,
-                train_labels,
-                test_weights,
-                train_weights,
-            )
         return dk.build_data_dictionary(
             train_features,
             test_features,
@@ -2336,11 +2346,6 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             None if weights.label is None else weights.label[train_idx]
         )
         test_label_weights = None if weights.label is None else weights.label[test_idx]
-        test_weights = QuickAdapterRegressorV3._compose_eval_weights(
-            test_base_weights,
-            test_label_weights,
-            context=f"[{dk.pair}] timeseries_split:test",
-        )
 
         if causal_mode:
             row_positions = QuickAdapterRegressorV3._row_positions(
@@ -2373,22 +2378,31 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             else:
                 _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,
+                test_base_weights, train_base_weights,
+                test_label_weights, train_label_weights,
+            )
+
         train_weights = QuickAdapterRegressorV3._compose_train_weights_with_support(
             train_base_weights,
             train_label_weights,
             weights.label_weighting_config,
             context=f"[{dk.pair}] timeseries_split:train",
         )
+        test_weights = QuickAdapterRegressorV3._compose_eval_weights(
+            test_base_weights,
+            test_label_weights,
+            context=f"[{dk.pair}] timeseries_split:test",
+        )
 
-        if feat_dict.get("reverse_train_test_order", False):
-            return dk.build_data_dictionary(
-                test_features,
-                train_features,
-                test_labels,
-                train_labels,
-                test_weights,
-                train_weights,
-            )
         return dk.build_data_dictionary(
             train_features,
             test_features,