]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
feat(quickadapter): add multiple aggregation methods for combined extrema weighting...
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Mon, 5 Jan 2026 20:41:43 +0000 (21:41 +0100)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Mon, 5 Jan 2026 20:41:43 +0000 (21:41 +0100)
- Add 5 new aggregation methods: arithmetic_mean, harmonic_mean, quadratic_mean, weighted_median, softmax
- Replace weighted_average (deprecated) with arithmetic_mean as new default
- Add softmax_temperature parameter (default: 1.0) for softmax aggregation
- Implement all methods using scipy.stats.pmean for power means (p=1,-1,2) and numpy for weighted_median
- Add softmax aggregation with temperature scaling and coefficient weighting
- Add validation and logging for softmax_temperature parameter
- Update README with precise mathematical formulas for all aggregation methods
- Bump version to 3.10.4 in strategy and model
- Add conditional logging for softmax_temperature when aggregation is softmax

README.md
quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py
quickadapter/user_data/strategies/ExtremaWeightingTransformer.py
quickadapter/user_data/strategies/QuickAdapterV3.py
quickadapter/user_data/strategies/Utils.py

index c445b7c1e06b73d4cc7e7d98ba663d86c857480b..20560ad9911e2aabc025f21b925c381b8ed7d46b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -69,7 +69,8 @@ docker compose up -d --build
 | _Extrema weighting_                                            |                               |                                                                                                                                              |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
 | freqai.extrema_weighting.strategy                              | `none`                        | enum {`none`,`amplitude`,`amplitude_threshold_ratio`,`volume_rate`,`speed`,`efficiency_ratio`,`volume_weighted_efficiency_ratio`,`combined`} | Extrema weighting metric: none (`none`), swing amplitude (`amplitude`), swing amplitude / median volatility-threshold ratio (`amplitude_threshold_ratio`), swing volume per candle (`volume_rate`), swing speed (`speed`), swing efficiency ratio (`efficiency_ratio`), swing volume-weighted efficiency ratio (`volume_weighted_efficiency_ratio`), or combined metrics aggregation (`combined`). Switching between `none` and any other strategy requires deleting trained models. |
 | freqai.extrema_weighting.metric_coefficients                   | {}                            | dict[str, float]                                                                                                                             | Per-metric coefficients for `combined` strategy. Keys: `amplitude`, `amplitude_threshold_ratio`, `volume_rate`, `speed`, `efficiency_ratio`, `volume_weighted_efficiency_ratio`.                                                                                                                                                                                                                                                                                                     |
-| freqai.extrema_weighting.aggregation                           | `weighted_average`            | enum {`weighted_average`,`geometric_mean`}                                                                                                   | Metric aggregation method for `combined` strategy. `weighted_average`=Σ(coef·metric)/Σ(coef), `geometric_mean`=∏(metric^coef)^(1/Σcoef).                                                                                                                                                                                                                                                                                                                                             |
+| freqai.extrema_weighting.aggregation                           | `arithmetic_mean`             | enum {`arithmetic_mean`,`geometric_mean`,`harmonic_mean`,`quadratic_mean`,`weighted_median`,`softmax`}                                       | Metric aggregation method for `combined` strategy. `arithmetic_mean`=(Σ(w·m)/Σ(w)), `geometric_mean`=(∏(m^w))^(1/Σw), `harmonic_mean`=Σ(w)/(Σ(w/m)), `quadratic_mean`=(Σ(w·m²)/Σ(w))^(1/2), `weighted_median`=Q₀.₅(m,w), `softmax`=Σ(m·s_i) where s_i=w_i·exp(m_i/T)/Σ(w_j·exp(m_j/T)).                                                                                                                                                                                              |
+| freqai.extrema_weighting.softmax_temperature                   | 1.0                           | float > 0                                                                                                                                    | Temperature T for `softmax` aggregation, controls distribution sharpness.                                                                                                                                                                                                                                                                                                                                                                                                            |
 | freqai.extrema_weighting.standardization                       | `none`                        | enum {`none`,`zscore`,`robust`,`mmad`,`power_yj`}                                                                                            | Standardization method applied to smoothed weighted extrema before normalization. `none`=w, `zscore`=(w-μ)/σ, `robust`=(w-median)/IQR, `mmad`=(w-median)/(MAD·k), `power_yj`=YJ(w).                                                                                                                                                                                                                                                                                                  |
 | freqai.extrema_weighting.robust_quantiles                      | [0.25, 0.75]                  | list[float] where 0 <= Q1 < Q3 <= 1                                                                                                          | Quantile range for robust standardization, Q1 and Q3.                                                                                                                                                                                                                                                                                                                                                                                                                                |
 | freqai.extrema_weighting.mmad_scaling_factor                   | 1.4826                        | float > 0                                                                                                                                    | Scaling factor for MMAD standardization.                                                                                                                                                                                                                                                                                                                                                                                                                                             |
