From: Jérôme Benoit Date: Mon, 25 May 2026 12:37:43 +0000 (+0200) Subject: chore(quickadapter): cleanup pre-existing docstring and logging issues X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=45dd23e3f4878191d57858dd05ac4fe5048d4e09;p=freqai-strategies.git chore(quickadapter): cleanup pre-existing docstring and logging issues - migrate_config: !r repr for config key paths in deprecation warnings - Sphinx :param/:return: -> plain prose (top/bottom_log_return, price_retracement_percent, _apply_pipelines, _make_timeseries_split_datasets) - NumPy section headers -> prose (reversal_confirmed) - Google Args/Returns headers -> prose (get_pnl_momentum, _t_statistic, _effective_df, _t_critical) - Delete boilerplate freqtrade interface override docstrings (leverage, plot_annotations, fit, get_trade_duration_candles, fit_regressor) - Document make_test_set_and_weights (None, None) contract on test_size <= 0 - Simplify train() docstring (renorm detail belongs to _apply_pipelines) --- diff --git a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py index 473a99b..97c4398 100644 --- a/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py +++ b/quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py @@ -1380,9 +1380,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel): - ``timeseries_split``: chronological final-fold split. Both paths compose per-row weights via ``_compose_per_row_weights`` before splitting and feed them to ``model.fit(sample_weight=...)`` - through ``_train_common``. Train and test weights are renormalized - to mean=1 after ``feature_pipeline.fit_transform`` to preserve the - invariant despite pipeline-level row drops. + through ``_train_common``. """ method = self.data_split_parameters.get( "method", QuickAdapterRegressorV3.DATA_SPLIT_METHOD_DEFAULT @@ -1631,17 +1629,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel): dk: FreqaiDataKitchen, pair: str, ) -> dict: - """ - Apply feature and label pipelines to train/test data. - - This helper reduces code duplication between train() methods that need - custom data splitting but share the same pipeline application logic. - - :param dd: data_dictionary with train/test features/labels/weights - :param dk: FreqaiDataKitchen instance - :param pair: Trading pair (for error messages) - :return: data_dictionary with transformed features/labels - """ + """Apply feature and label pipelines; renormalize weights post-transform.""" dk.feature_pipeline = self.define_data_pipeline(threads=dk.thread_count) dk.label_pipeline = self.define_label_pipeline(threads=dk.thread_count) @@ -1719,20 +1707,13 @@ class QuickAdapterRegressorV3(BaseRegressionModel): weights: NDArray[np.floating], dk: FreqaiDataKitchen, ) -> dict: - """ - Chronological train/test split using the final fold from sklearn's TimeSeriesSplit. - - n_splits controls train/test proportions (higher = larger train set). - gap excludes samples between train/test; when 0, auto-calculated from - label_period_candles. max_train_size enables sliding window mode. - - :param filtered_dataframe: Feature data to split - :param labels: Label data to split - :param weights: Pre-computed per-row sample weights aligned to - filtered_dataframe rows by position; sliced via - ``weights[train_idx]`` / ``weights[test_idx]``. - :param dk: FreqaiDataKitchen instance for data building - :return: data_dictionary with train/test features/labels/weights + """Chronological train/test split using sklearn's TimeSeriesSplit final fold. + + ``n_splits`` controls train/test proportions (higher = larger train). + ``gap`` excludes samples between train and test; when 0, auto-derived + from ``label_period_candles``. ``max_train_size`` enables sliding + window mode. ``weights`` is sliced positionally via ``train_idx`` / + ``test_idx``. """ feat_dict = self.ft_params if feat_dict.get("shuffle_after_split", False): @@ -1845,13 +1826,6 @@ class QuickAdapterRegressorV3(BaseRegressionModel): def fit( self, data_dictionary: dict[str, Any], dk: FreqaiDataKitchen, **kwargs ) -> Any: - """ - User sets up the training and test data to fit their desired model here - :param data_dictionary: the dictionary constructed by DataHandler to hold - all the training and test data/labels. - :param dk: the FreqaiDataKitchen object - """ - X = data_dictionary.get("train_features") y = data_dictionary.get("train_labels") train_weights = data_dictionary.get("train_weights") diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index 3d01231..b47844b 100644 --- a/quickadapter/user_data/strategies/QuickAdapterV3.py +++ b/quickadapter/user_data/strategies/QuickAdapterV3.py @@ -973,12 +973,6 @@ class QuickAdapterV3(IStrategy): return timeframe_to_prev_date(self.config.get("timeframe"), trade.open_date_utc) def get_trade_duration_candles(self, df: DataFrame, trade: Trade) -> Optional[int]: - """ - Get the number of candles since the trade entry. - :param df: DataFrame with the current data - :param trade: Trade object - :return: Number of candles since the trade entry - """ entry_date = self.get_trade_entry_date(trade) dates = df.get("date") if dates is None or dates.empty: @@ -1657,71 +1651,19 @@ class QuickAdapterV3(IStrategy): 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. - - Overview - -------- - 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_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 - and the function falls back to step 1 result only (permissive fallback). - - Parameters - ---------- - df : DataFrame - Must contain 'open', 'close' and the NATR label series used indirectly. - pair : str - Trading pair identifier. - side : {'long','short'} - Direction to confirm. - order : {'entry','exit'} - Context (affects log wording only). - rate : float - Candidate execution price; must break the current threshold. - lookback_period_candles : int - Number of historical confirmation steps requested; truncated to history. - 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 - ------- - bool - True iff the current threshold is broken AND (lookback chain succeeded OR - a permissive fallback occurred). False otherwise. - - Fallback Semantics - ------------------ - 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_fraction outside (0,1], invalid min/max ordering, failure to break current - threshold, or failed historical step comparison. - - Complexity - ---------- - O(lookback_period_candles) threshold computations. - - Logging - ------- - Logs rejection reasons (invalid decay_fraction, threshold not broken, failed step). - Fallback aborts are silent. - - Limitations - ----------- - No strict mode; partial data may still confirm. + """Confirm a directional reversal using a volatility-adaptive threshold. + + Computes a deviation-based threshold on the latest candle (-1); ``rate`` + must strictly break it (long: ``rate > threshold``; short: ``rate < + threshold``). When ``lookback_period_candles > 0``, requires that for + each ``k = 1..lookback_period_candles`` the close at ``-k`` strictly + broke the threshold recomputed at ``-(k+1)`` with the natr-multiplier + bounds geometrically decayed by ``decay_fraction ** k`` clamped to + ``[0, 1]``. Non-finite intermediate close or threshold aborts the chain + and falls back permissively to the current-candle result, which may + weaken strict multi-candle guarantees. Returns False on empty + dataframe, invalid side/order, non-finite rate, negative lookback, + ``decay_fraction`` outside ``(0, 1]``, or invalid min/max ordering. """ if df.empty: return False @@ -1844,17 +1786,12 @@ class QuickAdapterV3(IStrategy): float, float, ]: - """Compute velocity and acceleration from PnL history. - - Velocity is the first derivative of PnL, acceleration is the second. - - Args: - unrealized_pnl_history: PnL values sequence. - window_size: Recent window size (0 = no windowing). + """Compute velocity (first derivative) and acceleration (second) from PnL history. - Returns: - (velocity_values, velocity_mean, velocity_std, - acceleration_values, acceleration_mean, acceleration_std) + ``window_size > 0`` truncates to the most recent window before + differencing. Returns + ``(velocity_values, velocity_mean, velocity_std, acceleration_values, + acceleration_mean, acceleration_std)``. """ unrealized_pnl_history_array = np.asarray(unrealized_pnl_history, dtype=float) @@ -1883,17 +1820,10 @@ class QuickAdapterV3(IStrategy): @staticmethod @lru_cache(maxsize=128) def _t_statistic(mean: float, std: float, n: int) -> float: - """Compute t-statistic for H₀: μ = 0. + """Compute t-statistic for H0: mu = 0 as ``mean * sqrt(n) / std``. - Formula: t = mean * √n / std - - Args: - mean: Sample mean. - std: Sample standard deviation (ddof=1). - n: Sample size. - - Returns: - t-statistic, or NaN if n < 2 or std ≈ 0. + Returns NaN when ``n < 2``, ``std`` is approximately zero, or any + input is non-finite. """ if n < 2: return np.nan @@ -1917,15 +1847,12 @@ class QuickAdapterV3(IStrategy): @staticmethod @lru_cache(maxsize=128) def _effective_df(x: tuple[float, ...]) -> float: - """Compute effective degrees of freedom with Bartlett's autocorrelation correction. - - Formula: df_eff = (n - 1) * (1 - ρ₁) / (1 + ρ₁), where ρ₁ is lag-1 autocorrelation. + """Effective degrees of freedom with Bartlett's autocorrelation correction. - Args: - x: Observations tuple. - - Returns: - Effective df (≥ 1). Falls back to n - 1 if n < 4 or on error. + Computes ``df_eff = (n - 1) * (1 - rho1) / (1 + rho1)`` where ``rho1`` + is the lag-1 autocorrelation clamped to ``[-0.99, 0.99]``. Falls back + to ``n - 1`` when ``n < 4`` or pearsonr fails. Result is bounded + below by 1. """ n = len(x) if n < 4: @@ -1957,15 +1884,9 @@ class QuickAdapterV3(IStrategy): @staticmethod @lru_cache(maxsize=128) def _t_critical(q: float, df: float, default_t: float) -> float: - """Compute critical t-value from Student's t-distribution. - - Args: - q: Quantile in (0, 1), e.g. 0.75. - df: Degrees of freedom. - default_t: Fallback value on error. + """Critical t-value from Student's t-distribution at quantile ``q``. - Returns: - t.ppf(q, df), or default_t if invalid inputs. + Returns ``default_t`` on invalid inputs or scipy failure. """ if not (0.0 < q < 1.0): return default_t @@ -2243,20 +2164,6 @@ class QuickAdapterV3(IStrategy): side: str, **kwargs: Any, ) -> float: - """ - Customize leverage for each new trade. This method is only called in trading modes - which allow leverage (margin / futures). The strategy is expected to return a - leverage value between 1.0 and max_leverage. - - :param pair: Pair that's currently analyzed - :param current_time: datetime object, containing the current datetime - :param current_rate: Rate, calculated based on pricing settings in exit_pricing. - :param proposed_leverage: A leverage proposed by the bot. - :param max_leverage: Max leverage allowed on this pair - :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. - :param side: 'long' or 'short' - indicating the direction of the proposed trade - :return: A leverage amount, which will be between 1.0 and max_leverage. - """ return min(self.config.get("leverage", proposed_leverage), max_leverage) def plot_annotations( @@ -2267,16 +2174,6 @@ class QuickAdapterV3(IStrategy): dataframe: DataFrame, **kwargs: Any, ) -> list[AnnotationType]: - """ - Plot annotations. - - :param pair: Pair that's currently being plotted - :param start_date: Start date of the chart range - :param end_date: End date of the chart range - :param dataframe: DataFrame with analyzed data for this pair - :param **kwargs: Additional arguments - :return: List of annotations to display on the chart - """ annotations: list[AnnotationType] = [] open_trades = Trade.get_trades_proxy(pair=pair, is_open=True) diff --git a/quickadapter/user_data/strategies/Utils.py b/quickadapter/user_data/strategies/Utils.py index fdfb455..4befeb5 100644 --- a/quickadapter/user_data/strategies/Utils.py +++ b/quickadapter/user_data/strategies/Utils.py @@ -518,18 +518,18 @@ def migrate_config(config: dict[str, Any], logger: Logger) -> None: _set_path(config, new_path, old_value) _delete_path(config, old_path) if old_section == new_section: - logger.warning(f"{old_path} is deprecated, use {new_key} instead") + logger.warning(f"{old_path!r} is deprecated, use {new_key!r} instead") else: - logger.warning(f"{old_path} has moved to {new_path}") + logger.warning(f"{old_path!r} has moved to {new_path!r}") else: _delete_path(config, old_path) if old_section == new_section: logger.warning( - f"{new_section} has both {new_key} and deprecated {old_path.rsplit('.', 1)[-1]}, using {new_key}" + f"{new_section!r} has both {new_key!r} and deprecated {old_path.rsplit('.', 1)[-1]!r}, using {new_key!r}" ) else: logger.warning( - f"{new_section} has {new_key} and deprecated {old_path}, using {new_path}" + f"{new_section!r} has {new_key!r} and deprecated {old_path!r}, using {new_path!r}" ) @@ -1454,15 +1454,10 @@ def calculate_n_extrema(series: pd.Series) -> int: def top_log_return(dataframe: pd.DataFrame, period: int) -> pd.Series: - """ - Logarithmic return from rolling maximum: log(close / rolling_max). - - Measures distance below the highest close in previous `period` bars. - Returns ≤ 0 (e.g., -0.10 ≈ -9.5% below peak). Zero when at peak. + """Logarithmic return from rolling maximum: ``log(close / rolling_max)``. - :param dataframe: OHLCV DataFrame with 'close' column - :param period: Lookback window (>=1) - :return: Log return series (≤ 0) + Measures distance below the highest close in previous ``period`` bars. + Returns <= 0 (e.g. -0.10 ~ -9.5% below peak), zero when at peak. """ if period < 1: raise ValueError(f"Invalid period value {period!r}: must be >= 1") @@ -1475,15 +1470,10 @@ def top_log_return(dataframe: pd.DataFrame, period: int) -> pd.Series: def bottom_log_return(dataframe: pd.DataFrame, period: int) -> pd.Series: - """ - Logarithmic return from rolling minimum: log(close / rolling_min). + """Logarithmic return from rolling minimum: ``log(close / rolling_min)``. - Measures distance above the lowest close in previous `period` bars. - Returns ≥ 0 (e.g., +0.10 ≈ +10.5% above bottom). Zero when at bottom. - - :param dataframe: OHLCV DataFrame with 'close' column - :param period: Lookback window (>=1) - :return: Log return series (≥ 0) + Measures distance above the lowest close in previous ``period`` bars. + Returns >= 0 (e.g. +0.10 ~ +10.5% above bottom), zero when at bottom. """ if period < 1: raise ValueError(f"Invalid period value {period!r}: must be >= 1") @@ -1496,17 +1486,11 @@ def bottom_log_return(dataframe: pd.DataFrame, period: int) -> pd.Series: def price_retracement_percent(dataframe: pd.DataFrame, period: int) -> pd.Series: - """ - Normalized position (0-1) of close within rolling high/low range, using log scale. + """Normalized log-scale position of close within rolling high/low range. - Formula: log(close / low) / log(high / low) - - Returns 0 at bottom, 1 at top, 0.5 at geometric midpoint (not arithmetic). - Example: range [100, 200] → midpoint at ~141, not 150. - - :param dataframe: OHLCV DataFrame with 'close' column - :param period: Lookback window (>=1) - :return: Normalized position (0 to 1) + Formula: ``log(close / low) / log(high / low)``. Returns 0 at bottom, 1 + at top, 0.5 at geometric (not arithmetic) midpoint; e.g. range [100, + 200] has midpoint at ~141. """ if period < 1: raise ValueError(f"Invalid period value {period!r}: must be >= 1") @@ -2369,7 +2353,6 @@ def fit_regressor( model_path: Optional[Path] = None, trial: Optional[optuna.trial.Trial] = None, ) -> Any: - """Fit a regressor model.""" fit_callbacks = list(callbacks) if callbacks else [] has_eval_set = ( @@ -2677,6 +2660,10 @@ def make_test_set_and_weights( Optional[list[tuple[pd.DataFrame, pd.DataFrame]]], Optional[list[NDArray[np.floating]]], ]: + """Wrap test data for ``model.fit`` ``eval_set`` when ``test_size > 0``. + + Returns ``(None, None)`` when ``test_size <= 0`` to suppress evaluation. + """ if test_size <= 0: return None, None