From 6a4f90cb05e5453509b97be77e7cce690b81e917 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sat, 27 Sep 2025 17:29:28 +0200 Subject: [PATCH] fix(reforcexy): ensure padding frame is init with zeros MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- ReforceXY/user_data/freqaimodels/ReforceXY.py | 14 ++++++---- .../user_data/strategies/QuickAdapterV3.py | 27 +++++++++++-------- scripts/docker-upgrade.sh | 11 +++++--- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/ReforceXY/user_data/freqaimodels/ReforceXY.py b/ReforceXY/user_data/freqaimodels/ReforceXY.py index b35a8ab..3fc9bf8 100644 --- a/ReforceXY/user_data/freqaimodels/ReforceXY.py +++ b/ReforceXY/user_data/freqaimodels/ReforceXY.py @@ -160,7 +160,7 @@ class ReforceXY(BaseReinforcementLearningModel): self.unset_unsupported() @staticmethod - def is_short_allowed(trading_mode: str) -> bool: + def _is_short_allowed(trading_mode: str) -> bool: if trading_mode in {"margin", "futures"}: return True elif trading_mode == "spot": @@ -188,7 +188,7 @@ class ReforceXY(BaseReinforcementLearningModel): position: Positions, force_action: Optional[ForceActions] = None, ) -> NDArray[np.bool_]: - is_short_allowed = ReforceXY.is_short_allowed(trading_mode) + is_short_allowed = ReforceXY._is_short_allowed(trading_mode) position = ReforceXY._normalize_position(position) action_masks = np.zeros(len(Actions), dtype=np.bool_) @@ -684,14 +684,18 @@ class ReforceXY(BaseReinforcementLearningModel): del fb[0 : len(fb) - frame_stacking] if len(fb) < frame_stacking: pad_count = frame_stacking - len(fb) - pad_frame = fb[0] if fb else np_observation + pad_frame = np.zeros_like(np_observation, dtype=np.float32) fb_padded = [pad_frame] * pad_count + fb else: fb_padded = fb stacked_observations = np.concatenate(fb_padded, axis=1) - observations = stacked_observations.reshape(1, -1) + observations = stacked_observations.reshape( + 1, stacked_observations.shape[0], stacked_observations.shape[1] + ) else: - observations = np_observation.reshape(1, -1) + observations = np_observation.reshape( + 1, np_observation.shape[0], np_observation.shape[1] + ) if self.action_masking and self.inference_masking: action_masks_param["action_masks"] = ReforceXY.get_action_masks( diff --git a/quickadapter/user_data/strategies/QuickAdapterV3.py b/quickadapter/user_data/strategies/QuickAdapterV3.py index e48c2a3..6c43055 100644 --- a/quickadapter/user_data/strategies/QuickAdapterV3.py +++ b/quickadapter/user_data/strategies/QuickAdapterV3.py @@ -224,18 +224,15 @@ class QuickAdapterV3(IStrategy): ), } ) + self._candle_duration_secs = int( + timeframe_to_minutes(self.config.get("timeframe")) * 60 + ) + self.last_candle_start_secs: dict[str, Optional[int]] = { + pair: None for pair in self.pairs + } process_throttle_secs = self.config.get("internals", {}).get( "process_throttle_secs", 5 ) - self._throttle_modulo = max( - 1, - int( - round( - (timeframe_to_minutes(self.config.get("timeframe")) * 60) - / process_throttle_secs - ) - ), - ) self._max_history_size = int(12 * 60 * 60 / process_throttle_secs) self._pnl_momentum_window_size = int(30 * 60 / process_throttle_secs) self._exit_thresholds_calibration: dict[str, float] = { @@ -791,7 +788,10 @@ class QuickAdapterV3(IStrategy): current_time: datetime.datetime, callback: Callable[[], None], ) -> None: - if hash(pair + str(current_time)) % self._throttle_modulo == 0: + timestamp = int(current_time.timestamp()) + candle_start_secs = timestamp - (timestamp % self._candle_duration_secs) + if candle_start_secs != self.last_candle_start_secs.get(pair): + self.last_candle_start_secs[pair] = candle_start_secs try: callback() except Exception as e: @@ -1119,7 +1119,7 @@ class QuickAdapterV3(IStrategy): side: str, order: Literal["entry", "exit"], rate: float, - min_natr_ratio_percent: float = 0.005, + min_natr_ratio_percent: float = 0.0075, max_natr_ratio_percent: float = 0.025, lookback_period: int = 1, decay_ratio: float = 0.5, @@ -1551,6 +1551,11 @@ class QuickAdapterV3(IStrategy): side: str, **kwargs, ) -> bool: + if side not in {"long", "short"}: + return False + if side == "short" and not self.can_short: + logger.info(f"User denied short entry for {pair}: shorting not allowed") + return False if Trade.get_open_trade_count() >= self.config.get("max_open_trades"): return False max_open_trades_per_side = self.max_open_trades_per_side diff --git a/scripts/docker-upgrade.sh b/scripts/docker-upgrade.sh index d82b147..ce7b2e1 100755 --- a/scripts/docker-upgrade.sh +++ b/scripts/docker-upgrade.sh @@ -2,9 +2,9 @@ set -eu SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" -FREQTRADE_CONFIG="${SCRIPT_DIR}/user_data/config.json" -LOCAL_DOCKER_IMAGE="reforcexy-freqtrade" -REMOTE_DOCKER_IMAGE="freqtradeorg/freqtrade:stable_freqairl" +FREQTRADE_CONFIG="${FREQTRADE_CONFIG:-$SCRIPT_DIR}/user_data/config.json}" +LOCAL_DOCKER_IMAGE="${LOCAL_DOCKER_IMAGE:-reforcexy-freqtrade}" +REMOTE_DOCKER_IMAGE="${REMOTE_DOCKER_IMAGE:-freqtradeorg/freqtrade:stable_freqairl}" ################################ @@ -208,7 +208,7 @@ if [ ! -f "${SCRIPT_DIR}/docker-compose.yml" ] && [ ! -f "${SCRIPT_DIR}/docker-c fi if [ ! -f "$FREQTRADE_CONFIG" ]; then - echo_timestamped "Error: ${FREQTRADE_CONFIG} file not found from current directory" + echo_timestamped "Error: ${FREQTRADE_CONFIG} file not found" exit 1 fi @@ -237,6 +237,9 @@ if [ "$rebuild_local_image" = true ]; then echo_timestamped "Info: $message" send_telegram_message "$message" cd -- "$SCRIPT_DIR" || exit 1 + if ! command docker compose pull --quiet >/dev/null 2>&1; then + echo_timestamped "Warning: docker compose pull failed" + fi if ! command docker compose --progress quiet down; then echo_timestamped "Error: docker compose down failed" exit 1 -- 2.43.0