index e69fe5f98747ed88749ecb73d63b91874ea57f42..2c9aadf65b956b6b47618d079698ff5449bb3d8a 100644 (file)
@@ -87,7 +87,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
     https://github.com/sponsors/robcaulk
     """
 
-    version = "3.10.3"
+    version = "3.10.4"
 
     _TEST_SIZE: Final[float] = 0.1
 
index 695d7b46eca92651f2418f6d8df90badb86472f4..613472f65945cc5a8e0fe76ea889a8b3efee8a93 100644 (file)
@@ -44,10 +44,21 @@ COMBINED_METRICS: Final[tuple[CombinedMetric, ...]] = (
     "volume_weighted_efficiency_ratio",
 )
 
-CombinedAggregation = Literal["weighted_average", "geometric_mean"]
+CombinedAggregation = Literal[
+    "arithmetic_mean",
+    "geometric_mean",
+    "harmonic_mean",
+    "quadratic_mean",
+    "weighted_median",
+    "softmax",
+]
 COMBINED_AGGREGATIONS: Final[tuple[CombinedAggregation, ...]] = (
-    "weighted_average",
+    "arithmetic_mean",
     "geometric_mean",
+    "harmonic_mean",
+    "quadratic_mean",
+    "weighted_median",
+    "softmax",
 )
 
 WEIGHT_STRATEGIES: Final[tuple[WeightStrategy, ...]] = (
@@ -81,7 +92,8 @@ NORMALIZATION_TYPES: Final[tuple[NormalizationType, ...]] = (
 DEFAULTS_EXTREMA_WEIGHTING: Final[dict[str, Any]] = {
     "strategy": WEIGHT_STRATEGIES[0],  # "none"
     "metric_coefficients": {},
-    "aggregation": COMBINED_AGGREGATIONS[0],  # "weighted_average"
+    "aggregation": COMBINED_AGGREGATIONS[0],  # "arithmetic_mean"
+    "softmax_temperature": 1.0,
     # Phase 1: Standardization
     "standardization": STANDARDIZATION_TYPES[0],  # "none"
     "robust_quantiles": (0.25, 0.75),
index 373af19356f8ebfcfa46e3bc1737fc2cb1f0d914..1065d3020dd0be23e565ef4ed4631b31cfd5672c 100644 (file)
@@ -57,6 +57,7 @@ from Utils import (
     zigzag,
     zlema,
 )
+from ExtremaWeightingTransformer import COMBINED_AGGREGATIONS
 
 TradeDirection = Literal["long", "short"]
 InterpolationDirection = Literal["direct", "inverse"]
@@ -106,7 +107,7 @@ class QuickAdapterV3(IStrategy):
     _PLOT_EXTREMA_MIN_EPS: Final[float] = 0.01
 
     def version(self) -> str:
-        return "3.10.3"
+        return "3.10.4"
 
     timeframe = "5m"
     timeframe_minutes = timeframe_to_minutes(timeframe)
@@ -564,6 +565,12 @@ class QuickAdapterV3(IStrategy):
             f"  sigmoid_scale: {format_number(self.extrema_weighting['sigmoid_scale'])}"
         )
         logger.info(f"  gamma: {format_number(self.extrema_weighting['gamma'])}")
+        if (
+            self.extrema_weighting["aggregation"] == COMBINED_AGGREGATIONS[5]
+        ):  # "softmax"
+            logger.info(
+                f"  softmax_temperature: {format_number(self.extrema_weighting['softmax_temperature'])}"
+            )
 
         logger.info("Extrema Smoothing:")
         logger.info(f"  method: {self.extrema_smoothing['method']}")
index 51d904f839ecae3624de0b65fa9338b00c2bf598..dfc27c8b7a697001837a2c88c4c811601e5d42c1 100644 (file)
@@ -136,6 +136,19 @@ def get_extrema_weighting_config(
         )
         aggregation = DEFAULTS_EXTREMA_WEIGHTING["aggregation"]
 
+    softmax_temperature = extrema_weighting.get(
+        "softmax_temperature", DEFAULTS_EXTREMA_WEIGHTING["softmax_temperature"]
+    )
+    if (
+        not isinstance(softmax_temperature, (int, float))
+        or not np.isfinite(softmax_temperature)
+        or softmax_temperature <= 0
+    ):
+        logger.warning(
+            f"Invalid extrema_weighting softmax_temperature value {softmax_temperature!r}: must be a finite number > 0, using default {DEFAULTS_EXTREMA_WEIGHTING['softmax_temperature']!r}"
+        )
+        softmax_temperature = DEFAULTS_EXTREMA_WEIGHTING["softmax_temperature"]
+
     # Phase 1: Standardization
     standardization = extrema_weighting.get(
         "standardization", DEFAULTS_EXTREMA_WEIGHTING["standardization"]
@@ -250,6 +263,7 @@ def get_extrema_weighting_config(
         "strategy": strategy,
         "metric_coefficients": metric_coefficients,
         "aggregation": aggregation,
+        "softmax_temperature": softmax_temperature,
         # Phase 1: Standardization
         "standardization": standardization,
         "robust_quantiles": robust_quantiles,
@@ -521,14 +535,36 @@ def _aggregate_metrics(
     stacked_metrics: NDArray[np.floating],
     coefficients: NDArray[np.floating],
     aggregation: CombinedAggregation,
+    softmax_temperature: float,
 ) -> NDArray[np.floating]:
-    if aggregation == COMBINED_AGGREGATIONS[0]:  # "weighted_average"
-        return np.average(stacked_metrics, axis=0, weights=coefficients)
+    if aggregation == COMBINED_AGGREGATIONS[0]:  # "arithmetic_mean"
+        return sp.stats.pmean(stacked_metrics.T, p=1.0, weights=coefficients, axis=1)
     elif aggregation == COMBINED_AGGREGATIONS[1]:  # "geometric_mean"
-        return np.asarray(
-            sp.stats.gmean(stacked_metrics.T, weights=coefficients, axis=1),
-            dtype=float,
+        return sp.stats.pmean(stacked_metrics.T, p=0.0, weights=coefficients, axis=1)
+    elif aggregation == COMBINED_AGGREGATIONS[2]:  # "harmonic_mean"
+        return sp.stats.pmean(stacked_metrics.T, p=-1.0, weights=coefficients, axis=1)
+    elif aggregation == COMBINED_AGGREGATIONS[3]:  # "quadratic_mean"
+        return sp.stats.pmean(stacked_metrics.T, p=2.0, weights=coefficients, axis=1)
+    elif aggregation == COMBINED_AGGREGATIONS[4]:  # "weighted_median"
+        return np.array(
+            [
+                np.quantile(
+                    stacked_metrics[:, i],
+                    0.5,
+                    weights=coefficients,
+                    method="inverted_cdf",
+                )
+                for i in range(stacked_metrics.shape[1])
+            ]
         )
+    elif aggregation == COMBINED_AGGREGATIONS[5]:  # "softmax"
+        scaled_metrics = stacked_metrics / softmax_temperature
+        softmax_weights = sp.special.softmax(scaled_metrics, axis=0)
+        combined_weights = softmax_weights * coefficients[:, np.newaxis]
+        combined_weights = combined_weights / np.sum(
+            combined_weights, axis=0, keepdims=True
+        )
+        return np.sum(stacked_metrics * combined_weights, axis=0)
     else:
         raise ValueError(
             f"Invalid aggregation value {aggregation!r}: supported values are {', '.join(COMBINED_AGGREGATIONS)}"
@@ -545,6 +581,7 @@ def _compute_combined_weights(
     volume_weighted_efficiency_ratios: list[float],
     metric_coefficients: dict[str, Any],
     aggregation: CombinedAggregation,
+    softmax_temperature: float,
 ) -> NDArray[np.floating]:
     if len(indices) == 0:
         return np.asarray([], dtype=float)
@@ -585,7 +622,9 @@ def _compute_combined_weights(
     stacked_metrics = np.vstack(imputed_metrics)
     coefficients_array = np.asarray(coefficients_list, dtype=float)
 
-    return _aggregate_metrics(stacked_metrics, coefficients_array, aggregation)
+    return _aggregate_metrics(
+        stacked_metrics, coefficients_array, aggregation, softmax_temperature
+    )
 
 
 def compute_extrema_weights(
@@ -630,6 +669,7 @@ def compute_extrema_weights(
             volume_weighted_efficiency_ratios=volume_weighted_efficiency_ratios,
             metric_coefficients=extrema_weighting["metric_coefficients"],
             aggregation=extrema_weighting["aggregation"],
+            softmax_temperature=extrema_weighting["softmax_temperature"],
         )
 
     else: