From: Jérôme Benoit Date: Wed, 4 Nov 2026 15:37:25 +0000 (+0100) Subject: fix(quickadapter): align extrema retention defaults (#304) X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=d641f6e5608ae831629ee8fc65100f272f25617b;p=freqai-strategies.git fix(quickadapter): align extrema retention defaults (#304) --- diff --git a/quickadapter/README.md b/quickadapter/README.md index c092d4d..05e7ced 100644 --- a/quickadapter/README.md +++ b/quickadapter/README.md @@ -446,7 +446,7 @@ default five splits. | freqai.label_prediction.threshold_method | `mean` | enum {`mean`,`isodata`,`li`,`minimum`,`otsu`,`triangle`,`yen`,`median`,`soft_extremum`} | Thresholding method for prediction thresholds. | | freqai.label_prediction.soft_extremum_alpha | 12.0 | float >= 0 | Alpha for `soft_extremum` threshold method. | | freqai.label_prediction.outlier_quantile | 0.999 | float (0,1) | Quantile threshold for predictions outlier filtering. | -| freqai.label_prediction.keep_fraction | 0.0075 | float (0,1] | Fraction of extrema used for thresholds. 1 uses all, lower values keep only most significant. Applies to `rank_extrema` and `rank_peaks`; ignored for `partition`. | +| freqai.label_prediction.keep_fraction | 0.005 | float (0,1] | Fraction of extrema used for thresholds. 1 uses all, lower values keep only most significant. Applies to `rank_extrema` and `rank_peaks`; ignored for `partition`. | ### Optuna / HPO diff --git a/quickadapter/tests/test_label_transformer.py b/quickadapter/tests/test_label_transformer.py index 28f1234..9f3a91c 100644 --- a/quickadapter/tests/test_label_transformer.py +++ b/quickadapter/tests/test_label_transformer.py @@ -332,8 +332,8 @@ class LabelTransformerTest(QaTestCase): self.assertEqual(DEFAULTS_LABEL_PIPELINE, before) def test_a_flat_config_dict_is_read_as_the_default_block(self): - # The regressor hands over whatever get_label_pipeline_config produced, which may carry - # pipeline keys at the top level instead of under "default". + # Direct LabelTransformer callers may pass a flat pipeline configuration. + # The regressor instead passes the normalized "default"/"columns" mapping. # # A round trip alone is satisfied by ANY invertible pipeline, including one that # ignores the flat dict entirely and falls back to `DEFAULTS_LABEL_PIPELINE` diff --git a/quickadapter/tests/test_regressor_prediction_history.py b/quickadapter/tests/test_regressor_prediction_history.py index 6e87c0a..03bf6c4 100644 --- a/quickadapter/tests/test_regressor_prediction_history.py +++ b/quickadapter/tests/test_regressor_prediction_history.py @@ -9,6 +9,7 @@ import numpy as np import pandas as pd import scipy as sp from freqtrade.freqai.freqai_interface import IFreqaiModel +from LabelTransformer import DEFAULTS_LABEL_PREDICTION from qa_support import PAIR, QaTestCase from Utils import _OPTUNA_NAMESPACES, DEFAULTS_LABEL_PIPELINE @@ -132,14 +133,7 @@ def calibrated( model = regressor( _fit_live_predictions_candles=candles if budget is None else budget, label_prediction={ - "default": { - "method": "thresholding", - "selection_method": "rank_extrema", - "threshold_method": "mean", - "outlier_quantile": 0.999, - "soft_extremum_alpha": 12.0, - "keep_fraction": 0.0075, - }, + "default": dict(DEFAULTS_LABEL_PREDICTION), "columns": {}, }, min_max_pred=lambda *args, **kwargs: (-0.4, 0.6), diff --git a/quickadapter/tests/test_regressor_selection.py b/quickadapter/tests/test_regressor_selection.py index e823b02..98c9013 100644 --- a/quickadapter/tests/test_regressor_selection.py +++ b/quickadapter/tests/test_regressor_selection.py @@ -1233,10 +1233,10 @@ class RegressorSelectionTest(QaTestCase): # size 0 is the one case that may legitimately keep nothing, and keep_fraction # does not force a candidate out of a set that does not exist. self.assertEqual(QuickAdapterRegressorV3._calculate_n_kept_extrema(0, 1.0), 0) - self.assertEqual(QuickAdapterRegressorV3._calculate_n_kept_extrema(0, 0.0075), 0) + self.assertEqual(QuickAdapterRegressorV3._calculate_n_kept_extrema(0, 0.005), 0) def test_a_non_empty_extremum_set_always_keeps_at_least_one(self): - for keep_fraction in (0.0, 0.0075, 0.5, 1.0): + for keep_fraction in (0.0, 0.005, 0.5, 1.0): with self.subTest(keep_fraction=keep_fraction): self.assertGreaterEqual( QuickAdapterRegressorV3._calculate_n_kept_extrema(10, keep_fraction), 1 @@ -1244,10 +1244,10 @@ class RegressorSelectionTest(QaTestCase): def test_the_kept_count_uses_banker_rounding_not_ceiling(self): # round() is round-half-to-even: 133 * 0.5 == 66.5 keeps 66, not 67, and a - # keep_fraction of 0.0075 over 133 extrema still keeps only the floor of one. + # keep_fraction of 0.005 over 133 extrema still keeps only the floor of one. self.assertEqual(QuickAdapterRegressorV3._calculate_n_kept_extrema(133, 0.5), 66) self.assertEqual(QuickAdapterRegressorV3._calculate_n_kept_extrema(3, 0.5), 2) - self.assertEqual(QuickAdapterRegressorV3._calculate_n_kept_extrema(133, 0.0075), 1) + self.assertEqual(QuickAdapterRegressorV3._calculate_n_kept_extrema(133, 0.005), 1) self.assertEqual(QuickAdapterRegressorV3._calculate_n_kept_extrema(10, 0.5), 5) self.assertEqual(QuickAdapterRegressorV3._calculate_n_kept_extrema(10, 1.0), 10) diff --git a/quickadapter/user_data/config-template.json b/quickadapter/user_data/config-template.json index 70395cb..def7731 100644 --- a/quickadapter/user_data/config-template.json +++ b/quickadapter/user_data/config-template.json @@ -164,7 +164,7 @@ "label_prediction": { "method": "thresholding", "threshold_method": "isodata", - "keep_fraction": 0.0075 + "keep_fraction": 0.005 // Alternative per-label format: remove the sibling keys above, then uncomment: // "default": { // "method": "thresholding", diff --git a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py index abc1cc8..29389d4 100644 --- a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py +++ b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py @@ -3603,10 +3603,10 @@ class QuickAdapterRegressorV3(BaseRegressionModel): """Chronological train/test split using sklearn's TimeSeriesSplit final fold. ``n_splits`` controls train/test proportions (higher = larger train). - ``gap`` excludes samples between train and test; when 0, auto-derived - from ``label_period_candles``. ``max_train_size`` enables sliding - window mode. ``weights`` is sliced positionally via ``train_idx`` / - ``test_idx``. + ``gap`` excludes samples between train and test. When 0, it uses the label + horizon in causal mode, otherwise ``label_period_candles``. + ``max_train_size`` enables sliding windows. ``weights`` is sliced + positionally via ``train_idx`` / ``test_idx``. """ feat_dict = self.ft_params causal_mode = self._causal_mode diff --git a/quickadapter/user_data/strategies/LabelTransformer.py b/quickadapter/user_data/strategies/LabelTransformer.py index b510142..0d732cc 100644 --- a/quickadapter/user_data/strategies/LabelTransformer.py +++ b/quickadapter/user_data/strategies/LabelTransformer.py @@ -236,7 +236,7 @@ DEFAULTS_LABEL_PREDICTION: Final[dict[str, Any]] = { "threshold_method": SKIMAGE_THRESHOLD_METHODS[0], # "mean" "outlier_quantile": 0.999, "soft_extremum_alpha": 12.0, - "keep_fraction": 0.0075, + "keep_fraction": 0.005, }