]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
refactor(quickadapter)!: normalize tunables namespace for semantic consistency (#26)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Sun, 28 Dec 2025 18:51:56 +0000 (19:51 +0100)
committerGitHub <noreply@github.com>
Sun, 28 Dec 2025 18:51:56 +0000 (19:51 +0100)
* refactor(quickadapter): normalize tunables namespace for semantic consistency

Rename config keys and internal variables to follow consistent naming conventions:
- `_candles` suffix for time periods in candle units
- `_fraction` suffix for values in [0,1] range
- `_multiplier` suffix for scaling factors
- `_method` suffix for algorithm selectors

Config key renames (with backward-compatible deprecated aliases):
- lookback_period → lookback_period_candles
- decay_ratio → decay_fraction
- min/max_natr_ratio_percent → min/max_natr_ratio_fraction
- window → window_candles
- label_natr_ratio → label_natr_multiplier
- threshold_outlier → outlier_threshold_fraction
- thresholds_smoothing → threshold_smoothing_method
- thresholds_alpha → soft_extremum_alpha
- extrema_fraction → keep_extrema_fraction
- expansion_ratio → space_fraction
- trade_price_target → trade_price_target_method

Internal variable renames for code consistency:
- threshold_outlier → outlier_threshold_fraction
- thresholds_alpha → soft_extremum_alpha
- extrema_fraction → keep_extrema_fraction (local vars and function params)
- _reversal_lookback_period → _reversal_lookback_period_candles
- natr_ratio → natr_multiplier (zigzag function param)

All deprecated aliases emit warnings and remain functional for backward compatibility.

* chore(quickadapter): remove temporary audit file from codebase

* refactor(quickadapter): align constant names with normalized tunables

Rename class constants to match the normalized config key names:
- PREDICTIONS_EXTREMA_THRESHOLD_OUTLIER_DEFAULT → PREDICTIONS_EXTREMA_OUTLIER_THRESHOLD_FRACTION_DEFAULT
- PREDICTIONS_EXTREMA_THRESHOLDS_ALPHA_DEFAULT → PREDICTIONS_EXTREMA_SOFT_EXTREMUM_ALPHA_DEFAULT
- PREDICTIONS_EXTREMA_EXTREMA_FRACTION_DEFAULT → PREDICTIONS_EXTREMA_KEEP_EXTREMA_FRACTION_DEFAULT

* fix(quickadapter): rename outlier_threshold_fraction to outlier_threshold_quantile

The value (e.g., 0.999) represents the 99.9th percentile, which is
mathematically a quantile, not a fraction. This aligns the naming with
the semantic meaning of the parameter.

* fix(quickadapter): add missing deprecated alias support

Add backward-compatible deprecated alias handling for:
- freqai.optuna_hyperopt.expansion_ratio → space_fraction
- freqai.feature_parameters.min_label_natr_ratio → min_label_natr_multiplier
- freqai.feature_parameters.max_label_natr_ratio → max_label_natr_multiplier

Also add missing deprecated alias documentation in README for:
- reversal_confirmation.min_natr_ratio_percent → min_natr_ratio_fraction
- reversal_confirmation.max_natr_ratio_percent → max_natr_ratio_fraction

This ensures all deprecated aliases mentioned in the commit message of the
namespace normalization refactor are properly implemented.

* style(readme): normalize trailing whitespace

* fix(quickadapter): address PR review feedback

- Fix error message referencing 'window' instead of 'window_candles'
- Clarify soft_extremum_alpha error message (alpha=0 uses mean)
- Improve space_fraction documentation in README
- Simplify midpoint docstring

* refactor(quickadapter): rename safe configuration value retrieval helper

Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
* refactor(quickadapter): rename natr_ratio_fraction to natr_multiplier_fraction

- Align naming with label_natr_multiplier for consistency
- Rename get_config_value_with_deprecated_alias to get_config_value

* refactor(quickadapter): centralize label_natr_multiplier migration in get_label_defaults

- Move label_natr_ratio -> label_natr_multiplier migration to get_label_defaults()
- Update get_config_value to migrate in-place (pop old key, store new key)
- Remove redundant get_config_value calls in Strategy and Model __init__
- Simplify cached properties to use .get() since migration is done at init
- Rename _CUSTOM_STOPLOSS_NATR_RATIO_FRACTION to _CUSTOM_STOPLOSS_NATR_MULTIPLIER_FRACTION

* fix(quickadapter): check that df columns exist before using them

Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
* docs(README.md): update QuickAdapter strategy documentation

Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
* chore(quickadapter): bump version to 3.8.0

* refactor(quickadapter): remove unnecessary intermediate variable

* refactor(quickadapter): add cached properties for label_period_candles bounds

* chore(quickadapter): cleanup docstrings and comments

Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
---------

Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
.opencode/command/openspec-apply.md
.opencode/command/openspec-archive.md
.opencode/command/openspec-proposal.md
AGENTS.md
README.md
openspec/project.md
quickadapter/user_data/config-template.json
quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py
quickadapter/user_data/strategies/QuickAdapterV3.py
quickadapter/user_data/strategies/Utils.py

index 2686863870d3ea16f775980962ee90b40825adc6..2e2597469a48f5d6efbd0f62b09e78ab41f5be9c 100644 (file)
@@ -4,16 +4,13 @@ description: Implement an approved OpenSpec change and keep tasks in sync.
 ---
 
 <!-- OPENSPEC:START -->
-
 **Guardrails**
-
 - Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
 - Keep changes tightly scoped to the requested outcome.
 - Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
 
 **Steps**
 Track these steps as TODOs and complete them one by one.
-
 1. Read `changes/<id>/proposal.md`, `design.md` (if present), and `tasks.md` to confirm scope and acceptance criteria.
 2. Work through tasks sequentially, keeping edits minimal and focused on the requested change.
 3. Confirm completion before updating statuses—make sure every item in `tasks.md` is finished.
@@ -21,6 +18,5 @@ Track these steps as TODOs and complete them one by one.
 5. Reference `openspec list` or `openspec show <item>` when additional context is required.
 
 **Reference**
-
 - Use `openspec show <id> --json --deltas-only` if you need additional context from the proposal while implementing.
 <!-- OPENSPEC:END -->
index 8d60d35878ce972a5ea9f81fe27458dfcf841d81..d172e4483217679d9a892012edab58df4174c322 100644 (file)
@@ -1,7 +1,6 @@
 ---
 description: Archive a deployed OpenSpec change and update specs.
 ---
-
 <ChangeId>
   $ARGUMENTS
 </ChangeId>
@@ -12,7 +11,6 @@ description: Archive a deployed OpenSpec change and update specs.
 - Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
 
 **Steps**
-
 1. Determine the change ID to archive:
    - If this prompt already includes a specific change ID (for example inside a `<ChangeId>` block populated by slash-command arguments), use that value after trimming whitespace.
    - If the conversation references a change loosely (for example by title or summary), run `openspec list` to surface likely IDs, share the relevant candidates, and confirm which one the user intends.
@@ -24,7 +22,6 @@ description: Archive a deployed OpenSpec change and update specs.
 5. Validate with `openspec validate --strict` and inspect with `openspec show <id>` if anything looks off.
 
 **Reference**
-
 - Use `openspec list` to confirm change IDs before archiving.
 - Inspect refreshed specs with `openspec list --specs` and address any validation issues before handing off.
 <!-- OPENSPEC:END -->
index 9282a4fd20e5d606471f2e27c9aef5d5ba7425ae..47fabaaf498a76dbfbd9b1b34c1f5ebc72c1d5cc 100644 (file)
@@ -9,9 +9,7 @@ $ARGUMENTS
 </UserRequest>
 
 <!-- OPENSPEC:START -->
-
 **Guardrails**
-
 - Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
 - Keep changes tightly scoped to the requested outcome.
 - Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
@@ -19,7 +17,6 @@ $ARGUMENTS
 - Do not write any code during the proposal stage. Only create design documents (proposal.md, tasks.md, design.md, and spec deltas). Implementation happens in the apply stage after approval.
 
 **Steps**
-
 1. Review `openspec/project.md`, run `openspec list` and `openspec list --specs`, and inspect related code or docs (e.g., via `rg`/`ls`) to ground the proposal in current behaviour; note any gaps that require clarification.
 2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, and `design.md` (when needed) under `openspec/changes/<id>/`.
 3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing.
@@ -29,7 +26,6 @@ $ARGUMENTS
 7. Validate with `openspec validate <id> --strict` and resolve every issue before sharing the proposal.
 
 **Reference**
-
 - Use `openspec show <id> --json --deltas-only` or `openspec show <spec> --type spec` to inspect details when validation fails.
 - Search existing requirements with `rg -n "Requirement:|Scenario:" openspec/specs` before writing new ones.
 - Explore the codebase with `rg <keyword>`, `ls`, or direct file reads so proposals align with current implementation realities.
index 311596542ab3ea8b8205a634325ca1d14df8cdad..1a672f2eca02fc7870a431693294200bd37990ff 100644 (file)
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -20,4 +20,5 @@ Keep this managed block so 'openspec update' can refresh the instructions.
 
 <!-- OPENSPEC:END -->
 
-Open `@/.github/copilot-instructions.md`, read it and strictly follow the instructions.
+Open `@/.github/copilot-instructions.md`, read it and strictly follow the
+instructions.
index 930eb750dfcf9ec471cc09f02cb0fec094040e8c..dbae7aaf167d78b2a8af4bff3e5e8acfde4e9446 100644 (file)
--- a/README.md
+++ b/README.md
@@ -37,90 +37,90 @@ docker compose up -d --build
 
 ### Configuration tunables
 
-| Path                                                 | Default                       | Type / Range                                                                                                                               | Description                                                                                                                                                                                                                                                                                                                                                             |
-| ---------------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| _Protections_                                        |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
-| custom_protections.trade_duration_candles            | 72                            | int >= 1                                                                                                                                   | Estimated trade duration in candles. Scales protections stop duration candles and trade limit.                                                                                                                                                                                                                                                                          |
-| custom_protections.lookback_period_fraction          | 0.5                           | float (0,1]                                                                                                                                | Fraction of `fit_live_predictions_candles` used to calculate `lookback_period_candles` for _MaxDrawdown_ and _StoplossGuard_ protections.                                                                                                                                                                                                                               |
-| custom_protections.cooldown.enabled                  | true                          | bool                                                                                                                                       | Enable/disable _CooldownPeriod_ protection.                                                                                                                                                                                                                                                                                                                             |
-| custom_protections.cooldown.stop_duration_candles    | 4                             | int >= 1                                                                                                                                   | Number of candles to wait before allowing new trades after a trade is closed.                                                                                                                                                                                                                                                                                           |
-| custom_protections.drawdown.enabled                  | true                          | bool                                                                                                                                       | Enable/disable _MaxDrawdown_ protection.                                                                                                                                                                                                                                                                                                                                |
-| custom_protections.drawdown.max_allowed_drawdown     | 0.2                           | float (0,1)                                                                                                                                | Maximum allowed drawdown.                                                                                                                                                                                                                                                                                                                                               |
-| custom_protections.stoploss.enabled                  | true                          | bool                                                                                                                                       | Enable/disable _StoplossGuard_ protection.                                                                                                                                                                                                                                                                                                                              |
-| _Leverage_                                           |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
-| leverage                                             | `proposed_leverage`           | float [1.0, max_leverage]                                                                                                                  | Leverage. Fallback to `proposed_leverage` for the pair.                                                                                                                                                                                                                                                                                                                 |
-| _Exit pricing_                                       |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
-| exit_pricing.trade_price_target                      | `moving_average`              | enum {`moving_average`,`quantile_interpolation`,`weighted_average`}                                                                        | Trade NATR computation method.                                                                                                                                                                                                                                                                                                                                          |
-| exit_pricing.thresholds_calibration.decline_quantile | 0.75                          | float (0,1)                                                                                                                                | PnL decline quantile threshold.                                                                                                                                                                                                                                                                                                                                         |
-| _Reversal confirmation_                              |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
-| reversal_confirmation.lookback_period                | 0                             | int >= 0                                                                                                                                   | Prior confirming candles; 0 = none.                                                                                                                                                                                                                                                                                                                                     |
-| reversal_confirmation.decay_ratio                    | 0.5                           | float (0,1]                                                                                                                                | Geometric per-candle volatility adjusted reversal threshold relaxation factor.                                                                                                                                                                                                                                                                                          |
-| reversal_confirmation.min_natr_ratio_percent         | 0.0095                        | float [0,1]                                                                                                                                | Lower bound fraction for volatility adjusted reversal threshold.                                                                                                                                                                                                                                                                                                        |
-| reversal_confirmation.max_natr_ratio_percent         | 0.075                         | float [0,1]                                                                                                                                | Upper bound fraction (>= lower bound) for volatility adjusted reversal threshold.                                                                                                                                                                                                                                                                                       |
-| _Regressor model_                                    |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
-| freqai.regressor                                     | `xgboost`                     | enum {`xgboost`,`lightgbm`,`histgradientboostingregressor`}                                                                                | Machine learning regressor algorithm.                                                                                                                                                                                                                                                                                                                                   |
-| _Extrema smoothing_                                  |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
-| freqai.extrema_smoothing.method                      | `gaussian`                    | enum {`gaussian`,`kaiser`,`triang`,`smm`,`sma`,`savgol`,`gaussian_filter1d`}                                                               | Extrema smoothing method (`smm`=median, `sma`=mean, `savgol`=Savitzky–Golay).                                                                                                                                                                                                                                                                                           |
-| freqai.extrema_smoothing.window                      | 5                             | int >= 3                                                                                                                                   | Smoothing window length (candles).                                                                                                                                                                                                                                                                                                                                      |
-| freqai.extrema_smoothing.beta                        | 8.0                           | float > 0                                                                                                                                  | Shape parameter for `kaiser` kernel.                                                                                                                                                                                                                                                                                                                                    |
-| freqai.extrema_smoothing.polyorder                   | 3                             | int >= 1                                                                                                                                   | Polynomial order for `savgol` smoothing.                                                                                                                                                                                                                                                                                                                                |
-| freqai.extrema_smoothing.mode                        | `mirror`                      | enum {`mirror`,`constant`,`nearest`,`wrap`,`interp`}                                                                                       | Boundary mode for `savgol` and `gaussian_filter1d`.                                                                                                                                                                                                                                                                                                                     |
-| freqai.extrema_smoothing.sigma                       | 1.0                           | float > 0                                                                                                                                  | Gaussian `sigma` for `gaussian_filter1d` smoothing.                                                                                                                                                                                                                                                                                                                     |
-| _Extrema weighting_                                  |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
-| freqai.extrema_weighting.strategy                    | `none`                        | enum {`none`,`amplitude`,`amplitude_threshold_ratio`,`volume_rate`,`speed`,`efficiency_ratio`,`volume_weighted_efficiency_ratio`,`hybrid`} | Extrema weighting source: unweighted (`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 `hybrid`. |
-| freqai.extrema_weighting.source_weights              | `{}`                          | dict[str, float]                                                                                                                           | Weights on extrema weighting sources for `hybrid`.                                                                                                                                                                                                                                                                                                                      |
-| freqai.extrema_weighting.aggregation                 | `weighted_sum`                | enum {`weighted_sum`,`geometric_mean`}                                                                                                     | Aggregation method applied to weighted extrema weighting sources for `hybrid`.                                                                                                                                                                                                                                                                                          |
-| freqai.extrema_weighting.aggregation_normalization   | `none`                        | enum {`minmax`,`sigmoid`,`softmax`,`l1`,`l2`,`rank`,`none`}                                                                                | Normalization method applied to the aggregated extrema weighting source for `hybrid`.                                                                                                                                                                                                                                                                                   |
-| freqai.extrema_weighting.standardization             | `none`                        | enum {`none`,`zscore`,`robust`,`mmad`}                                                                                                     | Standardization method applied to weights before normalization. `none`=no standardization, `zscore`=(w-μ)/σ, `robust`=(w-median)/IQR, `mmad`=(w-median)/MAD.                                                                                                                                                                                                            |
-| 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.                                                                                                                                                                                                                                                                                                                                |
-| freqai.extrema_weighting.normalization               | `minmax`                      | enum {`minmax`,`sigmoid`,`softmax`,`l1`,`l2`,`rank`,`none`}                                                                                | Normalization method applied to weights.                                                                                                                                                                                                                                                                                                                                |
-| freqai.extrema_weighting.minmax_range                | [0.0, 1.0]                    | list[float]                                                                                                                                | Target range for `minmax` normalization, min and max.                                                                                                                                                                                                                                                                                                                   |
-| freqai.extrema_weighting.sigmoid_scale               | 1.0                           | float > 0                                                                                                                                  | Scale parameter for `sigmoid` normalization, controls steepness.                                                                                                                                                                                                                                                                                                        |
-| freqai.extrema_weighting.softmax_temperature         | 1.0                           | float > 0                                                                                                                                  | Temperature parameter for `softmax` normalization: lower values sharpen distribution, higher values flatten it.                                                                                                                                                                                                                                                         |
-| freqai.extrema_weighting.rank_method                 | `average`                     | enum {`average`,`min`,`max`,`dense`,`ordinal`}                                                                                             | Ranking method for `rank` normalization.                                                                                                                                                                                                                                                                                                                                |
-| freqai.extrema_weighting.gamma                       | 1.0                           | float (0,10]                                                                                                                               | Contrast exponent applied after normalization: >1 emphasizes extrema, values between 0 and 1 soften.                                                                                                                                                                                                                                                                    |
-| _Feature parameters_                                 |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
-| freqai.feature_parameters.label_period_candles       | min/max midpoint              | int >= 1                                                                                                                                   | Zigzag labeling NATR horizon.                                                                                                                                                                                                                                                                                                                                           |
-| freqai.feature_parameters.min_label_period_candles   | 12                            | int >= 1                                                                                                                                   | Minimum labeling NATR horizon used for reversals labeling HPO.                                                                                                                                                                                                                                                                                                          |
-| freqai.feature_parameters.max_label_period_candles   | 24                            | int >= 1                                                                                                                                   | Maximum labeling NATR horizon used for reversals labeling HPO.                                                                                                                                                                                                                                                                                                          |
-| freqai.feature_parameters.label_natr_ratio           | min/max midpoint              | float > 0                                                                                                                                  | Zigzag labeling NATR ratio.                                                                                                                                                                                                                                                                                                                                             |
-| freqai.feature_parameters.min_label_natr_ratio       | 9.0                           | float > 0                                                                                                                                  | Minimum labeling NATR ratio used for reversals labeling HPO.                                                                                                                                                                                                                                                                                                            |
-| freqai.feature_parameters.max_label_natr_ratio       | 12.0                          | float > 0                                                                                                                                  | Maximum labeling NATR ratio used for reversals labeling HPO.                                                                                                                                                                                                                                                                                                            |
-| freqai.feature_parameters.label_frequency_candles    | `auto`                        | int >= 2 \| `auto`                                                                                                                         | Reversals labeling frequency. `auto` = max(2, 2 \* number of whitelisted pairs).                                                                                                                                                                                                                                                                                        |
-| freqai.feature_parameters.label_metric               | `euclidean`                   | string (supported: `euclidean`,`minkowski`,`cityblock`,`chebyshev`,`mahalanobis`,`seuclidean`,`jensenshannon`,`sqeuclidean`,...)           | Metric used in distance calculations to ideal point.                                                                                                                                                                                                                                                                                                                    |
-| freqai.feature_parameters.label_weights              | [1/7,1/7,1/7,1/7,1/7,1/7,1/7] | list[float]                                                                                                                                | Per-objective weights used in distance calculations to ideal point. Objectives: (1) number of detected reversals, (2) median swing amplitude, (3) median swing amplitude / median volatility-threshold ratio, (4) median swing volume per candle, (5) median swing speed, (6) median swing efficiency ratio, (7) median swing volume-weighted efficiency ratio.         |
-| freqai.feature_parameters.label_p_order              | `None`                        | float \| None                                                                                                                              | p-order used by `minkowski` / `power_mean` (optional).                                                                                                                                                                                                                                                                                                                  |
-| freqai.feature_parameters.label_medoid_metric        | `euclidean`                   | string                                                                                                                                     | Metric used with `medoid`.                                                                                                                                                                                                                                                                                                                                              |
-| freqai.feature_parameters.label_kmeans_metric        | `euclidean`                   | string                                                                                                                                     | Metric used for k-means clustering.                                                                                                                                                                                                                                                                                                                                     |
-| freqai.feature_parameters.label_kmeans_selection     | `min`                         | enum {`min`,`medoid`}                                                                                                                      | Strategy to select trial in the best k-means cluster.                                                                                                                                                                                                                                                                                                                   |
-| freqai.feature_parameters.label_kmedoids_metric      | `euclidean`                   | string                                                                                                                                     | Metric used for k-medoids clustering.                                                                                                                                                                                                                                                                                                                                   |
-| freqai.feature_parameters.label_kmedoids_selection   | `min`                         | enum {`min`,`medoid`}                                                                                                                      | Strategy to select trial in the best k-medoids cluster.                                                                                                                                                                                                                                                                                                                 |
-| freqai.feature_parameters.label_knn_metric           | `minkowski`                   | string                                                                                                                                     | Distance metric for KNN.                                                                                                                                                                                                                                                                                                                                                |
-| freqai.feature_parameters.label_knn_p_order          | `None`                        | float \| None                                                                                                                              | Tunable for KNN neighbor distances aggregation methods: p-order (`knn_power_mean`, default: 1.0) or quantile (`knn_quantile`, default: 0.5). (optional)                                                                                                                                                                                                                 |
-| freqai.feature_parameters.label_knn_n_neighbors      | 5                             | int >= 1                                                                                                                                   | Number of neighbors for KNN.                                                                                                                                                                                                                                                                                                                                            |
-| _Predictions extrema_                                |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
-| freqai.predictions_extrema.selection_method          | `rank_extrema`                | enum {`rank_extrema`,`rank_peaks`,`partition`}                                                                                             | Extrema selection method. `rank_extrema` ranks extrema values, `rank_peaks` ranks detected peak values, `partition` uses sign-based partitioning.                                                                                                                                                                                                                       |
-| freqai.predictions_extrema.thresholds_smoothing      | `mean`                        | enum {`mean`,`isodata`,`li`,`minimum`,`otsu`,`triangle`,`yen`,`median`,`soft_extremum`}                                                    | Thresholding method for prediction thresholds smoothing.                                                                                                                                                                                                                                                                                                                |
-| freqai.predictions_extrema.thresholds_alpha          | 12.0                          | float > 0                                                                                                                                  | Alpha for `soft_extremum` thresholds smoothing.                                                                                                                                                                                                                                                                                                                         |
-| freqai.predictions_extrema.threshold_outlier         | 0.999                         | float (0,1)                                                                                                                                | Quantile threshold for predictions outlier filtering.                                                                                                                                                                                                                                                                                                                   |
-| freqai.predictions_extrema.extrema_fraction          | 1.0                           | float (0,1]                                                                                                                                | Fraction of extrema used for thresholds. `1.0` uses all, lower values keep only most significant. Applies to `rank_extrema` and `rank_peaks`; ignored for `partition`.                                                                                                                                                                                                  |
-| _Optuna / HPO_                                       |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
-| freqai.optuna_hyperopt.enabled                       | false                         | bool                                                                                                                                       | Enables HPO.                                                                                                                                                                                                                                                                                                                                                            |
-| freqai.optuna_hyperopt.sampler                       | `tpe`                         | enum {`tpe`,`auto`}                                                                                                                        | HPO sampler algorithm. `tpe` uses [TPESampler](https://optuna.readthedocs.io/en/stable/reference/samplers/generated/optuna.samplers.TPESampler.html) with multivariate and group, `auto` uses [AutoSampler](https://hub.optuna.org/samplers/auto_sampler).                                                                                                              |
-| freqai.optuna_hyperopt.storage                       | `file`                        | enum {`file`,`sqlite`}                                                                                                                     | HPO storage backend.                                                                                                                                                                                                                                                                                                                                                    |
-| freqai.optuna_hyperopt.continuous                    | true                          | bool                                                                                                                                       | Continuous HPO.                                                                                                                                                                                                                                                                                                                                                         |
-| freqai.optuna_hyperopt.warm_start                    | true                          | bool                                                                                                                                       | Warm start HPO with previous best value(s).                                                                                                                                                                                                                                                                                                                             |
-| freqai.optuna_hyperopt.n_startup_trials              | 15                            | int >= 0                                                                                                                                   | HPO startup trials.                                                                                                                                                                                                                                                                                                                                                     |
-| freqai.optuna_hyperopt.n_trials                      | 50                            | int >= 1                                                                                                                                   | Maximum HPO trials.                                                                                                                                                                                                                                                                                                                                                     |
-| freqai.optuna_hyperopt.n_jobs                        | CPU threads / 4               | int >= 1                                                                                                                                   | Parallel HPO workers.                                                                                                                                                                                                                                                                                                                                                   |
-| freqai.optuna_hyperopt.timeout                       | 7200                          | int >= 0                                                                                                                                   | HPO wall-clock timeout in seconds.                                                                                                                                                                                                                                                                                                                                      |
-| freqai.optuna_hyperopt.label_candles_step            | 1                             | int >= 1                                                                                                                                   | Step for Zigzag NATR horizon search space.                                                                                                                                                                                                                                                                                                                              |
-| freqai.optuna_hyperopt.train_candles_step            | 10                            | int >= 1                                                                                                                                   | Step for training sets size search space.                                                                                                                                                                                                                                                                                                                               |
-| freqai.optuna_hyperopt.space_reduction               | false                         | bool                                                                                                                                       | Enable/disable HPO search space reduction based on previous best parameters.                                                                                                                                                                                                                                                                                            |
-| freqai.optuna_hyperopt.expansion_ratio               | 0.4                           | float [0,1]                                                                                                                                | HPO search space expansion ratio.                                                                                                                                                                                                                                                                                                                                       |
-| freqai.optuna_hyperopt.min_resource                  | 3                             | int >= 1                                                                                                                                   | Minimum resource per Hyperband pruner rung.                                                                                                                                                                                                                                                                                                                             |
-| freqai.optuna_hyperopt.seed                          | 1                             | int >= 0                                                                                                                                   | HPO RNG seed.                                                                                                                                                                                                                                                                                                                                                           |
+| Path                                                  | Default                       | Type / Range                                                                                                                               | Description                                                                                                                                                                                                                                                                                                                                                             |
+| ----------------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| _Protections_                                         |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
+| custom_protections.trade_duration_candles             | 72                            | int >= 1                                                                                                                                   | Estimated trade duration in candles. Scales protections stop duration candles and trade limit.                                                                                                                                                                                                                                                                          |
+| custom_protections.lookback_period_fraction           | 0.5                           | float (0,1]                                                                                                                                | Fraction of `fit_live_predictions_candles` used to calculate `lookback_period_candles` for _MaxDrawdown_ and _StoplossGuard_ protections.                                                                                                                                                                                                                               |
+| custom_protections.cooldown.enabled                   | true                          | bool                                                                                                                                       | Enable/disable _CooldownPeriod_ protection.                                                                                                                                                                                                                                                                                                                             |
+| custom_protections.cooldown.stop_duration_candles     | 4                             | int >= 1                                                                                                                                   | Number of candles to wait before allowing new trades after a trade is closed.                                                                                                                                                                                                                                                                                           |
+| custom_protections.drawdown.enabled                   | true                          | bool                                                                                                                                       | Enable/disable _MaxDrawdown_ protection.                                                                                                                                                                                                                                                                                                                                |
+| custom_protections.drawdown.max_allowed_drawdown      | 0.2                           | float (0,1)                                                                                                                                | Maximum allowed drawdown.                                                                                                                                                                                                                                                                                                                                               |
+| custom_protections.stoploss.enabled                   | true                          | bool                                                                                                                                       | Enable/disable _StoplossGuard_ protection.                                                                                                                                                                                                                                                                                                                              |
+| _Leverage_                                            |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
+| leverage                                              | `proposed_leverage`           | float [1.0, max_leverage]                                                                                                                  | Leverage. Fallback to `proposed_leverage` for the pair.                                                                                                                                                                                                                                                                                                                 |
+| _Exit pricing_                                        |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
+| exit_pricing.trade_price_target_method                | `moving_average`              | enum {`moving_average`,`quantile_interpolation`,`weighted_average`}                                                                        | Trade NATR computation method. (Deprecated alias: `exit_pricing.trade_price_target`)                                                                                                                                                                                                                                                                                    |
+| exit_pricing.thresholds_calibration.decline_quantile  | 0.75                          | float (0,1)                                                                                                                                | PnL decline quantile threshold.                                                                                                                                                                                                                                                                                                                                         |
+| _Reversal confirmation_                               |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
+| reversal_confirmation.lookback_period_candles         | 0                             | int >= 0                                                                                                                                   | Prior confirming candles; 0 = none. (Deprecated alias: `reversal_confirmation.lookback_period`)                                                                                                                                                                                                                                                                         |
+| reversal_confirmation.decay_fraction                  | 0.5                           | float (0,1]                                                                                                                                | Geometric per-candle volatility adjusted reversal threshold relaxation factor. (Deprecated alias: `reversal_confirmation.decay_ratio`)                                                                                                                                                                                                                                  |
+| reversal_confirmation.min_natr_multiplier_fraction    | 0.0095                        | float [0,1]                                                                                                                                | Lower bound fraction for volatility adjusted reversal threshold. (Deprecated alias: `reversal_confirmation.min_natr_ratio_percent`)                                                                                                                                                                                                                                     |
+| reversal_confirmation.max_natr_multiplier_fraction    | 0.075                         | float [0,1]                                                                                                                                | Upper bound fraction (>= lower bound) for volatility adjusted reversal threshold. (Deprecated alias: `reversal_confirmation.max_natr_ratio_percent`)                                                                                                                                                                                                                    |
+| _Regressor model_                                     |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
+| freqai.regressor                                      | `xgboost`                     | enum {`xgboost`,`lightgbm`,`histgradientboostingregressor`}                                                                                | Machine learning regressor algorithm.                                                                                                                                                                                                                                                                                                                                   |
+| _Extrema smoothing_                                   |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
+| freqai.extrema_smoothing.method                       | `gaussian`                    | enum {`gaussian`,`kaiser`,`triang`,`smm`,`sma`,`savgol`,`gaussian_filter1d`}                                                               | Extrema smoothing method (`smm`=median, `sma`=mean, `savgol`=Savitzky–Golay).                                                                                                                                                                                                                                                                                           |
+| freqai.extrema_smoothing.window_candles               | 5                             | int >= 3                                                                                                                                   | Smoothing window length (candles). (Deprecated alias: `freqai.extrema_smoothing.window`)                                                                                                                                                                                                                                                                                |
+| freqai.extrema_smoothing.beta                         | 8.0                           | float > 0                                                                                                                                  | Shape parameter for `kaiser` kernel.                                                                                                                                                                                                                                                                                                                                    |
+| freqai.extrema_smoothing.polyorder                    | 3                             | int >= 1                                                                                                                                   | Polynomial order for `savgol` smoothing.                                                                                                                                                                                                                                                                                                                                |
+| freqai.extrema_smoothing.mode                         | `mirror`                      | enum {`mirror`,`constant`,`nearest`,`wrap`,`interp`}                                                                                       | Boundary mode for `savgol` and `gaussian_filter1d`.                                                                                                                                                                                                                                                                                                                     |
+| freqai.extrema_smoothing.sigma                        | 1.0                           | float > 0                                                                                                                                  | Gaussian `sigma` for `gaussian_filter1d` smoothing.                                                                                                                                                                                                                                                                                                                     |
+| _Extrema weighting_                                   |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
+| freqai.extrema_weighting.strategy                     | `none`                        | enum {`none`,`amplitude`,`amplitude_threshold_ratio`,`volume_rate`,`speed`,`efficiency_ratio`,`volume_weighted_efficiency_ratio`,`hybrid`} | Extrema weighting source: unweighted (`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 `hybrid`. |
+| freqai.extrema_weighting.source_weights               | `{}`                          | dict[str, float]                                                                                                                           | Weights on extrema weighting sources for `hybrid`.                                                                                                                                                                                                                                                                                                                      |
+| freqai.extrema_weighting.aggregation                  | `weighted_sum`                | enum {`weighted_sum`,`geometric_mean`}                                                                                                     | Aggregation method applied to weighted extrema weighting sources for `hybrid`.                                                                                                                                                                                                                                                                                          |
+| freqai.extrema_weighting.aggregation_normalization    | `none`                        | enum {`minmax`,`sigmoid`,`softmax`,`l1`,`l2`,`rank`,`none`}                                                                                | Normalization method applied to the aggregated extrema weighting source for `hybrid`.                                                                                                                                                                                                                                                                                   |
+| freqai.extrema_weighting.standardization              | `none`                        | enum {`none`,`zscore`,`robust`,`mmad`}                                                                                                     | Standardization method applied to weights before normalization. `none`=no standardization, `zscore`=(w-μ)/σ, `robust`=(w-median)/IQR, `mmad`=(w-median)/MAD.                                                                                                                                                                                                            |
+| 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.                                                                                                                                                                                                                                                                                                                                |
+| freqai.extrema_weighting.normalization                | `minmax`                      | enum {`minmax`,`sigmoid`,`softmax`,`l1`,`l2`,`rank`,`none`}                                                                                | Normalization method applied to weights.                                                                                                                                                                                                                                                                                                                                |
+| freqai.extrema_weighting.minmax_range                 | [0.0, 1.0]                    | list[float]                                                                                                                                | Target range for `minmax` normalization, min and max.                                                                                                                                                                                                                                                                                                                   |
+| freqai.extrema_weighting.sigmoid_scale                | 1.0                           | float > 0                                                                                                                                  | Scale parameter for `sigmoid` normalization, controls steepness.                                                                                                                                                                                                                                                                                                        |
+| freqai.extrema_weighting.softmax_temperature          | 1.0                           | float > 0                                                                                                                                  | Temperature parameter for `softmax` normalization: lower values sharpen distribution, higher values flatten it.                                                                                                                                                                                                                                                         |
+| freqai.extrema_weighting.rank_method                  | `average`                     | enum {`average`,`min`,`max`,`dense`,`ordinal`}                                                                                             | Ranking method for `rank` normalization.                                                                                                                                                                                                                                                                                                                                |
+| freqai.extrema_weighting.gamma                        | 1.0                           | float (0,10]                                                                                                                               | Contrast exponent applied after normalization: >1 emphasizes extrema, values between 0 and 1 soften.                                                                                                                                                                                                                                                                    |
+| _Feature parameters_                                  |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
+| freqai.feature_parameters.label_period_candles        | min/max midpoint              | int >= 1                                                                                                                                   | Zigzag labeling NATR horizon.                                                                                                                                                                                                                                                                                                                                           |
+| freqai.feature_parameters.min_label_period_candles    | 12                            | int >= 1                                                                                                                                   | Minimum labeling NATR horizon used for reversals labeling HPO.                                                                                                                                                                                                                                                                                                          |
+| freqai.feature_parameters.max_label_period_candles    | 24                            | int >= 1                                                                                                                                   | Maximum labeling NATR horizon used for reversals labeling HPO.                                                                                                                                                                                                                                                                                                          |
+| freqai.feature_parameters.label_natr_multiplier       | min/max midpoint              | float > 0                                                                                                                                  | Zigzag labeling NATR multiplier. (Deprecated alias: `freqai.feature_parameters.label_natr_ratio`)                                                                                                                                                                                                                                                                       |
+| freqai.feature_parameters.min_label_natr_multiplier   | 9.0                           | float > 0                                                                                                                                  | Minimum labeling NATR multiplier used for reversals labeling HPO. (Deprecated alias: `freqai.feature_parameters.min_label_natr_ratio`)                                                                                                                                                                                                                                  |
+| freqai.feature_parameters.max_label_natr_multiplier   | 12.0                          | float > 0                                                                                                                                  | Maximum labeling NATR multiplier used for reversals labeling HPO. (Deprecated alias: `freqai.feature_parameters.max_label_natr_ratio`)                                                                                                                                                                                                                                  |
+| freqai.feature_parameters.label_frequency_candles     | `auto`                        | int >= 2 \| `auto`                                                                                                                         | Reversals labeling frequency. `auto` = max(2, 2 \* number of whitelisted pairs).                                                                                                                                                                                                                                                                                        |
+| freqai.feature_parameters.label_metric                | `euclidean`                   | string (supported: `euclidean`,`minkowski`,`cityblock`,`chebyshev`,`mahalanobis`,`seuclidean`,`jensenshannon`,`sqeuclidean`,...)           | Metric used in distance calculations to ideal point.                                                                                                                                                                                                                                                                                                                    |
+| freqai.feature_parameters.label_weights               | [1/7,1/7,1/7,1/7,1/7,1/7,1/7] | list[float]                                                                                                                                | Per-objective weights used in distance calculations to ideal point. Objectives: (1) number of detected reversals, (2) median swing amplitude, (3) median swing amplitude / median volatility-threshold ratio, (4) median swing volume per candle, (5) median swing speed, (6) median swing efficiency ratio, (7) median swing volume-weighted efficiency ratio.         |
+| freqai.feature_parameters.label_p_order               | `None`                        | float \| None                                                                                                                              | p-order used by `minkowski` / `power_mean` (optional).                                                                                                                                                                                                                                                                                                                  |
+| freqai.feature_parameters.label_medoid_metric         | `euclidean`                   | string                                                                                                                                     | Metric used with `medoid`.                                                                                                                                                                                                                                                                                                                                              |
+| freqai.feature_parameters.label_kmeans_metric         | `euclidean`                   | string                                                                                                                                     | Metric used for k-means clustering.                                                                                                                                                                                                                                                                                                                                     |
+| freqai.feature_parameters.label_kmeans_selection      | `min`                         | enum {`min`,`medoid`}                                                                                                                      | Strategy to select trial in the best k-means cluster.                                                                                                                                                                                                                                                                                                                   |
+| freqai.feature_parameters.label_kmedoids_metric       | `euclidean`                   | string                                                                                                                                     | Metric used for k-medoids clustering.                                                                                                                                                                                                                                                                                                                                   |
+| freqai.feature_parameters.label_kmedoids_selection    | `min`                         | enum {`min`,`medoid`}                                                                                                                      | Strategy to select trial in the best k-medoids cluster.                                                                                                                                                                                                                                                                                                                 |
+| freqai.feature_parameters.label_knn_metric            | `minkowski`                   | string                                                                                                                                     | Distance metric for KNN.                                                                                                                                                                                                                                                                                                                                                |
+| freqai.feature_parameters.label_knn_p_order           | `None`                        | float \| None                                                                                                                              | Tunable for KNN neighbor distances aggregation methods: p-order (`knn_power_mean`, default: 1.0) or quantile (`knn_quantile`, default: 0.5). (optional)                                                                                                                                                                                                                 |
+| freqai.feature_parameters.label_knn_n_neighbors       | 5                             | int >= 1                                                                                                                                   | Number of neighbors for KNN.                                                                                                                                                                                                                                                                                                                                            |
+| _Predictions extrema_                                 |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
+| freqai.predictions_extrema.selection_method           | `rank_extrema`                | enum {`rank_extrema`,`rank_peaks`,`partition`}                                                                                             | Extrema selection method. `rank_extrema` ranks extrema values, `rank_peaks` ranks detected peak values, `partition` uses sign-based partitioning.                                                                                                                                                                                                                       |
+| freqai.predictions_extrema.threshold_smoothing_method | `mean`                        | enum {`mean`,`isodata`,`li`,`minimum`,`otsu`,`triangle`,`yen`,`median`,`soft_extremum`}                                                    | Thresholding method for prediction thresholds smoothing. (Deprecated alias: `freqai.predictions_extrema.thresholds_smoothing`)                                                                                                                                                                                                                                          |
+| freqai.predictions_extrema.soft_extremum_alpha        | 12.0                          | float >= 0                                                                                                                                 | Alpha for `soft_extremum` thresholds smoothing. (Deprecated alias: `freqai.predictions_extrema.thresholds_alpha`)                                                                                                                                                                                                                                                       |
+| freqai.predictions_extrema.outlier_threshold_quantile | 0.999                         | float (0,1)                                                                                                                                | Quantile threshold for predictions outlier filtering. (Deprecated alias: `freqai.predictions_extrema.threshold_outlier`)                                                                                                                                                                                                                                                |
+| freqai.predictions_extrema.keep_extrema_fraction      | 1.0                           | float (0,1]                                                                                                                                | Fraction of extrema used for thresholds. `1.0` uses all, lower values keep only most significant. Applies to `rank_extrema` and `rank_peaks`; ignored for `partition`. (Deprecated alias: `freqai.predictions_extrema.extrema_fraction`)                                                                                                                                |
+| _Optuna / HPO_                                        |                               |                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                         |
+| freqai.optuna_hyperopt.enabled                        | false                         | bool                                                                                                                                       | Enables HPO.                                                                                                                                                                                                                                                                                                                                                            |
+| freqai.optuna_hyperopt.sampler                        | `tpe`                         | enum {`tpe`,`auto`}                                                                                                                        | HPO sampler algorithm. `tpe` uses [TPESampler](https://optuna.readthedocs.io/en/stable/reference/samplers/generated/optuna.samplers.TPESampler.html) with multivariate and group, `auto` uses [AutoSampler](https://hub.optuna.org/samplers/auto_sampler).                                                                                                              |
+| freqai.optuna_hyperopt.storage                        | `file`                        | enum {`file`,`sqlite`}                                                                                                                     | HPO storage backend.                                                                                                                                                                                                                                                                                                                                                    |
+| freqai.optuna_hyperopt.continuous                     | true                          | bool                                                                                                                                       | Continuous HPO.                                                                                                                                                                                                                                                                                                                                                         |
+| freqai.optuna_hyperopt.warm_start                     | true                          | bool                                                                                                                                       | Warm start HPO with previous best value(s).                                                                                                                                                                                                                                                                                                                             |
+| freqai.optuna_hyperopt.n_startup_trials               | 15                            | int >= 0                                                                                                                                   | HPO startup trials.                                                                                                                                                                                                                                                                                                                                                     |
+| freqai.optuna_hyperopt.n_trials                       | 50                            | int >= 1                                                                                                                                   | Maximum HPO trials.                                                                                                                                                                                                                                                                                                                                                     |
+| freqai.optuna_hyperopt.n_jobs                         | CPU threads / 4               | int >= 1                                                                                                                                   | Parallel HPO workers.                                                                                                                                                                                                                                                                                                                                                   |
+| freqai.optuna_hyperopt.timeout                        | 7200                          | int >= 0                                                                                                                                   | HPO wall-clock timeout in seconds.                                                                                                                                                                                                                                                                                                                                      |
+| freqai.optuna_hyperopt.label_candles_step             | 1                             | int >= 1                                                                                                                                   | Step for Zigzag NATR horizon search space.                                                                                                                                                                                                                                                                                                                              |
+| freqai.optuna_hyperopt.train_candles_step             | 10                            | int >= 1                                                                                                                                   | Step for training sets size search space.                                                                                                                                                                                                                                                                                                                               |
+| freqai.optuna_hyperopt.space_reduction                | false                         | bool                                                                                                                                       | Enable/disable HPO search space reduction based on previous best parameters.                                                                                                                                                                                                                                                                                            |
+| freqai.optuna_hyperopt.space_fraction                 | 0.4                           | float [0,1]                                                                                                                                | Fraction of the HPO search space to use with `space_reduction`. Lower values create narrower search ranges around the best parameters. (Deprecated alias: `freqai.optuna_hyperopt.expansion_ratio`)                                                                                                                                                                     |
+| freqai.optuna_hyperopt.min_resource                   | 3                             | int >= 1                                                                                                                                   | Minimum resource per Hyperband pruner rung.                                                                                                                                                                                                                                                                                                                             |
+| freqai.optuna_hyperopt.seed                           | 1                             | int >= 0                                                                                                                                   | HPO RNG seed.                                                                                                                                                                                                                                                                                                                                                           |
 
 ## ReforceXY
 
index a9e27d74f909368703cc2aa2f32c2380adeb6fec..9b5b4b4d5645fad42d351373fd7de1f14d9a34cf 100644 (file)
@@ -2,13 +2,17 @@
 
 ## Purpose
 
-Research, prototype, and refine advanced ML‑driven and RL‑driven trading strategies for the FreqAI / Freqtrade ecosystem. Two strategies:
-
-- QuickAdapter: ML strategy + adaptive execution heuristics for partial exits, volatility‑aware stop/take profit logic.
-- ReforceXY: RL strategy and reward space analysis.
-  Reward space analysis goals:
-  - Provide deterministic synthetic sampling and statistical diagnostics to validate reward components and potential‑based shaping behavior and reason about reward parameterization before RL training.
-  - Maintain deterministic runs and rich diagnostics to accelerate iteration and anomaly debugging.
+Research, prototype, and refine advanced ML‑driven and RL‑driven trading
+strategies for the FreqAI / Freqtrade ecosystem. Two strategies:
+
+- QuickAdapter: ML strategy + adaptive execution heuristics for partial exits,
+  volatility‑aware stop/take profit logic.
+- ReforceXY: RL strategy and reward space analysis. Reward space analysis goals:
+  - Provide deterministic synthetic sampling and statistical diagnostics to
+    validate reward components and potential‑based shaping behavior and reason
+    about reward parameterization before RL training.
+  - Maintain deterministic runs and rich diagnostics to accelerate iteration and
+    anomaly debugging.
 
 ## Tech Stack
 
@@ -26,54 +30,89 @@ Research, prototype, and refine advanced ML‑driven and RL‑driven trading str
 
 ### Code Style
 
-- Base formatting guided by `.editorconfig` (UTF-8, LF, final newline, trimming whitespace, Python indent = 4 spaces, global indent_size=2 for non‑Python where appropriate, max Python line length target 100; Markdown max line length disabled).
+- Base formatting guided by `.editorconfig` (UTF-8, LF, final newline, trimming
+  whitespace, Python indent = 4 spaces, global indent_size=2 for non‑Python
+  where appropriate, max Python line length target 100; Markdown max line length
+  disabled).
 - Naming:
   - Functions & methods: `snake_case`.
   - Constants: `UPPER_SNAKE_CASE`.
-  - Internal strategy transient labels/features use prefixes: `"%-"` for engineered feature columns; special markers like `"&s-"` / `"&-"` for internal prediction target(s).
-  - Private helpers or internal state use leading underscore (`_exit_thresholds_calibration`).
-- Avoid one‑letter variable names; prefer descriptive one (e.g. `trade_duration_candles`, `natr_ratio_percent`).
-- Prefer explicit type hints (Python 3.11+ built‑in generics: `list[str]`, `dict[str, float]`).
-- Logging: use module logger (`logger = logging.getLogger(__name__)`), info for decision denials, warning for anomalies, error for exceptions.
+  - Internal strategy transient labels/features use prefixes: `"%-"` for
+    engineered feature columns; special markers like `"&s-"` / `"&-"` for
+    internal prediction target(s).
+  - Private helpers or internal state use leading underscore
+    (`_exit_thresholds_calibration`).
+- Avoid one‑letter variable names; prefer descriptive one (e.g.
+  `trade_duration_candles`, `natr_multiplier_fraction`).
+- Prefer explicit type hints (Python 3.11+ built‑in generics: `list[str]`,
+  `dict[str, float]`).
+- Logging: use module logger (`logger = logging.getLogger(__name__)`), info for
+  decision denials, warning for anomalies, error for exceptions.
 - No non-English terms in code, docs, comments, logs.
 
 ### Architecture Patterns
 
-- Strategy classes subclass `IStrategy` with model classes subclass `IFreqaiModel`; separate standalone strategy under root directory.
-- Reward Space Analysis: standalone CLI module (`reward_space_analysis.py`) + tests focusing on deterministic synthetic scenario generation, decomposition, statistical validation, potential‑based reward shaping (PBRS) variants.
-- Separation of concerns: reward analytic tooling does not depend on strategy runtime state; consumption is offline pre‑training / validation.
+- Strategy classes subclass `IStrategy` with model classes subclass
+  `IFreqaiModel`; separate standalone strategy under root directory.
+- Reward Space Analysis: standalone CLI module (`reward_space_analysis.py`) +
+  tests focusing on deterministic synthetic scenario generation, decomposition,
+  statistical validation, potential‑based reward shaping (PBRS) variants.
+- Separation of concerns: reward analytic tooling does not depend on strategy
+  runtime state; consumption is offline pre‑training / validation.
 
 ### Reward Space Analysis Testing Strategy
 
 - PyTest test modules in `reward_space_analysis/tests/<focus>`.
-- Focus: correctness of reward calculations, statistical invariants, PBRS modes, transforms, robustness, integration end‑to‑end.
-- Logging configured for concise INFO output; colored, warnings disabled by default in test runs.
-- Coverage goal: ≥85% on new analytical logic; critical reward shaping paths must be exercised (component bounds, invariance checks, exit attenuation kernels, transform functions, distribution metrics).
-- Focused test invocation examples (integration, statistical coherence, reward alignment) documented in README.
-- Run tests after: modifying reward logic; before major analyses; dependency or Python version changes; unexpected anomalies.
+- Focus: correctness of reward calculations, statistical invariants, PBRS modes,
+  transforms, robustness, integration end‑to‑end.
+- Logging configured for concise INFO output; colored, warnings disabled by
+  default in test runs.
+- Coverage goal: ≥85% on new analytical logic; critical reward shaping paths
+  must be exercised (component bounds, invariance checks, exit attenuation
+  kernels, transform functions, distribution metrics).
+- Focused test invocation examples (integration, statistical coherence, reward
+  alignment) documented in README.
+- Run tests after: modifying reward logic; before major analyses; dependency or
+  Python version changes; unexpected anomalies.
 
 ### Git Workflow
 
-- Primary branch: `main`. Feature / experiment branches should be: `feat/<concise-topic>`, `exp/<strategy-or-reward-param>`. Fix branches should be: `fix/<bug>`.
-- Commit messages: imperative, follow Conventional Commits. Emphasize WHY over raw WHAT when non‑obvious.
-- Avoid large mixed commits; isolate analytical tooling changes from strategy behavior changes.
-- Keep manifests and generated outputs out of version control (only code + templates); user data directories contain `.gitkeep` placeholders.
+- Primary branch: `main`. Feature / experiment branches should be:
+  `feat/<concise-topic>`, `exp/<strategy-or-reward-param>`. Fix branches should
+  be: `fix/<bug>`.
+- Commit messages: imperative, follow Conventional Commits. Emphasize WHY over
+  raw WHAT when non‑obvious.
+- Avoid large mixed commits; isolate analytical tooling changes from strategy
+  behavior changes.
+- Keep manifests and generated outputs out of version control (only code +
+  templates); user data directories contain `.gitkeep` placeholders.
 
 ## Domain Context
 
 - Strategies operate on sequential market OHLCV data.
-- QuickAdapterV3 features engineered include volatility metrics (NATR/ATR), momentum (MACD, EWO), market structure shift (extrema labeling via zigzag), band widths (BB, KC, VWAP), and price distance measures.
-- QuickAdapterV3 integrates dynamic volatility interpolation (weighted/moving average/interpolation modes) to derive adaptive NATR for stoploss/take profit calculations; partial exits based on staged NATR ratio percentages.
-- ReforceXY reward shaping emphasizes potential‑based reward shaping (PBRS) invariance: canonical vs non canonical modes, hold/entry/exit additive toggles, duration penalties, exit attenuation kernels (linear/power/half‑life/etc.).
+- QuickAdapterV3 features engineered include volatility metrics (NATR/ATR),
+  momentum (MACD, EWO), market structure shift (extrema labeling via zigzag),
+  band widths (BB, KC, VWAP), and price distance measures.
+- QuickAdapterV3 integrates dynamic volatility interpolation (weighted/moving
+  average/interpolation modes) to derive adaptive NATR for stoploss/take profit
+  calculations; partial exits based on staged NATR ratio percentages.
+- ReforceXY reward shaping emphasizes potential‑based reward shaping (PBRS)
+  invariance: canonical vs non canonical modes, hold/entry/exit additive
+  toggles, duration penalties, exit attenuation kernels
+  (linear/power/half‑life/etc.).
 
 ## Important Constraints
 
 - Python version ≥3.11 (target for type hints).
-- Trading mode affects short availability (spot disallows shorts); logic must gate short entries accordingly.
+- Trading mode affects short availability (spot disallows shorts); logic must
+  gate short entries accordingly.
 - Computations must handle missing/NaN gracefully.
-- Regulatory / business: none explicit; treat strategies as experimental research (no performance guarantees) and avoid embedding sensitive credentials.
+- Regulatory / business: none explicit; treat strategies as experimental
+  research (no performance guarantees) and avoid embedding sensitive
+  credentials.
 
 ## External Dependencies
 
 - Freqtrade / FreqAI framework APIs.
-- Docker images defined per strategy project (`Dockerfile.quickadapter`, `Dockerfile.reforcexy`) for containerized execution.
+- Docker images defined per strategy project (`Dockerfile.quickadapter`,
+  `Dockerfile.reforcexy`) for containerized execution.
index de39edace57fa9718e9f0d550f3e80216c3c9f90..e5b93d767d80633211ee14f8aecf1da79eb13be6 100644 (file)
@@ -39,7 +39,7 @@
     "price_last_balance": 0.0
   },
   "reversal_confirmation": {
-    "max_natr_ratio_percent": 0.05
+    "max_natr_multiplier_fraction": 0.05
   },
   "exchange": {
     "name": "binance",
     },
     "extrema_smoothing": {
       "method": "kaiser",
-      "window": 5,
+      "window_candles": 5,
       "beta": 12.0
     },
     "predictions_extrema": {
-      "thresholds_smoothing": "isodata"
+      "threshold_smoothing_method": "isodata"
     },
     "optuna_hyperopt": {
       "enabled": true,
       "&s-minima_threshold": -2,
       "&s-maxima_threshold": 2,
       "label_period_candles": 18,
-      "label_natr_ratio": 10.5,
+      "label_natr_multiplier": 10.5,
       "hp_rmse": -1,
       "train_rmse": -1
     },
         "4h"
       ],
       "label_period_candles": 18,
+      "label_natr_multiplier": 10.5,
       "label_metric": "kmedoids",
       "include_shifted_candles": 6,
       "DI_threshold": 10,
index 9a2e364aa5877600d7705db340e9b5e068471a02..e81c95586148bcfaae55c646e406ae8b03241cfa 100644 (file)
@@ -32,6 +32,7 @@ from Utils import (
     calculate_n_extrema,
     fit_regressor,
     format_number,
+    get_config_value,
     get_label_defaults,
     get_min_max_label_period_candles,
     get_optuna_callbacks,
@@ -71,7 +72,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
     https://github.com/sponsors/robcaulk
     """
 
-    version = "3.7.141"
+    version = "3.8.0"
 
     _TEST_SIZE: Final[float] = 0.1
 
@@ -170,17 +171,17 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         "jensenshannon",
     )
 
-    PREDICTIONS_EXTREMA_THRESHOLD_OUTLIER_DEFAULT: Final[float] = 0.999
-    PREDICTIONS_EXTREMA_THRESHOLDS_ALPHA_DEFAULT: Final[float] = 12.0
-    PREDICTIONS_EXTREMA_EXTREMA_FRACTION_DEFAULT: Final[float] = 1.0
+    PREDICTIONS_EXTREMA_OUTLIER_THRESHOLD_QUANTILE_DEFAULT: Final[float] = 0.999
+    PREDICTIONS_EXTREMA_SOFT_EXTREMUM_ALPHA_DEFAULT: Final[float] = 12.0
+    PREDICTIONS_EXTREMA_KEEP_EXTREMA_FRACTION_DEFAULT: Final[float] = 1.0
 
     FIT_LIVE_PREDICTIONS_CANDLES_DEFAULT: Final[int] = (
         DEFAULT_FIT_LIVE_PREDICTIONS_CANDLES
     )
     MIN_LABEL_PERIOD_CANDLES_DEFAULT: Final[int] = 12
     MAX_LABEL_PERIOD_CANDLES_DEFAULT: Final[int] = 24
-    MIN_LABEL_NATR_RATIO_DEFAULT: Final[float] = 9.0
-    MAX_LABEL_NATR_RATIO_DEFAULT: Final[float] = 12.0
+    MIN_LABEL_NATR_MULTIPLIER_DEFAULT: Final[float] = 9.0
+    MAX_LABEL_NATR_MULTIPLIER_DEFAULT: Final[float] = 12.0
     LABEL_KNN_N_NEIGHBORS_DEFAULT: Final[int] = 5
 
     @staticmethod
@@ -259,15 +260,53 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             "label_candles_step": 1,
             "train_candles_step": 10,
             "space_reduction": False,
-            "expansion_ratio": 0.4,
+            "space_fraction": 0.4,
             "min_resource": 3,
             "seed": 1,
         }
+        optuna_hyperopt = self.config.get("freqai", {}).get("optuna_hyperopt", {})
+        get_config_value(
+            optuna_hyperopt,
+            new_key="space_fraction",
+            old_key="expansion_ratio",
+            default=optuna_default_config["space_fraction"],
+            logger=logger,
+            new_path="freqai.optuna_hyperopt.space_fraction",
+            old_path="freqai.optuna_hyperopt.expansion_ratio",
+        )
         return {
             **optuna_default_config,
-            **self.config.get("freqai", {}).get("optuna_hyperopt", {}),
+            **optuna_hyperopt,
         }
 
+    @cached_property
+    def _min_label_period_candles(self) -> int:
+        return self.ft_params.get(
+            "min_label_period_candles",
+            QuickAdapterRegressorV3.MIN_LABEL_PERIOD_CANDLES_DEFAULT,
+        )
+
+    @cached_property
+    def _max_label_period_candles(self) -> int:
+        return self.ft_params.get(
+            "max_label_period_candles",
+            QuickAdapterRegressorV3.MAX_LABEL_PERIOD_CANDLES_DEFAULT,
+        )
+
+    @cached_property
+    def _min_label_natr_multiplier(self) -> float:
+        return self.ft_params.get(
+            "min_label_natr_multiplier",
+            QuickAdapterRegressorV3.MIN_LABEL_NATR_MULTIPLIER_DEFAULT,
+        )
+
+    @cached_property
+    def _max_label_natr_multiplier(self) -> float:
+        return self.ft_params.get(
+            "max_label_natr_multiplier",
+            QuickAdapterRegressorV3.MAX_LABEL_NATR_MULTIPLIER_DEFAULT,
+        )
+
     @cached_property
     def _label_frequency_candles(self) -> int:
         """
@@ -322,18 +361,21 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         if not isinstance(predictions_extrema, dict):
             predictions_extrema = {}
 
-        threshold_outlier = predictions_extrema.get(
-            "threshold_outlier",
-            QuickAdapterRegressorV3.PREDICTIONS_EXTREMA_THRESHOLD_OUTLIER_DEFAULT,
+        outlier_threshold_quantile = get_config_value(
+            predictions_extrema,
+            new_key="outlier_threshold_quantile",
+            old_key="threshold_outlier",
+            default=QuickAdapterRegressorV3.PREDICTIONS_EXTREMA_OUTLIER_THRESHOLD_QUANTILE_DEFAULT,
+            logger=logger,
+            new_path="freqai.predictions_extrema.outlier_threshold_quantile",
+            old_path="freqai.predictions_extrema.threshold_outlier",
         )
         if (
-            not isinstance(threshold_outlier, (int, float))
-            or not np.isfinite(threshold_outlier)
-            or not (0 < threshold_outlier < 1)
+            not isinstance(outlier_threshold_quantile, (int, float))
+            or not np.isfinite(outlier_threshold_quantile)
+            or not (0 < outlier_threshold_quantile < 1)
         ):
-            threshold_outlier = (
-                QuickAdapterRegressorV3.PREDICTIONS_EXTREMA_THRESHOLD_OUTLIER_DEFAULT
-            )
+            outlier_threshold_quantile = QuickAdapterRegressorV3.PREDICTIONS_EXTREMA_OUTLIER_THRESHOLD_QUANTILE_DEFAULT
 
         selection_method = str(
             predictions_extrema.get(
@@ -347,47 +389,63 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         ):
             selection_method = QuickAdapterRegressorV3._EXTREMA_SELECTION_METHODS[0]
 
-        thresholds_smoothing = str(
-            predictions_extrema.get(
-                "thresholds_smoothing",
-                QuickAdapterRegressorV3._THRESHOLD_METHODS[0],  # "mean"
+        threshold_smoothing_method = str(
+            get_config_value(
+                predictions_extrema,
+                new_key="threshold_smoothing_method",
+                old_key="thresholds_smoothing",
+                default=QuickAdapterRegressorV3._THRESHOLD_METHODS[0],  # "mean"
+                logger=logger,
+                new_path="freqai.predictions_extrema.threshold_smoothing_method",
+                old_path="freqai.predictions_extrema.thresholds_smoothing",
             )
         )
-        if thresholds_smoothing not in QuickAdapterRegressorV3._threshold_methods_set():
-            thresholds_smoothing = QuickAdapterRegressorV3._THRESHOLD_METHODS[
+        if (
+            threshold_smoothing_method
+            not in QuickAdapterRegressorV3._threshold_methods_set()
+        ):
+            threshold_smoothing_method = QuickAdapterRegressorV3._THRESHOLD_METHODS[
                 0
             ]  # "mean"
 
-        thresholds_alpha = predictions_extrema.get(
-            "thresholds_alpha",
-            QuickAdapterRegressorV3.PREDICTIONS_EXTREMA_THRESHOLDS_ALPHA_DEFAULT,
+        soft_extremum_alpha = get_config_value(
+            predictions_extrema,
+            new_key="soft_extremum_alpha",
+            old_key="thresholds_alpha",
+            default=QuickAdapterRegressorV3.PREDICTIONS_EXTREMA_SOFT_EXTREMUM_ALPHA_DEFAULT,
+            logger=logger,
+            new_path="freqai.predictions_extrema.soft_extremum_alpha",
+            old_path="freqai.predictions_extrema.thresholds_alpha",
         )
         if (
-            not isinstance(thresholds_alpha, (int, float))
-            or not np.isfinite(thresholds_alpha)
-            or thresholds_alpha < 0
+            not isinstance(soft_extremum_alpha, (int, float))
+            or not np.isfinite(soft_extremum_alpha)
+            or soft_extremum_alpha < 0
         ):
-            thresholds_alpha = (
-                QuickAdapterRegressorV3.PREDICTIONS_EXTREMA_THRESHOLDS_ALPHA_DEFAULT
-            )
-
-        extrema_fraction = predictions_extrema.get(
-            "extrema_fraction",
-            QuickAdapterRegressorV3.PREDICTIONS_EXTREMA_EXTREMA_FRACTION_DEFAULT,
+            soft_extremum_alpha = (
+                QuickAdapterRegressorV3.PREDICTIONS_EXTREMA_SOFT_EXTREMUM_ALPHA_DEFAULT
+            )
+
+        keep_extrema_fraction = get_config_value(
+            predictions_extrema,
+            new_key="keep_extrema_fraction",
+            old_key="extrema_fraction",
+            default=QuickAdapterRegressorV3.PREDICTIONS_EXTREMA_KEEP_EXTREMA_FRACTION_DEFAULT,
+            logger=logger,
+            new_path="freqai.predictions_extrema.keep_extrema_fraction",
+            old_path="freqai.predictions_extrema.extrema_fraction",
         )
-        if not isinstance(extrema_fraction, (int, float)) or not (
-            0 < extrema_fraction <= 1
+        if not isinstance(keep_extrema_fraction, (int, float)) or not (
+            0 < keep_extrema_fraction <= 1
         ):
-            extrema_fraction = (
-                QuickAdapterRegressorV3.PREDICTIONS_EXTREMA_EXTREMA_FRACTION_DEFAULT
-            )
+            keep_extrema_fraction = QuickAdapterRegressorV3.PREDICTIONS_EXTREMA_KEEP_EXTREMA_FRACTION_DEFAULT
 
         return {
-            "threshold_outlier": float(threshold_outlier),
+            "outlier_threshold_quantile": float(outlier_threshold_quantile),
             "selection_method": selection_method,
-            "thresholds_smoothing": thresholds_smoothing,
-            "thresholds_alpha": float(thresholds_alpha),
-            "extrema_fraction": float(extrema_fraction),
+            "threshold_smoothing_method": threshold_smoothing_method,
+            "soft_extremum_alpha": float(soft_extremum_alpha),
+            "keep_extrema_fraction": float(keep_extrema_fraction),
         }
 
     @property
@@ -438,7 +496,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         self._optuna_label_candle: dict[str, int] = {}
         self._optuna_label_candles: dict[str, int] = {}
         self._optuna_label_incremented_pairs: list[str] = []
-        self._default_label_natr_ratio, self._default_label_period_candles = (
+        self._default_label_natr_multiplier, self._default_label_period_candles = (
             get_label_defaults(self.ft_params, logger)
         )
         for pair in self.pairs:
@@ -477,10 +535,10 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                         "label_period_candles",
                         self._default_label_period_candles,
                     ),
-                    "label_natr_ratio": float(
+                    "label_natr_multiplier": float(
                         self.ft_params.get(
-                            "label_natr_ratio",
-                            self._default_label_natr_ratio,
+                            "label_natr_multiplier",
+                            self._default_label_natr_multiplier,
                         )
                     ),
                 }
@@ -522,7 +580,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             )
             logger.info(f"  space_reduction: {optuna_config.get('space_reduction')}")
             logger.info(
-                f"  expansion_ratio: {format_number(optuna_config.get('expansion_ratio'))}"
+                f"  space_fraction: {format_number(optuna_config.get('space_fraction'))}"
             )
             logger.info(f"  min_resource: {optuna_config.get('min_resource')}")
             logger.info(f"  seed: {optuna_config.get('seed')}")
@@ -736,16 +794,16 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             f"  selection_method: {predictions_extrema.get('selection_method')}"
         )
         logger.info(
-            f"  thresholds_smoothing: {predictions_extrema.get('thresholds_smoothing')}"
+            f"  threshold_smoothing_method: {predictions_extrema.get('threshold_smoothing_method')}"
         )
         logger.info(
-            f"  threshold_outlier: {format_number(predictions_extrema.get('threshold_outlier'))}"
+            f"  outlier_threshold_quantile: {format_number(predictions_extrema.get('outlier_threshold_quantile'))}"
         )
         logger.info(
-            f"  thresholds_alpha: {format_number(predictions_extrema.get('thresholds_alpha'))}"
+            f"  soft_extremum_alpha: {format_number(predictions_extrema.get('soft_extremum_alpha'))}"
         )
         logger.info(
-            f"  extrema_fraction: {format_number(predictions_extrema.get('extrema_fraction'))}"
+            f"  keep_extrema_fraction: {format_number(predictions_extrema.get('keep_extrema_fraction'))}"
         )
 
         logger.info("Label Configuration:")
@@ -753,17 +811,13 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             f"  fit_live_predictions_candles: {self.freqai_info.get('fit_live_predictions_candles', QuickAdapterRegressorV3.FIT_LIVE_PREDICTIONS_CANDLES_DEFAULT)}"
         )
         logger.info(f"  label_frequency_candles: {self._label_frequency_candles}")
+        logger.info(f"  min_label_period_candles: {self._min_label_period_candles}")
+        logger.info(f"  max_label_period_candles: {self._max_label_period_candles}")
         logger.info(
-            f"  min_label_period_candles: {self.ft_params.get('min_label_period_candles', QuickAdapterRegressorV3.MIN_LABEL_PERIOD_CANDLES_DEFAULT)}"
-        )
-        logger.info(
-            f"  max_label_period_candles: {self.ft_params.get('max_label_period_candles', QuickAdapterRegressorV3.MAX_LABEL_PERIOD_CANDLES_DEFAULT)}"
-        )
-        logger.info(
-            f"  min_label_natr_ratio: {format_number(self.ft_params.get('min_label_natr_ratio', QuickAdapterRegressorV3.MIN_LABEL_NATR_RATIO_DEFAULT))}"
+            f"  min_label_natr_multiplier: {format_number(self._min_label_natr_multiplier)}"
         )
         logger.info(
-            f"  max_label_natr_ratio: {format_number(self.ft_params.get('max_label_natr_ratio', QuickAdapterRegressorV3.MAX_LABEL_NATR_RATIO_DEFAULT))}"
+            f"  max_label_natr_multiplier: {format_number(self._max_label_natr_multiplier)}"
         )
 
         if self._optuna_hyperopt:
@@ -773,7 +827,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                 if params:
                     logger.info(
                         f"  {pair}: label_period_candles={params.get('label_period_candles')}, "
-                        f"label_natr_ratio={format_number(params.get('label_natr_ratio'))}"
+                        f"label_natr_multiplier={format_number(params.get('label_natr_multiplier'))}"
                     )
         else:
             logger.info("Label Parameters:")
@@ -781,7 +835,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                 f"  label_period_candles: {self.ft_params.get('label_period_candles', self._default_label_period_candles)}"
             )
             logger.info(
-                f"  label_natr_ratio: {format_number(float(self.ft_params.get('label_natr_ratio', self._default_label_natr_ratio)))}"
+                f"  label_natr_multiplier: {format_number(float(self.ft_params.get('label_natr_multiplier', self._default_label_natr_multiplier)))}"
             )
 
         logger.info("=" * 60)
@@ -950,7 +1004,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                     ),  # "hp"
                     model_training_parameters,
                     self._optuna_config.get("space_reduction"),
-                    self._optuna_config.get("expansion_ratio"),
+                    self._optuna_config.get("space_fraction"),
                 ),
                 direction=optuna.study.StudyDirection.MINIMIZE,
             )
@@ -1107,22 +1161,10 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                         ),
                         fit_live_predictions_candles,
                         self._optuna_config.get("label_candles_step"),
-                        min_label_period_candles=self.ft_params.get(
-                            "min_label_period_candles",
-                            QuickAdapterRegressorV3.MIN_LABEL_PERIOD_CANDLES_DEFAULT,
-                        ),
-                        max_label_period_candles=self.ft_params.get(
-                            "max_label_period_candles",
-                            QuickAdapterRegressorV3.MAX_LABEL_PERIOD_CANDLES_DEFAULT,
-                        ),
-                        min_label_natr_ratio=self.ft_params.get(
-                            "min_label_natr_ratio",
-                            QuickAdapterRegressorV3.MIN_LABEL_NATR_RATIO_DEFAULT,
-                        ),
-                        max_label_natr_ratio=self.ft_params.get(
-                            "max_label_natr_ratio",
-                            QuickAdapterRegressorV3.MAX_LABEL_NATR_RATIO_DEFAULT,
-                        ),
+                        min_label_period_candles=self._min_label_period_candles,
+                        max_label_period_candles=self._max_label_period_candles,
+                        min_label_natr_multiplier=self._min_label_natr_multiplier,
+                        max_label_natr_multiplier=self._max_label_natr_multiplier,
                     ),
                     directions=list(QuickAdapterRegressorV3._OPTUNA_LABEL_DIRECTIONS),
                 ),
@@ -1182,7 +1224,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                 pd.to_numeric(di_values, errors="coerce").dropna(), floc=0
             )
             cutoff = sp.stats.weibull_min.ppf(
-                self.predictions_extrema["threshold_outlier"], *f
+                self.predictions_extrema["outlier_threshold_quantile"], *f
             )
 
         dk.data["DI_value_mean"] = di_values.mean()
@@ -1197,10 +1239,12 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                 pair, QuickAdapterRegressorV3._OPTUNA_NAMESPACES[2]
             ).get("label_period_candles")  # "label"
         )
-        dk.data["extra_returns_per_train"]["label_natr_ratio"] = self.get_optuna_params(
-            pair,
-            QuickAdapterRegressorV3._OPTUNA_NAMESPACES[2],  # "label"
-        ).get("label_natr_ratio")
+        dk.data["extra_returns_per_train"]["label_natr_multiplier"] = (
+            self.get_optuna_params(
+                pair,
+                QuickAdapterRegressorV3._OPTUNA_NAMESPACES[2],  # "label"
+            ).get("label_natr_multiplier")
+        )
 
         hp_rmse = self.optuna_validate_value(
             self.get_optuna_value(pair, QuickAdapterRegressorV3._OPTUNA_NAMESPACES[0])
@@ -1258,30 +1302,35 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         pred_extrema = pred_df.get(EXTREMA_COLUMN).iloc[-thresholds_candles:].copy()
 
         extrema_selection = self.predictions_extrema["selection_method"]
-        thresholds_smoothing = self.predictions_extrema["thresholds_smoothing"]
-        extrema_fraction = self.predictions_extrema["extrema_fraction"]
+        threshold_smoothing_method = self.predictions_extrema[
+            "threshold_smoothing_method"
+        ]
+        keep_extrema_fraction = self.predictions_extrema["keep_extrema_fraction"]
 
         if (
-            thresholds_smoothing == QuickAdapterRegressorV3._THRESHOLD_METHODS[7]
+            threshold_smoothing_method == QuickAdapterRegressorV3._THRESHOLD_METHODS[7]
         ):  # "median"
             return QuickAdapterRegressorV3.median_min_max(
-                pred_extrema, extrema_selection, extrema_fraction
+                pred_extrema, extrema_selection, keep_extrema_fraction
             )
         elif (
-            thresholds_smoothing == QuickAdapterRegressorV3._THRESHOLD_METHODS[8]
+            threshold_smoothing_method == QuickAdapterRegressorV3._THRESHOLD_METHODS[8]
         ):  # "soft_extremum"
             return QuickAdapterRegressorV3.soft_extremum_min_max(
                 pred_extrema,
-                self.predictions_extrema["thresholds_alpha"],
+                self.predictions_extrema["soft_extremum_alpha"],
                 extrema_selection,
-                extrema_fraction,
+                keep_extrema_fraction,
             )
         elif (
-            thresholds_smoothing
+            threshold_smoothing_method
             in QuickAdapterRegressorV3._skimage_threshold_methods_set()
         ):
             return QuickAdapterRegressorV3.skimage_min_max(
-                pred_extrema, thresholds_smoothing, extrema_selection, extrema_fraction
+                pred_extrema,
+                threshold_smoothing_method,
+                extrema_selection,
+                keep_extrema_fraction,
             )
 
     @staticmethod
@@ -1297,15 +1346,15 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         pred_extrema: pd.Series,
         minima_indices: NDArray[np.intp],
         maxima_indices: NDArray[np.intp],
-        extrema_fraction: float = 1.0,
+        keep_extrema_fraction: float = 1.0,
     ) -> tuple[pd.Series, pd.Series]:
         n_minima = (
-            max(1, int(round(minima_indices.size * extrema_fraction)))
+            max(1, int(round(minima_indices.size * keep_extrema_fraction)))
             if minima_indices.size > 0
             else 0
         )
         n_maxima = (
-            max(1, int(round(maxima_indices.size * extrema_fraction)))
+            max(1, int(round(maxima_indices.size * keep_extrema_fraction)))
             if maxima_indices.size > 0
             else 0
         )
@@ -1330,15 +1379,15 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         pred_extrema: pd.Series,
         n_minima: int,
         n_maxima: int,
-        extrema_fraction: float = 1.0,
+        keep_extrema_fraction: float = 1.0,
     ) -> tuple[pd.Series, pd.Series]:
         pred_minima = (
-            pred_extrema.nsmallest(max(1, int(round(n_minima * extrema_fraction))))
+            pred_extrema.nsmallest(max(1, int(round(n_minima * keep_extrema_fraction))))
             if n_minima > 0
             else pd.Series(dtype=float)
         )
         pred_maxima = (
-            pred_extrema.nlargest(max(1, int(round(n_maxima * extrema_fraction))))
+            pred_extrema.nlargest(max(1, int(round(n_maxima * keep_extrema_fraction))))
             if n_maxima > 0
             else pd.Series(dtype=float)
         )
@@ -1349,7 +1398,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
     def get_pred_min_max(
         pred_extrema: pd.Series,
         extrema_selection: ExtremaSelectionMethod,
-        extrema_fraction: float = 1.0,
+        keep_extrema_fraction: float = 1.0,
     ) -> tuple[pd.Series, pd.Series]:
         pred_extrema = (
             pd.to_numeric(pred_extrema, errors="coerce")
@@ -1369,7 +1418,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                 pred_extrema,
                 minima_indices.size,
                 maxima_indices.size,
-                extrema_fraction,
+                keep_extrema_fraction,
             )
 
         elif (
@@ -1379,7 +1428,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                 QuickAdapterRegressorV3._get_extrema_indices(pred_extrema)
             )
             pred_minima, pred_maxima = QuickAdapterRegressorV3._get_ranked_peaks(
-                pred_extrema, minima_indices, maxima_indices, extrema_fraction
+                pred_extrema, minima_indices, maxima_indices, keep_extrema_fraction
             )
 
         elif (
@@ -1430,12 +1479,12 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         pred_extrema: pd.Series,
         alpha: float,
         extrema_selection: ExtremaSelectionMethod,
-        extrema_fraction: float = 1.0,
+        keep_extrema_fraction: float = 1.0,
     ) -> tuple[float, float]:
         if alpha < 0:
             raise ValueError(f"Invalid alpha {alpha!r}: must be >= 0")
         pred_minima, pred_maxima = QuickAdapterRegressorV3.get_pred_min_max(
-            pred_extrema, extrema_selection, extrema_fraction
+            pred_extrema, extrema_selection, keep_extrema_fraction
         )
         soft_minimum = soft_extremum(pred_minima, alpha=-alpha)
         if not np.isfinite(soft_minimum):
@@ -1449,10 +1498,10 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
     def median_min_max(
         pred_extrema: pd.Series,
         extrema_selection: ExtremaSelectionMethod,
-        extrema_fraction: float = 1.0,
+        keep_extrema_fraction: float = 1.0,
     ) -> tuple[float, float]:
         pred_minima, pred_maxima = QuickAdapterRegressorV3.get_pred_min_max(
-            pred_extrema, extrema_selection, extrema_fraction
+            pred_extrema, extrema_selection, keep_extrema_fraction
         )
 
         if pred_minima.empty:
@@ -1476,10 +1525,10 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         pred_extrema: pd.Series,
         method: str,
         extrema_selection: ExtremaSelectionMethod,
-        extrema_fraction: float = 1.0,
+        keep_extrema_fraction: float = 1.0,
     ) -> tuple[float, float]:
         pred_minima, pred_maxima = QuickAdapterRegressorV3.get_pred_min_max(
-            pred_extrema, extrema_selection, extrema_fraction
+            pred_extrema, extrema_selection, keep_extrema_fraction
         )
 
         try:
@@ -2622,14 +2671,14 @@ def hp_objective(
     model_training_best_parameters: dict[str, Any],
     model_training_parameters: dict[str, Any],
     space_reduction: bool,
-    expansion_ratio: float,
+    space_fraction: float,
 ) -> float:
     study_model_parameters = get_optuna_study_model_parameters(
         trial,
         regressor,
         model_training_best_parameters,
         space_reduction,
-        expansion_ratio,
+        space_fraction,
     )
     model_training_parameters = {**model_training_parameters, **study_model_parameters}
 
@@ -2658,8 +2707,8 @@ def label_objective(
     candles_step: int,
     min_label_period_candles: int = QuickAdapterRegressorV3.MIN_LABEL_PERIOD_CANDLES_DEFAULT,
     max_label_period_candles: int = QuickAdapterRegressorV3.MAX_LABEL_PERIOD_CANDLES_DEFAULT,
-    min_label_natr_ratio: float = QuickAdapterRegressorV3.MIN_LABEL_NATR_RATIO_DEFAULT,
-    max_label_natr_ratio: float = QuickAdapterRegressorV3.MAX_LABEL_NATR_RATIO_DEFAULT,
+    min_label_natr_multiplier: float = QuickAdapterRegressorV3.MIN_LABEL_NATR_MULTIPLIER_DEFAULT,
+    max_label_natr_multiplier: float = QuickAdapterRegressorV3.MAX_LABEL_NATR_MULTIPLIER_DEFAULT,
 ) -> tuple[int, float, float, float, float, float, float]:
     min_label_period_candles, max_label_period_candles, candles_step = (
         get_min_max_label_period_candles(
@@ -2678,8 +2727,11 @@ def label_objective(
         max_label_period_candles,
         step=candles_step,
     )
-    label_natr_ratio = trial.suggest_float(
-        "label_natr_ratio", min_label_natr_ratio, max_label_natr_ratio, step=0.05
+    label_natr_multiplier = trial.suggest_float(
+        "label_natr_multiplier",
+        min_label_natr_multiplier,
+        max_label_natr_multiplier,
+        step=0.05,
     )
 
     df = df.iloc[
@@ -2705,7 +2757,7 @@ def label_objective(
     ) = zigzag(
         df,
         natr_period=label_period_candles,
-        natr_ratio=label_natr_ratio,
+        natr_multiplier=label_natr_multiplier,
     )
 
     median_amplitude = np.nanmedian(np.asarray(pivots_amplitudes, dtype=float))
index e0422cc17abaf89e719f33195ed401268dda6991..2359bf4a6ec05d5c0307152db4086d21cfe1767d 100644 (file)
@@ -48,6 +48,7 @@ from Utils import (
     ewo,
     format_number,
     get_callable_sha256,
+    get_config_value,
     get_distance,
     get_label_defaults,
     get_weighted_extrema,
@@ -105,7 +106,7 @@ class QuickAdapterV3(IStrategy):
     _TRADING_MODES: Final[tuple[TradingMode, ...]] = ("spot", "margin", "futures")
 
     def version(self) -> str:
-        return "3.3.191"
+        return "3.8.0"
 
     timeframe = "5m"
 
@@ -122,25 +123,25 @@ class QuickAdapterV3(IStrategy):
     }
 
     default_reversal_confirmation: ClassVar[dict[str, int | float]] = {
-        "lookback_period": 0,
-        "decay_ratio": 0.5,
-        "min_natr_ratio_percent": 0.0095,
-        "max_natr_ratio_percent": 0.075,
+        "lookback_period_candles": 0,
+        "decay_fraction": 0.5,
+        "min_natr_multiplier_fraction": 0.0095,
+        "max_natr_multiplier_fraction": 0.075,
     }
 
     position_adjustment_enable = True
 
-    # {stage: (natr_ratio_percent, stake_percent, color)}
+    # {stage: (natr_multiplier_fraction, stake_percent, color)}
     partial_exit_stages: ClassVar[dict[int, tuple[float, float, str]]] = {
         0: (0.4858, 0.4, "lime"),
         1: (0.6180, 0.3, "yellow"),
         2: (0.7640, 0.2, "coral"),
     }
 
-    # (natr_ratio_percent, stake_percent, color)
+    # (natr_multiplier_fraction, stake_percent, color)
     _FINAL_EXIT_STAGE: Final[tuple[float, float, str]] = (1.0, 1.0, "deepskyblue")
 
-    _CUSTOM_STOPLOSS_NATR_RATIO_PERCENT: Final[float] = 0.7860
+    _CUSTOM_STOPLOSS_NATR_MULTIPLIER_FRACTION: Final[float] = 0.7860
 
     _ANNOTATION_LINE_OFFSET_CANDLES: Final[int] = 10
 
@@ -328,7 +329,7 @@ class QuickAdapterV3(IStrategy):
             / self.freqai_info.get("identifier")
         )
         feature_parameters = self.freqai_info.get("feature_parameters", {})
-        self._default_label_natr_ratio, self._default_label_period_candles = (
+        self._default_label_natr_multiplier, self._default_label_period_candles = (
             get_label_defaults(feature_parameters, logger)
         )
         self._label_params: dict[str, dict[str, Any]] = {}
@@ -341,10 +342,10 @@ class QuickAdapterV3(IStrategy):
                         "label_period_candles",
                         self._default_label_period_candles,
                     ),
-                    "label_natr_ratio": float(
+                    "label_natr_multiplier": float(
                         feature_parameters.get(
-                            "label_natr_ratio",
-                            self._default_label_natr_ratio,
+                            "label_natr_multiplier",
+                            self._default_label_natr_multiplier,
                         )
                     ),
                 }
@@ -407,46 +408,56 @@ class QuickAdapterV3(IStrategy):
 
         logger.info("Extrema Smoothing:")
         logger.info(f"  method: {self.extrema_smoothing['method']}")
-        logger.info(f"  window: {self.extrema_smoothing['window']}")
+        logger.info(f"  window_candles: {self.extrema_smoothing['window_candles']}")
         logger.info(f"  beta: {format_number(self.extrema_smoothing['beta'])}")
         logger.info(f"  polyorder: {self.extrema_smoothing['polyorder']}")
         logger.info(f"  mode: {self.extrema_smoothing['mode']}")
         logger.info(f"  sigma: {format_number(self.extrema_smoothing['sigma'])}")
 
         logger.info("Reversal Confirmation:")
-        logger.info(f"  lookback_period: {self._reversal_lookback_period}")
-        logger.info(f"  decay_ratio: {format_number(self._reversal_decay_ratio)}")
         logger.info(
-            f"  min_natr_ratio_percent: {format_number(self._reversal_min_natr_ratio_percent)}"
+            f"  lookback_period_candles: {self._reversal_lookback_period_candles}"
         )
+        logger.info(f"  decay_fraction: {format_number(self._reversal_decay_fraction)}")
         logger.info(
-            f"  max_natr_ratio_percent: {format_number(self._reversal_max_natr_ratio_percent)}"
+            f"  min_natr_multiplier_fraction: {format_number(self._reversal_min_natr_multiplier_fraction)}"
+        )
+        logger.info(
+            f"  max_natr_multiplier_fraction: {format_number(self._reversal_max_natr_multiplier_fraction)}"
         )
 
         exit_pricing = self.config.get("exit_pricing", {})
-        trade_price_target = exit_pricing.get("trade_price_target", "moving_average")
+        trade_price_target_method = get_config_value(
+            exit_pricing,
+            new_key="trade_price_target_method",
+            old_key="trade_price_target",
+            default=TRADE_PRICE_TARGETS[0],  # "moving_average"
+            logger=logger,
+            new_path="exit_pricing.trade_price_target_method",
+            old_path="exit_pricing.trade_price_target",
+        )
         logger.info("Exit Pricing:")
-        logger.info(f"  trade_price_target: {trade_price_target}")
+        logger.info(f"  trade_price_target_method: {trade_price_target_method}")
         logger.info(f"  thresholds_calibration: {self._exit_thresholds_calibration}")
 
         logger.info("Custom Stoploss:")
         logger.info(
-            f"  natr_ratio_percent: {format_number(QuickAdapterV3._CUSTOM_STOPLOSS_NATR_RATIO_PERCENT)}"
+            f"  natr_multiplier_fraction: {format_number(QuickAdapterV3._CUSTOM_STOPLOSS_NATR_MULTIPLIER_FRACTION)}"
         )
 
         logger.info("Partial Exit Stages:")
         for stage, (
-            natr_ratio_percent,
+            natr_multiplier_fraction,
             stake_percent,
             color,
         ) in QuickAdapterV3.partial_exit_stages.items():
             logger.info(
-                f"  stage {stage}: natr_ratio_percent={format_number(natr_ratio_percent)}, stake_percent={format_number(stake_percent)}, color={color}"
+                f"  stage {stage}: natr_multiplier_fraction={format_number(natr_multiplier_fraction)}, stake_percent={format_number(stake_percent)}, color={color}"
             )
 
         final_stage = max(QuickAdapterV3.partial_exit_stages.keys(), default=-1) + 1
         logger.info(
-            f"Final Exit Stage: stage {final_stage}: natr_ratio_percent={format_number(QuickAdapterV3._FINAL_EXIT_STAGE[0])}, stake_percent={format_number(QuickAdapterV3._FINAL_EXIT_STAGE[1])}, color={QuickAdapterV3._FINAL_EXIT_STAGE[2]}"
+            f"Final Exit Stage: stage {final_stage}: natr_multiplier_fraction={format_number(QuickAdapterV3._FINAL_EXIT_STAGE[0])}, stake_percent={format_number(QuickAdapterV3._FINAL_EXIT_STAGE[1])}, color={QuickAdapterV3._FINAL_EXIT_STAGE[2]}"
         )
 
         logger.info("Protections:")
@@ -477,56 +488,92 @@ class QuickAdapterV3(IStrategy):
 
     def _init_reversal_confirmation_defaults(self) -> None:
         reversal_confirmation = self.config.get("reversal_confirmation", {})
-        lookback_period = reversal_confirmation.get(
-            "lookback_period",
-            QuickAdapterV3.default_reversal_confirmation["lookback_period"],
-        )
-        decay_ratio = reversal_confirmation.get(
-            "decay_ratio", QuickAdapterV3.default_reversal_confirmation["decay_ratio"]
-        )
-        min_natr_ratio_percent = reversal_confirmation.get(
-            "min_natr_ratio_percent",
-            QuickAdapterV3.default_reversal_confirmation["min_natr_ratio_percent"],
-        )
-        max_natr_ratio_percent = reversal_confirmation.get(
-            "max_natr_ratio_percent",
-            QuickAdapterV3.default_reversal_confirmation["max_natr_ratio_percent"],
+        lookback_period_candles = get_config_value(
+            reversal_confirmation,
+            new_key="lookback_period_candles",
+            old_key="lookback_period",
+            default=QuickAdapterV3.default_reversal_confirmation[
+                "lookback_period_candles"
+            ],
+            logger=logger,
+            new_path="reversal_confirmation.lookback_period_candles",
+            old_path="reversal_confirmation.lookback_period",
+        )
+        decay_fraction = get_config_value(
+            reversal_confirmation,
+            new_key="decay_fraction",
+            old_key="decay_ratio",
+            default=QuickAdapterV3.default_reversal_confirmation["decay_fraction"],
+            logger=logger,
+            new_path="reversal_confirmation.decay_fraction",
+            old_path="reversal_confirmation.decay_ratio",
+        )
+
+        min_natr_multiplier_fraction = get_config_value(
+            reversal_confirmation,
+            new_key="min_natr_multiplier_fraction",
+            old_key="min_natr_ratio_percent",
+            default=QuickAdapterV3.default_reversal_confirmation[
+                "min_natr_multiplier_fraction"
+            ],
+            logger=logger,
+            new_path="reversal_confirmation.min_natr_multiplier_fraction",
+            old_path="reversal_confirmation.min_natr_ratio_percent",
+        )
+        max_natr_multiplier_fraction = get_config_value(
+            reversal_confirmation,
+            new_key="max_natr_multiplier_fraction",
+            old_key="max_natr_ratio_percent",
+            default=QuickAdapterV3.default_reversal_confirmation[
+                "max_natr_multiplier_fraction"
+            ],
+            logger=logger,
+            new_path="reversal_confirmation.max_natr_multiplier_fraction",
+            old_path="reversal_confirmation.max_natr_ratio_percent",
         )
 
-        if not isinstance(lookback_period, int) or lookback_period < 0:
+        if not isinstance(lookback_period_candles, int) or lookback_period_candles < 0:
             logger.warning(
-                f"Invalid reversal_confirmation lookback_period {lookback_period!r}: must be >= 0. Using default {QuickAdapterV3.default_reversal_confirmation['lookback_period']!r}"
+                f"Invalid reversal_confirmation lookback_period_candles {lookback_period_candles!r}: must be >= 0. Using default {QuickAdapterV3.default_reversal_confirmation['lookback_period_candles']!r}"
             )
-            lookback_period = QuickAdapterV3.default_reversal_confirmation[
-                "lookback_period"
+            lookback_period_candles = QuickAdapterV3.default_reversal_confirmation[
+                "lookback_period_candles"
             ]
 
-        if not isinstance(decay_ratio, (int, float)) or not (0.0 < decay_ratio <= 1.0):
+        if not isinstance(decay_fraction, (int, float)) or not (
+            0.0 < decay_fraction <= 1.0
+        ):
             logger.warning(
-                f"Invalid reversal_confirmation decay_ratio {decay_ratio!r}: must be in range (0, 1]. Using default {QuickAdapterV3.default_reversal_confirmation['decay_ratio']!r}"
+                f"Invalid reversal_confirmation decay_fraction {decay_fraction!r}: must be in range (0, 1]. Using default {QuickAdapterV3.default_reversal_confirmation['decay_fraction']!r}"
             )
-            decay_ratio = QuickAdapterV3.default_reversal_confirmation["decay_ratio"]
+            decay_fraction = QuickAdapterV3.default_reversal_confirmation[
+                "decay_fraction"
+            ]
 
-        min_natr_ratio_percent, max_natr_ratio_percent = validate_range(
-            min_natr_ratio_percent,
-            max_natr_ratio_percent,
+        min_natr_multiplier_fraction, max_natr_multiplier_fraction = validate_range(
+            min_natr_multiplier_fraction,
+            max_natr_multiplier_fraction,
             logger,
-            name="natr_ratio_percent",
+            name="natr_multiplier_fraction",
             default_min=QuickAdapterV3.default_reversal_confirmation[
-                "min_natr_ratio_percent"
+                "min_natr_multiplier_fraction"
             ],
             default_max=QuickAdapterV3.default_reversal_confirmation[
-                "max_natr_ratio_percent"
+                "max_natr_multiplier_fraction"
             ],
             allow_equal=False,
             non_negative=True,
             finite_only=True,
         )
 
-        self._reversal_lookback_period = int(lookback_period)
-        self._reversal_decay_ratio = float(decay_ratio)
-        self._reversal_min_natr_ratio_percent = float(min_natr_ratio_percent)
-        self._reversal_max_natr_ratio_percent = float(max_natr_ratio_percent)
+        self._reversal_lookback_period_candles = int(lookback_period_candles)
+        self._reversal_decay_fraction = float(decay_fraction)
+        self._reversal_min_natr_multiplier_fraction = float(
+            min_natr_multiplier_fraction
+        )
+        self._reversal_max_natr_multiplier_fraction = float(
+            max_natr_multiplier_fraction
+        )
 
     def feature_engineering_expand_all(
         self, dataframe: DataFrame, period: int, metadata: dict[str, Any], **kwargs
@@ -710,27 +757,33 @@ class QuickAdapterV3(IStrategy):
         if isinstance(label_period_candles, int):
             self._label_params[pair]["label_period_candles"] = label_period_candles
 
-    def get_label_natr_ratio(self, pair: str) -> float:
-        label_natr_ratio = self._label_params.get(pair, {}).get("label_natr_ratio")
-        if label_natr_ratio and isinstance(label_natr_ratio, float):
-            return label_natr_ratio
+    def get_label_natr_multiplier(self, pair: str) -> float:
+        label_natr_multiplier = self._label_params.get(pair, {}).get(
+            "label_natr_multiplier"
+        )
+        if label_natr_multiplier and isinstance(label_natr_multiplier, float):
+            return label_natr_multiplier
+        feature_parameters = self.freqai_info.get("feature_parameters", {})
         return float(
-            self.freqai_info.get("feature_parameters", {}).get(
-                "label_natr_ratio",
-                self._default_label_natr_ratio,
+            feature_parameters.get(
+                "label_natr_multiplier", self._default_label_natr_multiplier
             )
         )
 
-    def set_label_natr_ratio(self, pair: str, label_natr_ratio: float) -> None:
-        if isinstance(label_natr_ratio, float) and np.isfinite(label_natr_ratio):
-            self._label_params[pair]["label_natr_ratio"] = label_natr_ratio
+    def set_label_natr_multiplier(
+        self, pair: str, label_natr_multiplier: float
+    ) -> None:
+        if isinstance(label_natr_multiplier, float) and np.isfinite(
+            label_natr_multiplier
+        ):
+            self._label_params[pair]["label_natr_multiplier"] = label_natr_multiplier
 
-    def get_label_natr_ratio_percent(self, pair: str, percent: float) -> float:
-        if not isinstance(percent, float) or not (0.0 <= percent <= 1.0):
+    def get_label_natr_multiplier_fraction(self, pair: str, fraction: float) -> float:
+        if not isinstance(fraction, float) or not (0.0 <= fraction <= 1.0):
             raise ValueError(
-                f"Invalid percent {percent!r}: must be a float in range [0, 1]"
+                f"Invalid fraction {fraction!r}: must be a float in range [0, 1]"
             )
-        return self.get_label_natr_ratio(pair) * percent
+        return self.get_label_natr_multiplier(pair) * fraction
 
     @staticmethod
     def _get_extrema_weighting_params(
@@ -996,14 +1049,23 @@ class QuickAdapterV3(IStrategy):
             )
             smoothing_method = SMOOTHING_METHODS[0]
 
-        smoothing_window = extrema_smoothing.get(
-            "window", DEFAULTS_EXTREMA_SMOOTHING["window"]
+        smoothing_window_candles = get_config_value(
+            extrema_smoothing,
+            new_key="window_candles",
+            old_key="window",
+            default=DEFAULTS_EXTREMA_SMOOTHING["window_candles"],
+            logger=logger,
+            new_path="freqai.extrema_smoothing.window_candles",
+            old_path="freqai.extrema_smoothing.window",
         )
-        if not isinstance(smoothing_window, int) or smoothing_window < 3:
+        if (
+            not isinstance(smoothing_window_candles, int)
+            or smoothing_window_candles < 3
+        ):
             logger.warning(
-                f"Invalid extrema_smoothing window {smoothing_window!r}: must be an integer >= 3. Using default {DEFAULTS_EXTREMA_SMOOTHING['window']!r}"
+                f"Invalid extrema_smoothing window_candles {smoothing_window_candles!r}: must be an integer >= 3. Using default {DEFAULTS_EXTREMA_SMOOTHING['window_candles']!r}"
             )
-            smoothing_window = DEFAULTS_EXTREMA_SMOOTHING["window"]
+            smoothing_window_candles = int(DEFAULTS_EXTREMA_SMOOTHING["window_candles"])
 
         smoothing_beta = extrema_smoothing.get(
             "beta", DEFAULTS_EXTREMA_SMOOTHING["beta"]
@@ -1051,7 +1113,7 @@ class QuickAdapterV3(IStrategy):
 
         return {
             "method": smoothing_method,
-            "window": int(smoothing_window),
+            "window_candles": int(smoothing_window_candles),
             "beta": smoothing_beta,
             "polyorder": int(smoothing_polyorder),
             "mode": smoothing_mode,
@@ -1080,7 +1142,7 @@ class QuickAdapterV3(IStrategy):
     ) -> DataFrame:
         pair = str(metadata.get("pair"))
         label_period_candles = self.get_label_period_candles(pair)
-        label_natr_ratio = self.get_label_natr_ratio(pair)
+        label_natr_multiplier = self.get_label_natr_multiplier(pair)
         (
             pivots_indices,
             _,
@@ -1094,7 +1156,7 @@ class QuickAdapterV3(IStrategy):
         ) = zigzag(
             dataframe,
             natr_period=label_period_candles,
-            natr_ratio=label_natr_ratio,
+            natr_multiplier=label_natr_multiplier,
         )
         label_period = datetime.timedelta(
             minutes=len(dataframe) * timeframe_to_minutes(self.config.get("timeframe"))
@@ -1105,11 +1167,11 @@ class QuickAdapterV3(IStrategy):
 
         if len(pivots_indices) == 0:
             logger.warning(
-                f"[{pair}] No extrema to label | label_period: {QuickAdapterV3._td_format(label_period)} | label_period_candles: {label_period_candles} | label_natr_ratio: {format_number(label_natr_ratio)}"
+                f"[{pair}] No extrema to label | label_period: {QuickAdapterV3._td_format(label_period)} | label_period_candles: {label_period_candles} | label_natr_multiplier: {format_number(label_natr_multiplier)}"
             )
         else:
             logger.info(
-                f"[{pair}] Labeled {len(pivots_indices)} extrema | label_period: {QuickAdapterV3._td_format(label_period)} | label_period_candles: {label_period_candles} | label_natr_ratio: {format_number(label_natr_ratio)}"
+                f"[{pair}] Labeled {len(pivots_indices)} extrema | label_period: {QuickAdapterV3._td_format(label_period)} | label_period_candles: {label_period_candles} | label_natr_multiplier: {format_number(label_natr_multiplier)}"
             )
             dataframe.loc[pivots_indices, EXTREMA_COLUMN] = pivots_directions
 
@@ -1159,7 +1221,7 @@ class QuickAdapterV3(IStrategy):
         dataframe[EXTREMA_COLUMN] = smooth_extrema(
             weighted_extrema,
             self.extrema_smoothing["method"],
-            self.extrema_smoothing["window"],
+            self.extrema_smoothing["window_candles"],
             self.extrema_smoothing["beta"],
             self.extrema_smoothing["polyorder"],
             self.extrema_smoothing["mode"],
@@ -1173,18 +1235,21 @@ class QuickAdapterV3(IStrategy):
     ) -> DataFrame:
         dataframe = self.freqai.start(dataframe, metadata, self)
 
-        dataframe["DI_catch"] = np.where(
-            dataframe.get("DI_values") > dataframe.get("DI_cutoff"),
-            0,
-            1,
-        )
+        di_values = dataframe.get("DI_values")
+        di_cutoff = dataframe.get("DI_cutoff")
+        if di_values is not None and di_cutoff is not None:
+            dataframe["DI_catch"] = np.where(di_values > di_cutoff, 0, 1)
+        else:
+            dataframe["DI_catch"] = 1
 
         pair = str(metadata.get("pair"))
 
-        self.set_label_period_candles(
-            pair, dataframe.get("label_period_candles").iloc[-1]
-        )
-        self.set_label_natr_ratio(pair, dataframe.get("label_natr_ratio").iloc[-1])
+        label_period_candles_series = dataframe.get("label_period_candles")
+        if label_period_candles_series is not None:
+            self.set_label_period_candles(pair, label_period_candles_series.iloc[-1])
+        label_natr_multiplier_series = dataframe.get("label_natr_multiplier")
+        if label_natr_multiplier_series is not None:
+            self.set_label_natr_multiplier(pair, label_natr_multiplier_series.iloc[-1])
 
         dataframe["natr_label_period_candles"] = ta.NATR(
             dataframe, timeperiod=self.get_label_period_candles(pair)
@@ -1391,9 +1456,15 @@ class QuickAdapterV3(IStrategy):
     def get_trade_natr(
         self, df: DataFrame, trade: Trade, trade_duration_candles: int
     ) -> Optional[float]:
-        trade_price_target = self.config.get("exit_pricing", {}).get(
-            "trade_price_target",
-            TRADE_PRICE_TARGETS[0],  # "moving_average"
+        exit_pricing = self.config.get("exit_pricing", {})
+        trade_price_target_method = get_config_value(
+            exit_pricing,
+            new_key="trade_price_target_method",
+            old_key="trade_price_target",
+            default=TRADE_PRICE_TARGETS[0],  # "moving_average"
+            logger=logger,
+            new_path="exit_pricing.trade_price_target_method",
+            old_path="exit_pricing.trade_price_target",
         )
         trade_price_target_methods: dict[str, Callable[[], Optional[float]]] = {
             # 0 - "moving_average"
@@ -1409,13 +1480,15 @@ class QuickAdapterV3(IStrategy):
                 df, trade
             ),
         }
-        trade_price_target_fn = trade_price_target_methods.get(trade_price_target)
-        if trade_price_target_fn is None:
+        trade_price_target_method_fn = trade_price_target_methods.get(
+            trade_price_target_method
+        )
+        if trade_price_target_method_fn is None:
             raise ValueError(
-                f"Invalid trade_price_target {trade_price_target!r}. "
+                f"Invalid trade_price_target_method {trade_price_target_method!r}. "
                 f"Supported: {', '.join(TRADE_PRICE_TARGETS)}"
             )
-        return trade_price_target_fn()
+        return trade_price_target_method_fn()
 
     @staticmethod
     def get_trade_exit_stage(trade: Trade) -> int:
@@ -1438,11 +1511,11 @@ class QuickAdapterV3(IStrategy):
         df: DataFrame,
         trade: Trade,
         current_rate: float,
-        natr_ratio_percent: float,
+        natr_multiplier_fraction: float,
     ) -> Optional[float]:
-        if not (0.0 <= natr_ratio_percent <= 1.0):
+        if not (0.0 <= natr_multiplier_fraction <= 1.0):
             raise ValueError(
-                f"Invalid natr_ratio_percent {natr_ratio_percent!r}: must be in range [0, 1]"
+                f"Invalid natr_multiplier_fraction {natr_multiplier_fraction!r}: must be in range [0, 1]"
             )
         trade_duration_candles = self.get_trade_duration_candles(df, trade)
         if not QuickAdapterV3.is_trade_duration_valid(trade_duration_candles):
@@ -1453,7 +1526,9 @@ class QuickAdapterV3(IStrategy):
         return (
             current_rate
             * (trade_natr / 100.0)
-            * self.get_label_natr_ratio_percent(trade.pair, natr_ratio_percent)
+            * self.get_label_natr_multiplier_fraction(
+                trade.pair, natr_multiplier_fraction
+            )
             * QuickAdapterV3.get_stoploss_factor(
                 trade_duration_candles + int(round(trade.nr_of_successful_exits**1.5))
             )
@@ -1465,11 +1540,11 @@ class QuickAdapterV3(IStrategy):
         return math.log10(9.75 + 0.25 * trade_duration_candles)
 
     def get_take_profit_distance(
-        self, df: DataFrame, trade: Trade, natr_ratio_percent: float
+        self, df: DataFrame, trade: Trade, natr_multiplier_fraction: float
     ) -> Optional[float]:
-        if not (0.0 <= natr_ratio_percent <= 1.0):
+        if not (0.0 <= natr_multiplier_fraction <= 1.0):
             raise ValueError(
-                f"Invalid natr_ratio_percent {natr_ratio_percent!r}: must be in range [0, 1]"
+                f"Invalid natr_multiplier_fraction {natr_multiplier_fraction!r}: must be in range [0, 1]"
             )
         trade_duration_candles = self.get_trade_duration_candles(df, trade)
         if not QuickAdapterV3.is_trade_duration_valid(trade_duration_candles):
@@ -1480,7 +1555,9 @@ class QuickAdapterV3(IStrategy):
         return (
             trade.open_rate
             * (trade_natr / 100.0)
-            * self.get_label_natr_ratio_percent(trade.pair, natr_ratio_percent)
+            * self.get_label_natr_multiplier_fraction(
+                trade.pair, natr_multiplier_fraction
+            )
             * QuickAdapterV3.get_take_profit_factor(trade_duration_candles)
         )
 
@@ -1533,7 +1610,10 @@ class QuickAdapterV3(IStrategy):
             return None
 
         stoploss_distance = self.get_stoploss_distance(
-            df, trade, current_rate, QuickAdapterV3._CUSTOM_STOPLOSS_NATR_RATIO_PERCENT
+            df,
+            trade,
+            current_rate,
+            QuickAdapterV3._CUSTOM_STOPLOSS_NATR_MULTIPLIER_FRACTION,
         )
         if isna(stoploss_distance) or stoploss_distance <= 0:
             return None
@@ -1555,13 +1635,13 @@ class QuickAdapterV3(IStrategy):
     def get_take_profit_price(
         self, df: DataFrame, trade: Trade, exit_stage: int
     ) -> Optional[float]:
-        natr_ratio_percent = (
+        natr_multiplier_fraction = (
             QuickAdapterV3.partial_exit_stages[exit_stage][0]
             if exit_stage in QuickAdapterV3.partial_exit_stages
             else QuickAdapterV3._FINAL_EXIT_STAGE[0]
         )
         take_profit_distance = self.get_take_profit_distance(
-            df, trade, natr_ratio_percent
+            df, trade, natr_multiplier_fraction
         )
         if isna(take_profit_distance) or take_profit_distance <= 0:
             return None
@@ -1754,8 +1834,8 @@ class QuickAdapterV3(IStrategy):
         self,
         df: DataFrame,
         pair: str,
-        min_natr_ratio_percent: float,
-        max_natr_ratio_percent: float,
+        min_natr_multiplier_fraction: float,
+        max_natr_multiplier_fraction: float,
         candle_idx: int = -1,
         interpolation_direction: InterpolationDirection = "direct",
         quantile_exponent: float = 1.5,
@@ -1770,8 +1850,8 @@ class QuickAdapterV3(IStrategy):
         cache_key: CandleDeviationCacheKey = (
             pair,
             df_signature,
-            float(min_natr_ratio_percent),
-            float(max_natr_ratio_percent),
+            float(min_natr_multiplier_fraction),
+            float(max_natr_multiplier_fraction),
             candle_idx,
             interpolation_direction,
             float(quantile_exponent),
@@ -1802,17 +1882,17 @@ class QuickAdapterV3(IStrategy):
         if (
             interpolation_direction == QuickAdapterV3._INTERPOLATION_DIRECTIONS[0]
         ):  # "direct"
-            natr_ratio_percent = (
-                min_natr_ratio_percent
-                + (max_natr_ratio_percent - min_natr_ratio_percent)
+            natr_multiplier_fraction = (
+                min_natr_multiplier_fraction
+                + (max_natr_multiplier_fraction - min_natr_multiplier_fraction)
                 * candle_label_natr_value_quantile**quantile_exponent
             )
         elif (
             interpolation_direction == QuickAdapterV3._INTERPOLATION_DIRECTIONS[1]
         ):  # "inverse"
-            natr_ratio_percent = (
-                max_natr_ratio_percent
-                - (max_natr_ratio_percent - min_natr_ratio_percent)
+            natr_multiplier_fraction = (
+                max_natr_multiplier_fraction
+                - (max_natr_multiplier_fraction - min_natr_multiplier_fraction)
                 * candle_label_natr_value_quantile**quantile_exponent
             )
         else:
@@ -1822,7 +1902,7 @@ class QuickAdapterV3(IStrategy):
             )
         candle_deviation = (
             candle_label_natr_value / 100.0
-        ) * self.get_label_natr_ratio_percent(pair, natr_ratio_percent)
+        ) * self.get_label_natr_multiplier_fraction(pair, natr_multiplier_fraction)
         self._candle_deviation_cache[cache_key] = candle_deviation
         return self._candle_deviation_cache[cache_key]
 
@@ -1831,8 +1911,8 @@ class QuickAdapterV3(IStrategy):
         df: DataFrame,
         pair: str,
         side: TradeDirection,
-        min_natr_ratio_percent: float,
-        max_natr_ratio_percent: float,
+        min_natr_multiplier_fraction: float,
+        max_natr_multiplier_fraction: float,
         candle_idx: int = -1,
     ) -> float:
         df_signature = QuickAdapterV3._df_signature(df)
@@ -1847,16 +1927,16 @@ class QuickAdapterV3(IStrategy):
             df_signature,
             side,
             candle_idx,
-            float(min_natr_ratio_percent),
-            float(max_natr_ratio_percent),
+            float(min_natr_multiplier_fraction),
+            float(max_natr_multiplier_fraction),
         )
         if cache_key in self._candle_threshold_cache:
             return self._candle_threshold_cache[cache_key]
         current_deviation = self._calculate_candle_deviation(
             df,
             pair,
-            min_natr_ratio_percent=min_natr_ratio_percent,
-            max_natr_ratio_percent=max_natr_ratio_percent,
+            min_natr_multiplier_fraction=min_natr_multiplier_fraction,
+            max_natr_multiplier_fraction=max_natr_multiplier_fraction,
             candle_idx=candle_idx,
             interpolation_direction=QuickAdapterV3._INTERPOLATION_DIRECTIONS[
                 0
@@ -1903,10 +1983,10 @@ class QuickAdapterV3(IStrategy):
         side: TradeDirection,
         order: OrderType,
         rate: float,
-        lookback_period: int,
-        decay_ratio: float,
-        min_natr_ratio_percent: float,
-        max_natr_ratio_percent: float,
+        lookback_period_candles: int,
+        decay_fraction: float,
+        min_natr_multiplier_fraction: float,
+        max_natr_multiplier_fraction: float,
     ) -> bool:
         """Confirm a directional reversal using a volatility-adaptive current-candle
         threshold and optionally a backward confirmation chain with geometric decay.
@@ -1915,9 +1995,9 @@ class QuickAdapterV3(IStrategy):
         --------
         1. Compute a deviation-based threshold on the latest candle (-1). The current
            rate must strictly break it (long: rate > threshold; short: rate < threshold).
-        2. If lookback_period > 0, for each k = 1..lookback_period:
-             - Decay (min_natr_ratio_percent, max_natr_ratio_percent) by (decay_ratio ** k),
-               clamped to [0, 1].
+        2. If lookback_period_candles > 0, for each k = 1..lookback_period_candles:
+             - Decay (min_natr_multiplier_fraction, max_natr_multiplier_fraction) by
+               (decay_fraction ** k), clamped to [0, 1].
              - Recompute the threshold on candle index -(k+1).
              - Require close[-k] to have strictly broken that historical threshold.
         3. If an intermediate close or threshold is non-finite, chain evaluation aborts
@@ -1935,14 +2015,14 @@ class QuickAdapterV3(IStrategy):
             Context (affects log wording only).
         rate : float
             Candidate execution price; must break the current threshold.
-        lookback_period : int
+        lookback_period_candles : int
             Number of historical confirmation steps requested; truncated to history.
-        decay_ratio : float
-            Geometric decay factor per step (0 < decay_ratio <= 1); 1.0 disables decay.
-        min_natr_ratio_percent : float
-            Lower bound fraction (e.g. 0.009 = 0.9%).
-        max_natr_ratio_percent : float
-            Upper bound fraction (>= lower bound).
+        decay_fraction : float
+            Geometric decay factor per step (0 < decay_fraction <= 1); 1.0 disables decay.
+        min_natr_multiplier_fraction : float
+            Lower-bound fraction (e.g. 0.009 = 0.9%).
+        max_natr_multiplier_fraction : float
+            Upper-bound fraction (>= lower bound).
 
         Returns
         -------
@@ -1952,22 +2032,22 @@ class QuickAdapterV3(IStrategy):
 
         Fallback Semantics
         ------------------
-        Missing / non-finite intermediate data  stop chain; return current candle result.
+        Missing / non-finite intermediate data -> stop chain; return current candle result.
         This may yield True on partial history, weakening strict multi-candle guarantees.
 
         Rejection Conditions
         --------------------
         Empty dataframe, invalid side/order, non-finite rate, negative lookback,
-        decay_ratio outside (0,1], invalid min/max ordering, failure to break current
+        decay_fraction outside (0,1], invalid min/max ordering, failure to break current
         threshold, or failed historical step comparison.
 
         Complexity
         ----------
-        O(lookback_period) threshold computations.
+        O(lookback_period_candles) threshold computations.
 
         Logging
         -------
-        Logs rejection reasons (invalid decay_ratio, threshold not broken, failed step).
+        Logs rejection reasons (invalid decay_fraction, threshold not broken, failed step).
         Fallback aborts are silent.
 
         Limitations
@@ -1983,29 +2063,28 @@ class QuickAdapterV3(IStrategy):
         if not isinstance(rate, (int, float)) or not np.isfinite(rate):
             return False
         if (
-            not isinstance(min_natr_ratio_percent, (int, float))
-            or not isinstance(max_natr_ratio_percent, (int, float))
-            or not np.isfinite(min_natr_ratio_percent)
-            or not np.isfinite(max_natr_ratio_percent)
-            or min_natr_ratio_percent < 0
-            or max_natr_ratio_percent < 0
-            or min_natr_ratio_percent > max_natr_ratio_percent
+            not isinstance(min_natr_multiplier_fraction, (int, float))
+            or not isinstance(max_natr_multiplier_fraction, (int, float))
+            or not np.isfinite(min_natr_multiplier_fraction)
+            or not np.isfinite(max_natr_multiplier_fraction)
+            or min_natr_multiplier_fraction < 0
+            or max_natr_multiplier_fraction < 0
+            or min_natr_multiplier_fraction > max_natr_multiplier_fraction
         ):
             return False
 
         trade_direction = side
 
         max_lookback_period = max(0, len(df) - 1)
-        if lookback_period > max_lookback_period:
-            lookback_period = max_lookback_period
-        if not isinstance(decay_ratio, (int, float)):
+        lookback_period_candles = min(lookback_period_candles, max_lookback_period)
+        if not isinstance(decay_fraction, (int, float)):
             logger.debug(
-                f"[{pair}] Denied {trade_direction} {order}: invalid decay_ratio type"
+                f"[{pair}] Denied {trade_direction} {order}: invalid decay_fraction type"
             )
             return False
-        if not (0.0 < decay_ratio <= 1.0):
+        if not (0.0 < decay_fraction <= 1.0):
             logger.debug(
-                f"[{pair}] Denied {trade_direction} {order}: invalid decay_ratio {decay_ratio}, must be in (0, 1]"
+                f"[{pair}] Denied {trade_direction} {order}: invalid decay_fraction {decay_fraction}, must be in (0, 1]"
             )
             return False
 
@@ -2013,8 +2092,8 @@ class QuickAdapterV3(IStrategy):
             df,
             pair,
             side,
-            min_natr_ratio_percent=min_natr_ratio_percent,
-            max_natr_ratio_percent=max_natr_ratio_percent,
+            min_natr_multiplier_fraction=min_natr_multiplier_fraction,
+            max_natr_multiplier_fraction=max_natr_multiplier_fraction,
             candle_idx=-1,
         )
         current_ok = np.isfinite(current_threshold) and (
@@ -2036,29 +2115,29 @@ class QuickAdapterV3(IStrategy):
             )
             return False
 
-        if lookback_period == 0:
+        if lookback_period_candles == 0:
             return current_ok
 
-        for k in range(1, lookback_period + 1):
+        for k in range(1, lookback_period_candles + 1):
             close_k = df.iloc[-k].get("close")
             if not isinstance(close_k, (int, float)) or not np.isfinite(close_k):
                 return current_ok
 
-            decay_factor = decay_ratio**k
-            decayed_min_natr_ratio_percent = max(
-                0.0, min(1.0, min_natr_ratio_percent * decay_factor)
+            decay_factor = decay_fraction**k
+            decayed_min_natr_multiplier_fraction = max(
+                0.0, min(1.0, min_natr_multiplier_fraction * decay_factor)
             )
-            decayed_max_natr_ratio_percent = max(
-                decayed_min_natr_ratio_percent,
-                min(1.0, max_natr_ratio_percent * decay_factor),
+            decayed_max_natr_multiplier_fraction = max(
+                decayed_min_natr_multiplier_fraction,
+                min(1.0, max_natr_multiplier_fraction * decay_factor),
             )
 
             threshold_k = self._calculate_candle_threshold(
                 df,
                 pair,
                 side,
-                min_natr_ratio_percent=decayed_min_natr_ratio_percent,
-                max_natr_ratio_percent=decayed_max_natr_ratio_percent,
+                min_natr_multiplier_fraction=decayed_min_natr_multiplier_fraction,
+                max_natr_multiplier_fraction=decayed_max_natr_multiplier_fraction,
                 candle_idx=-(k + 1),
             )
             if not isinstance(threshold_k, (int, float)) or not np.isfinite(
@@ -2077,7 +2156,7 @@ class QuickAdapterV3(IStrategy):
                     f"[{pair}] Denied {trade_direction} {order}: "
                     f"close_k[{-k}] {format_number(close_k)} "
                     f"did not break threshold_k[{-(k + 1)}] {format_number(threshold_k)} "
-                    f"(decayed natr_ratio_percent: min={format_number(decayed_min_natr_ratio_percent)}, max={format_number(decayed_max_natr_ratio_percent)})"
+                    f"(decayed natr_multiplier_fraction: min={format_number(decayed_min_natr_multiplier_fraction)}, max={format_number(decayed_max_natr_multiplier_fraction)})"
                 )
                 return False
 
@@ -2274,10 +2353,10 @@ class QuickAdapterV3(IStrategy):
                 QuickAdapterV3._TRADE_DIRECTIONS[0],  # "long"
                 QuickAdapterV3._ORDER_TYPES[1],  # "exit"
                 current_rate,
-                self._reversal_lookback_period,
-                self._reversal_decay_ratio,
-                self._reversal_min_natr_ratio_percent,
-                self._reversal_max_natr_ratio_percent,
+                self._reversal_lookback_period_candles,
+                self._reversal_decay_fraction,
+                self._reversal_min_natr_multiplier_fraction,
+                self._reversal_max_natr_multiplier_fraction,
             )
         ):
             return "minima_detected_short"
@@ -2292,10 +2371,10 @@ class QuickAdapterV3(IStrategy):
                 QuickAdapterV3._TRADE_DIRECTIONS[1],  # "short"
                 QuickAdapterV3._ORDER_TYPES[1],  # "exit"
                 current_rate,
-                self._reversal_lookback_period,
-                self._reversal_decay_ratio,
-                self._reversal_min_natr_ratio_percent,
-                self._reversal_max_natr_ratio_percent,
+                self._reversal_lookback_period_candles,
+                self._reversal_decay_fraction,
+                self._reversal_min_natr_multiplier_fraction,
+                self._reversal_max_natr_multiplier_fraction,
             )
         ):
             return "maxima_detected_long"
@@ -2455,10 +2534,10 @@ class QuickAdapterV3(IStrategy):
             side,
             QuickAdapterV3._ORDER_TYPES[0],  # "entry"
             rate,
-            self._reversal_lookback_period,
-            self._reversal_decay_ratio,
-            self._reversal_min_natr_ratio_percent,
-            self._reversal_max_natr_ratio_percent,
+            self._reversal_lookback_period_candles,
+            self._reversal_decay_fraction,
+            self._reversal_min_natr_multiplier_fraction,
+            self._reversal_max_natr_multiplier_fraction,
         ):
             return True
         return False
index 4364ee3f0656a9334a137a8ce146b5262ee30a46..98881add6067635c883c34d9e2807ad9c3763b98 100644 (file)
@@ -131,7 +131,7 @@ TRADE_PRICE_TARGETS: Final[tuple[TradePriceTarget, ...]] = (
 
 DEFAULTS_EXTREMA_SMOOTHING: Final[dict[str, Any]] = {
     "method": SMOOTHING_METHODS[0],  # "gaussian"
-    "window": 5,
+    "window_candles": 5,
     "beta": 8.0,
     "polyorder": 3,
     "mode": SMOOTHING_MODES[0],  # "mirror"
@@ -167,7 +167,7 @@ def get_distance(p1: T, p2: T) -> T:
 
 
 def midpoint(value1: T, value2: T) -> T:
-    """Calculate the midpoint (geometric center) between two values."""
+    """Calculate the midpoint between two values."""
     return (value1 + value2) / 2
 
 
@@ -265,7 +265,7 @@ def zero_phase_filter(
 def smooth_extrema(
     series: pd.Series,
     method: SmoothingMethod = DEFAULTS_EXTREMA_SMOOTHING["method"],
-    window: int = DEFAULTS_EXTREMA_SMOOTHING["window"],
+    window: int = DEFAULTS_EXTREMA_SMOOTHING["window_candles"],
     beta: float = DEFAULTS_EXTREMA_SMOOTHING["beta"],
     polyorder: int = DEFAULTS_EXTREMA_SMOOTHING["polyorder"],
     mode: SmoothingMode = DEFAULTS_EXTREMA_SMOOTHING["mode"],
@@ -1462,7 +1462,7 @@ class TrendDirection(IntEnum):
 def zigzag(
     df: pd.DataFrame,
     natr_period: int = 14,
-    natr_ratio: float = 9.0,
+    natr_multiplier: float = 9.0,
 ) -> tuple[
     list[int],
     list[float],
@@ -1491,7 +1491,7 @@ def zigzag(
     natr_values = (ta.NATR(df, timeperiod=natr_period).bfill() / 100.0).to_numpy()
 
     indices: list[int] = df.index.tolist()
-    thresholds: NDArray[np.floating] = natr_values * natr_ratio
+    thresholds: NDArray[np.floating] = natr_values * natr_multiplier
     closes = df.get("close").to_numpy()
     log_closes = np.log(closes)
     highs = df.get("high").to_numpy()
@@ -2085,22 +2085,44 @@ def fit_regressor(
     return model
 
 
+def _build_int_range(
+    frange: tuple[float, float],
+    min_val: int = 1,
+) -> tuple[int, int]:
+    lo, hi = math.ceil(frange[0]), math.floor(frange[1])
+    if lo > hi:
+        lo = hi = max(min_val, int(round((frange[0] + frange[1]) / 2)))
+    return max(min_val, lo), max(min_val, hi)
+
+
+def _optuna_suggest_int_from_range(
+    trial: optuna.trial.Trial,
+    name: str,
+    frange: tuple[float, float],
+    *,
+    min_val: int = 1,
+    log: bool = False,
+) -> int:
+    int_range = _build_int_range(frange, min_val=min_val)
+    return trial.suggest_int(name, int_range[0], int_range[1], log=log)
+
+
 def get_optuna_study_model_parameters(
     trial: optuna.trial.Trial,
     regressor: Regressor,
     model_training_best_parameters: dict[str, Any],
     space_reduction: bool,
-    expansion_ratio: float,
+    space_fraction: float,
 ) -> dict[str, Any]:
     if regressor not in set(REGRESSORS):
         raise ValueError(
             f"Invalid regressor {regressor!r}. Supported: {', '.join(REGRESSORS)}"
         )
-    if not isinstance(expansion_ratio, (int, float)) or not (
-        0.0 <= expansion_ratio <= 1.0
+    if not isinstance(space_fraction, (int, float)) or not (
+        0.0 <= space_fraction <= 1.0
     ):
         raise ValueError(
-            f"Invalid expansion_ratio {expansion_ratio!r}: must be in range [0, 1]"
+            f"Invalid space_fraction {space_fraction!r}: must be in range [0, 1]"
         )
 
     def _build_ranges(
@@ -2128,11 +2150,12 @@ def get_optuna_study_model_parameters(
                 if param in log_scaled_params:
                     if center_value <= 0:
                         continue
-                    factor = 1 + expansion_ratio
+                    # Proportional reduction in log-space
+                    factor = math.pow(default_max / default_min, space_fraction / 2)
                     new_min = center_value / factor
                     new_max = center_value * factor
                 else:
-                    margin = (default_max - default_min) * expansion_ratio / 2
+                    margin = (default_max - default_min) * space_fraction / 2
                     new_min = center_value - margin
                     new_max = center_value + margin
                 param_min = max(default_min, new_min)
@@ -2175,18 +2198,19 @@ def get_optuna_study_model_parameters(
 
         ranges = _build_ranges(default_ranges, log_scaled_params)
 
-        tree_method = trial.suggest_categorical("tree_method", ["hist", "approx"])
         grow_policy = trial.suggest_categorical(
             "grow_policy", ["depthwise", "lossguide"]
         )
+        tree_method = (
+            "hist"
+            if grow_policy == "lossguide"
+            else trial.suggest_categorical("tree_method", ["hist", "approx"])
+        )
 
         return {
             # Boosting/Training
-            "n_estimators": trial.suggest_int(
-                "n_estimators",
-                int(ranges["n_estimators"][0]),
-                int(ranges["n_estimators"][1]),
-                log=True,
+            "n_estimators": _optuna_suggest_int_from_range(
+                trial, "n_estimators", ranges["n_estimators"], min_val=1, log=True
             ),
             "learning_rate": trial.suggest_float(
                 "learning_rate",
@@ -2200,19 +2224,14 @@ def get_optuna_study_model_parameters(
             **(
                 {
                     "max_depth": 0,
-                    "max_leaves": trial.suggest_int(
-                        "max_leaves",
-                        int(ranges["max_leaves"][0]),
-                        int(ranges["max_leaves"][1]),
-                        log=True,
+                    "max_leaves": _optuna_suggest_int_from_range(
+                        trial, "max_leaves", ranges["max_leaves"], min_val=2, log=True
                     ),
                 }
                 if grow_policy == "lossguide"
                 else {
-                    "max_depth": trial.suggest_int(
-                        "max_depth",
-                        int(ranges["max_depth"][0]),
-                        int(ranges["max_depth"][1]),
+                    "max_depth": _optuna_suggest_int_from_range(
+                        trial, "max_depth", ranges["max_depth"], min_val=1
                     ),
                 }
             ),
@@ -2290,11 +2309,8 @@ def get_optuna_study_model_parameters(
 
         return {
             # Boosting/Training
-            "n_estimators": trial.suggest_int(
-                "n_estimators",
-                int(ranges["n_estimators"][0]),
-                int(ranges["n_estimators"][1]),
-                log=True,
+            "n_estimators": _optuna_suggest_int_from_range(
+                trial, "n_estimators", ranges["n_estimators"], min_val=1, log=True
             ),
             "learning_rate": trial.suggest_float(
                 "learning_rate",
@@ -2303,10 +2319,8 @@ def get_optuna_study_model_parameters(
                 log=True,
             ),
             # Tree structure
-            "num_leaves": trial.suggest_int(
-                "num_leaves",
-                int(ranges["num_leaves"][0]),
-                int(ranges["num_leaves"][1]),
+            "num_leaves": _optuna_suggest_int_from_range(
+                trial, "num_leaves", ranges["num_leaves"], min_val=2
             ),
             # Leaf constraints
             "min_child_weight": trial.suggest_float(
@@ -2315,10 +2329,8 @@ def get_optuna_study_model_parameters(
                 ranges["min_child_weight"][1],
                 log=True,
             ),
-            "min_child_samples": trial.suggest_int(
-                "min_child_samples",
-                int(ranges["min_child_samples"][0]),
-                int(ranges["min_child_samples"][1]),
+            "min_child_samples": _optuna_suggest_int_from_range(
+                trial, "min_child_samples", ranges["min_child_samples"], min_val=1
             ),
             "min_split_gain": trial.suggest_float(
                 "min_split_gain",
@@ -2330,10 +2342,8 @@ def get_optuna_study_model_parameters(
             "subsample": trial.suggest_float(
                 "subsample", ranges["subsample"][0], ranges["subsample"][1]
             ),
-            "subsample_freq": trial.suggest_int(
-                "subsample_freq",
-                int(ranges["subsample_freq"][0]),
-                int(ranges["subsample_freq"][1]),
+            "subsample_freq": _optuna_suggest_int_from_range(
+                trial, "subsample_freq", ranges["subsample_freq"], min_val=1
             ),
             "colsample_bytree": trial.suggest_float(
                 "colsample_bytree",
@@ -2348,10 +2358,8 @@ def get_optuna_study_model_parameters(
                 "reg_lambda", ranges["reg_lambda"][0], ranges["reg_lambda"][1], log=True
             ),
             # Binning
-            "max_bin": trial.suggest_int(
-                "max_bin",
-                int(ranges["max_bin"][0]),
-                int(ranges["max_bin"][1]),
+            "max_bin": _optuna_suggest_int_from_range(
+                trial, "max_bin", ranges["max_bin"], min_val=2
             ),
         }
 
@@ -2402,11 +2410,8 @@ def get_optuna_study_model_parameters(
 
         return {
             # Boosting/Training
-            "max_iter": trial.suggest_int(
-                "max_iter",
-                int(ranges["max_iter"][0]),
-                int(ranges["max_iter"][1]),
-                log=True,
+            "max_iter": _optuna_suggest_int_from_range(
+                trial, "max_iter", ranges["max_iter"], min_val=1, log=True
             ),
             "learning_rate": trial.suggest_float(
                 "learning_rate",
@@ -2418,17 +2423,15 @@ def get_optuna_study_model_parameters(
             "max_depth": trial.suggest_categorical(
                 "max_depth", [None, 2, 3, 4, 5, 6, 7, 8, 10, 12, 15]
             ),
-            "max_leaf_nodes": trial.suggest_int(
-                "max_leaf_nodes",
-                int(ranges["max_leaf_nodes"][0]),
-                int(ranges["max_leaf_nodes"][1]),
-                log=True,
+            "max_leaf_nodes": _optuna_suggest_int_from_range(
+                trial, "max_leaf_nodes", ranges["max_leaf_nodes"], min_val=2, log=True
             ),
             # Leaf constraints
-            "min_samples_leaf": trial.suggest_int(
+            "min_samples_leaf": _optuna_suggest_int_from_range(
+                trial,
                 "min_samples_leaf",
-                int(ranges["min_samples_leaf"][0]),
-                int(ranges["min_samples_leaf"][1]),
+                ranges["min_samples_leaf"],
+                min_val=1,
                 log=True,
             ),
             # Sampling
@@ -2440,16 +2443,12 @@ def get_optuna_study_model_parameters(
             # Regularization
             "l2_regularization": l2_regularization,
             # Binning
-            "max_bins": trial.suggest_int(
-                "max_bins",
-                int(ranges["max_bins"][0]),
-                int(ranges["max_bins"][1]),
+            "max_bins": _optuna_suggest_int_from_range(
+                trial, "max_bins", ranges["max_bins"], min_val=2
             ),
             # Early stopping
-            "n_iter_no_change": trial.suggest_int(
-                "n_iter_no_change",
-                int(ranges["n_iter_no_change"][0]),
-                int(ranges["n_iter_no_change"][1]),
+            "n_iter_no_change": _optuna_suggest_int_from_range(
+                trial, "n_iter_no_change", ranges["n_iter_no_change"], min_val=1
             ),
             "tol": trial.suggest_float(
                 "tol",
@@ -2617,6 +2616,33 @@ def floor_to_step(value: float | int, step: int) -> int:
     return int(math.floor(float(value) / step) * step)
 
 
+def get_config_value(
+    config: Any,
+    *,
+    new_key: str,
+    old_key: str,
+    default: Any,
+    logger: Logger,
+    new_path: str,
+    old_path: str,
+) -> Any:
+    if not isinstance(config, dict):
+        return default
+
+    if new_key in config:
+        return config[new_key]
+
+    if old_key in config:
+        logger.warning(
+            f"Deprecated config key {old_path} detected; use {new_path} instead"
+        )
+        config[new_key] = config.pop(old_key)
+        return config[new_key]
+
+    config[new_key] = default
+    return default
+
+
 def validate_range(
     min_val: float | int,
     max_val: float | int,
@@ -2689,28 +2715,49 @@ def get_label_defaults(
     *,
     default_min_label_period_candles: int = 12,
     default_max_label_period_candles: int = 24,
-    default_min_label_natr_ratio: float = 9.0,
-    default_max_label_natr_ratio: float = 12.0,
+    default_min_label_natr_multiplier: float = 9.0,
+    default_max_label_natr_multiplier: float = 12.0,
 ) -> tuple[float, int]:
-    min_label_natr_ratio = feature_parameters.get(
-        "min_label_natr_ratio", default_min_label_natr_ratio
+    min_label_natr_multiplier = get_config_value(
+        feature_parameters,
+        new_key="min_label_natr_multiplier",
+        old_key="min_label_natr_ratio",
+        default=default_min_label_natr_multiplier,
+        logger=logger,
+        new_path="freqai.feature_parameters.min_label_natr_multiplier",
+        old_path="freqai.feature_parameters.min_label_natr_ratio",
     )
-    max_label_natr_ratio = feature_parameters.get(
-        "max_label_natr_ratio", default_max_label_natr_ratio
+    max_label_natr_multiplier = get_config_value(
+        feature_parameters,
+        new_key="max_label_natr_multiplier",
+        old_key="max_label_natr_ratio",
+        default=default_max_label_natr_multiplier,
+        logger=logger,
+        new_path="freqai.feature_parameters.max_label_natr_multiplier",
+        old_path="freqai.feature_parameters.max_label_natr_ratio",
     )
-    min_label_natr_ratio, max_label_natr_ratio = validate_range(
-        min_label_natr_ratio,
-        max_label_natr_ratio,
+    min_label_natr_multiplier, max_label_natr_multiplier = validate_range(
+        min_label_natr_multiplier,
+        max_label_natr_multiplier,
         logger,
-        name="label_natr_ratio",
-        default_min=default_min_label_natr_ratio,
-        default_max=default_max_label_natr_ratio,
+        name="label_natr_multiplier",
+        default_min=default_min_label_natr_multiplier,
+        default_max=default_max_label_natr_multiplier,
         allow_equal=False,
         non_negative=True,
         finite_only=True,
     )
-    default_label_natr_ratio = float(
-        midpoint(min_label_natr_ratio, max_label_natr_ratio)
+    default_label_natr_multiplier = float(
+        midpoint(min_label_natr_multiplier, max_label_natr_multiplier)
+    )
+    get_config_value(
+        feature_parameters,
+        new_key="label_natr_multiplier",
+        old_key="label_natr_ratio",
+        default=default_label_natr_multiplier,
+        logger=logger,
+        new_path="freqai.feature_parameters.label_natr_multiplier",
+        old_path="freqai.feature_parameters.label_natr_ratio",
     )
 
     min_label_period_candles = feature_parameters.get(
@@ -2734,4 +2781,4 @@ def get_label_defaults(
         round(midpoint(min_label_period_candles, max_label_period_candles))
     )
 
-    return default_label_natr_ratio, default_label_period_candles
+    return default_label_natr_multiplier, default_label_period_candles