From fb8214970b28d441ca2bb7ebda538496692625f8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 21 Nov 2025 18:21:13 +0100 Subject: [PATCH] fix(qav3): correct normalization bounds and consolidate defaults MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit - Fix tanh normalization formula to guarantee [0, gain] range Update formula: gain * 0.5 * (tanh(scale * z) + 1.0) Update default tanh_gain: 0.5 → 1.0 - Fix robust normalization to ensure [0,1] bounded output Add min-max rescaling after IQR standardization - Refactor normalization functions to use canonical DEFAULTS_EXTREMA_WEIGHTING Eliminates hardcoded values in _normalize_{robust,softmax,tanh,rank} --- README.md | 2 +- quickadapter/user_data/strategies/Utils.py | 29 ++++++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1a19d8a..d983bf9 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ docker compose up -d --build | freqai.extrema_weighting.gamma | 1.0 | float (0,10] | Contrast exponent applied after normalization (>1 emphasizes extremes, 0 0 | Temperature parameter for softmax normalization (lower values sharpen distribution, higher values flatten it). | | freqai.extrema_weighting.tanh_scale | 1.0 | float > 0 | Scale parameter for tanh normalization. | -| freqai.extrema_weighting.tanh_gain | 0.5 | float > 0 | Gain parameter for tanh normalization. | +| freqai.extrema_weighting.tanh_gain | 1.0 | float > 0 | Gain parameter for tanh normalization. | | freqai.extrema_weighting.robust_quantiles | [0.25, 0.75] | list[float] where 0 <= q_low < q_high <= 1 | Quantile range for robust normalization. | | freqai.extrema_weighting.rank_method | `average` | enum {`average`,`min`,`max`,`dense`,`ordinal`} | Ranking method for rank normalization. | | _Feature parameters_ | | | | diff --git a/quickadapter/user_data/strategies/Utils.py b/quickadapter/user_data/strategies/Utils.py index 2f0ad92..97ee645 100644 --- a/quickadapter/user_data/strategies/Utils.py +++ b/quickadapter/user_data/strategies/Utils.py @@ -68,8 +68,7 @@ DEFAULTS_EXTREMA_WEIGHTING: Final[dict[str, Any]] = { "strategy": WEIGHT_STRATEGIES[0], # "none" "softmax_temperature": 1.0, "tanh_scale": 1.0, - "tanh_gain": 0.5, - "robust_quantiles": (0.25, 0.75), + "tanh_gain": 1.0, "rank_method": RANK_METHODS[0], # "average" } @@ -274,7 +273,8 @@ def _normalize_l2(weights: NDArray[np.floating]) -> NDArray[np.floating]: def _normalize_robust( - weights: NDArray[np.floating], quantiles: tuple[float, float] = (0.25, 0.75) + weights: NDArray[np.floating], + quantiles: tuple[float, float] = DEFAULTS_EXTREMA_WEIGHTING["robust_quantiles"], ) -> NDArray[np.floating]: weights = weights.astype(float, copy=False) if np.isnan(weights).any(): @@ -287,12 +287,22 @@ def _normalize_robust( if np.isclose(iqr, 0.0): return np.full_like(weights, float(DEFAULT_EXTREMA_WEIGHT), dtype=float) - normalized_weights = (weights - median) / iqr + robust_scores = (weights - median) / iqr + + r_min = np.min(robust_scores) + r_max = np.max(robust_scores) + r_range = r_max - r_min + + if np.isclose(r_range, 0.0): + return np.full_like(weights, float(DEFAULT_EXTREMA_WEIGHT), dtype=float) + + normalized_weights = (robust_scores - r_min) / r_range return normalized_weights def _normalize_softmax( - weights: NDArray[np.floating], temperature: float = 1.0 + weights: NDArray[np.floating], + temperature: float = DEFAULTS_EXTREMA_WEIGHTING["softmax_temperature"], ) -> NDArray[np.floating]: weights = weights.astype(float, copy=False) if np.isnan(weights).any(): @@ -303,19 +313,22 @@ def _normalize_softmax( def _normalize_tanh( - weights: NDArray[np.floating], scale: float = 1.0, gain: float = 0.5 + weights: NDArray[np.floating], + scale: float = DEFAULTS_EXTREMA_WEIGHTING["tanh_scale"], + gain: float = DEFAULTS_EXTREMA_WEIGHTING["tanh_gain"], ) -> NDArray[np.floating]: weights = weights.astype(float, copy=False) if np.isnan(weights).any(): return np.full_like(weights, float(DEFAULT_EXTREMA_WEIGHT), dtype=float) z_scores = _normalize_zscore(weights, rescale_to_unit_range=False) - normalized_weights = gain * (np.tanh(scale * z_scores) + 1.0) + normalized_weights = gain * 0.5 * (np.tanh(scale * z_scores) + 1.0) return normalized_weights def _normalize_rank( - weights: NDArray[np.floating], method: RankMethod = "average" + weights: NDArray[np.floating], + method: RankMethod = DEFAULTS_EXTREMA_WEIGHTING["rank_method"], ) -> NDArray[np.floating]: weights = weights.astype(float, copy=False) if np.isnan(weights).any(): -- 2.43.0