From 178dfc826a595d325dacee164bd58afaaa18cefa Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 15 Feb 2026 15:21:02 +0100 Subject: [PATCH] feat(quickadapter): add VRAM-aware CatBoost GPU hyperparameter ranges --- README.md | 2 + .../freqaimodels/QuickAdapterRegressorV3.py | 2 +- .../user_data/strategies/QuickAdapterV3.py | 2 +- quickadapter/user_data/strategies/Utils.py | 52 +++++++++++++------ 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6e9a387..c4e8e79 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ docker compose up -d --build | reversal_confirmation.max_natr_multiplier_fraction | 0.075 | float [0,1] | Upper bound fraction (>= lower bound) for volatility adjusted reversal threshold. | | _Regressor model_ | | | | | freqai.regressor | `xgboost` | enum {`xgboost`,`lightgbm`,`histgradientboostingregressor`,`ngboost`,`catboost`} | Machine learning regressor algorithm. | +| _Model training parameters_ | | | | +| freqai.model_training_parameters.gpu_vram_gb | 80 | enum {8,10,12,16,24,32,40,48,64,80} | GPU VRAM capacity (GB) for CatBoost. Constrains `depth`, `border_count`, and `max_ctr_complexity` ranges to fit memory. | | _Data split parameters_ | | | | | freqai.data_split_parameters.method | `train_test_split` | enum {`train_test_split`,`timeseries_split`} | Data splitting strategy. `train_test_split` for sequential split, `timeseries_split` for chronological split with configurable gap. | | freqai.data_split_parameters.test_size | 0.1 / None | float (0,1) \| int >= 1 \| None | Test set size. Float for fraction, int for count. Default: 0.1 for `train_test_split`, None for `timeseries_split` (sklearn dynamic sizing). | diff --git a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py index d428194..c1d08a7 100644 --- a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py +++ b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py @@ -96,7 +96,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel): https://github.com/sponsors/robcaulk """ - version = "3.11.2" + version = "3.11.3" _TEST_SIZE: Final[float] = 0.1 diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index 789bd9c..d40dc69 100644 --- a/quickadapter/user_data/strategies/QuickAdapterV3.py +++ b/quickadapter/user_data/strategies/QuickAdapterV3.py @@ -109,7 +109,7 @@ class QuickAdapterV3(IStrategy): _PLOT_EXTREMA_MIN_EPS: Final[float] = 0.01 def version(self) -> str: - return "3.11.2" + return "3.11.3" timeframe = "5m" timeframe_minutes = timeframe_to_minutes(timeframe) diff --git a/quickadapter/user_data/strategies/Utils.py b/quickadapter/user_data/strategies/Utils.py index 4f04d08..598cda3 100644 --- a/quickadapter/user_data/strategies/Utils.py +++ b/quickadapter/user_data/strategies/Utils.py @@ -2129,6 +2129,23 @@ _CATBOOST_GPU_PAIRWISE_LOSS_FUNCTIONS: Final[tuple[str, ...]] = ( "QueryCrossEntropy", ) +# CatBoost GPU param ranges keyed by VRAM capacity (GB). +# Formula: VRAM_MB = 58778 * 2^(depth-12) * (border_count+1) / 256 +_CATBOOST_GPU_VRAM_PARAM_RANGES: Final[dict[int, dict[str, tuple[int, int]]]] = { + 8: {"depth": (4, 9), "border_count": (32, 192), "max_ctr_complexity": (1, 4)}, + 10: {"depth": (4, 9), "border_count": (32, 255), "max_ctr_complexity": (1, 4)}, + 12: {"depth": (4, 10), "border_count": (32, 160), "max_ctr_complexity": (1, 4)}, + 16: {"depth": (4, 10), "border_count": (32, 224), "max_ctr_complexity": (1, 5)}, + 24: {"depth": (4, 10), "border_count": (32, 255), "max_ctr_complexity": (1, 5)}, + 32: {"depth": (4, 11), "border_count": (32, 192), "max_ctr_complexity": (1, 5)}, + 40: {"depth": (4, 11), "border_count": (32, 255), "max_ctr_complexity": (1, 6)}, + 48: {"depth": (4, 11), "border_count": (32, 255), "max_ctr_complexity": (1, 6)}, + 64: {"depth": (4, 12), "border_count": (32, 192), "max_ctr_complexity": (1, 6)}, + 80: {"depth": (4, 12), "border_count": (32, 255), "max_ctr_complexity": (1, 6)}, +} + +_CATBOOST_GPU_VRAM_DEFAULT: Final[int] = 80 + def get_ngboost_dist(dist_name: str) -> type: from ngboost.distns import Exponential, Laplace, LogNormal, Normal, T @@ -2397,8 +2414,9 @@ def fit_regressor( task_type = model_training_parameters.get("task_type", "CPU") loss_function = model_training_parameters.get("loss_function", "RMSE") if task_type == "GPU": - model_training_parameters.setdefault("max_ctr_complexity", 4) + model_training_parameters.pop("gpu_vram_gb", None) model_training_parameters.pop("n_jobs", None) + model_training_parameters.setdefault("max_ctr_complexity", 4) if loss_function not in _CATBOOST_GPU_RSM_LOSS_FUNCTIONS: model_training_parameters.pop("rsm", None) else: @@ -2962,26 +2980,30 @@ def get_optuna_study_model_parameters( task_type = model_training_parameters.get("task_type", "CPU") loss_function = model_training_parameters.get("loss_function", "RMSE") - if ( - task_type == "GPU" - and loss_function in _CATBOOST_GPU_PAIRWISE_LOSS_FUNCTIONS - ): - max_depth = 8 - elif task_type == "GPU": - max_depth = 12 - else: # CPU - max_depth = 10 - if task_type == "GPU": + gpu_vram_gb = model_training_parameters.get( + "gpu_vram_gb", _CATBOOST_GPU_VRAM_DEFAULT + ) + matched_vram_gb = max( + (v for v in _CATBOOST_GPU_VRAM_PARAM_RANGES if v <= gpu_vram_gb), + default=min(_CATBOOST_GPU_VRAM_PARAM_RANGES.keys()), + ) + param_ranges = _CATBOOST_GPU_VRAM_PARAM_RANGES[matched_vram_gb] + + if loss_function in _CATBOOST_GPU_PAIRWISE_LOSS_FUNCTIONS: + max_depth = min(8, param_ranges["depth"][1]) + else: + max_depth = param_ranges["depth"][1] + default_ranges: dict[str, tuple[float, float]] = { # Boosting/Training "iterations": (100, 2000), "learning_rate": (0.001, 0.3), # Tree structure - "depth": (4, max_depth), + "depth": (param_ranges["depth"][0], max_depth), "min_data_in_leaf": (1, 20), - "border_count": (128, 255), - "max_ctr_complexity": (2, 6), + "border_count": param_ranges["border_count"], + "max_ctr_complexity": param_ranges["max_ctr_complexity"], # Regularization "l2_leaf_reg": (1, 10), "model_size_reg": (0.0, 1.0), @@ -2999,7 +3021,7 @@ def get_optuna_study_model_parameters( "iterations": (100, 2000), "learning_rate": (0.001, 0.3), # Tree structure - "depth": (4, max_depth), + "depth": (4, 10), "min_data_in_leaf": (1, 20), # Regularization "l2_leaf_reg": (1, 10), -- 2.53.0