From 405be8e0e87849eabb8a9992d4aff71916a0d27c Mon Sep 17 00:00:00 2001 From: darkvolg Date: Thu, 16 Apr 2026 09:22:11 +0300 Subject: [PATCH] fix: address review feedback from xmatthias - Remove "Public version" block from docstring (no need to imply private version) - Remove inline Telegram messages from confirm_trade_entry/exit (duplicates freqtrade notifications) - Clean up stoploss/trailing comments (remove "WIDE" wording) - Remove "V4 cascading exit" from README table (internal versioning, not user-facing) - Keep confidence-based entry filter in confirm_trade_entry Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 2 +- user_data/strategies/TrendRiderStrategy.py | 131 ++------------------- 2 files changed, 11 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 1da7335..6302c58 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Value below are result from backtesting from 2018-01-10 to 2018-01-30 and | [Strategy 003](https://github.com/freqtrade/freqtrade-strategies/blob/main/user_data/strategies/Strategy003.py) | 14 | 1.47 | 0.00081740 | 227.5 | 2018-01-10 to 2018-01-30 | | [Strategy 004](https://github.com/freqtrade/freqtrade-strategies/blob/main/user_data/strategies/Strategy004.py) | 37 | 0.69 | 0.00102128 | 367.3 | 2018-01-10 to 2018-01-30 | | [Strategy 005](https://github.com/freqtrade/freqtrade-strategies/blob/main/user_data/strategies/Strategy005.py) | 180 | 1.16 | 0.00827589 | 156.2 | 2018-01-10 to 2018-01-30 | -| [TrendRiderStrategy](https://github.com/freqtrade/freqtrade-strategies/blob/main/user_data/strategies/TrendRiderStrategy.py) | 94 | 0.26 | +3.04% | 11h 48m | 2026-03-15 to 2026-04-14 (Bybit 1h, 15 USDT perps, V4 cascading exit) | +| [TrendRiderStrategy](https://github.com/freqtrade/freqtrade-strategies/blob/main/user_data/strategies/TrendRiderStrategy.py) | 94 | 0.26 | +3.04% | 11h 48m | 2026-03-15 to 2026-04-14 (Bybit 1h, 15 USDT perps) | Strategies from this repo are free to use. Feel free to update them to your likings. Most of them were designed from Hyperopt calculations. diff --git a/user_data/strategies/TrendRiderStrategy.py b/user_data/strategies/TrendRiderStrategy.py index 66cfc02..159842a 100644 --- a/user_data/strategies/TrendRiderStrategy.py +++ b/user_data/strategies/TrendRiderStrategy.py @@ -1,15 +1,12 @@ """ -TrendRider Public v2.11.0 — Strat Ninja Edition +TrendRider Strategy -Philosophy: Ride established trends with WIDE stoploss. -Key insight: crypto swings 2-4% per hour. Stoploss must be >= 5-6%. +Ride established trends with ATR-aware stoploss. +Key insight: crypto swings 2-4% per hour, stoploss must accommodate this volatility. -Public version: -- No external API calls (FNG, Bybit funding/OI) -- No SQLite price alerts -- No Cornix formatting - Leverage 1x (spot-safe) -- All TA-Lib indicators and confidence scoring preserved +- TA-Lib indicators with confidence scoring +- Multiple entry signals: pullback, EMA bounce, RSI bounce, crossover, BB bounce, MACD reversal """ import talib.abstract as ta @@ -33,11 +30,11 @@ class TrendRiderStrategy(IStrategy): "764": 0, # breakeven after ~12.7h } - # --- Stoploss: WIDE for crypto volatility --- + # --- Stoploss --- stoploss = -0.06 # 6% default (ATR-based custom stoploss overrides) use_custom_stoploss = False - # --- Trailing Stop: WIDE --- + # --- Trailing Stop --- trailing_stop = True trailing_stop_positive = 0.03 # 3% trail trailing_stop_positive_offset = 0.05 # Activate after +5% @@ -619,127 +616,19 @@ class TrendRiderStrategy(IStrategy): def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, current_time: datetime, entry_tag: str | None, side: str, **kwargs) -> bool: - # Calculate levels (LONG only, can_short = False) - sl_price = rate * (1 + self.stoploss) - tp2_price = rate * 1.05 # +5% - - leverage = self.leverage_value - side_str = "LONG" - - # Risk/reward ratio - risk = abs(rate - sl_price) - reward = abs(tp2_price - rate) - rr_ratio = reward / risk if risk > 0 else 0 - - # Entry reason mapping - reasons = { - "trend_pullback": "Pullback to EMA in uptrend, bounce with volume confirmation", - "ema50_bounce": "Deep pullback to EMA50, bounce with rising MACD", - "rsi_bounce": "RSI oversold, bounce from lower Bollinger in bull market", - "ema_crossover": "EMA9 crossed above EMA16, golden cross with trend confirmation", - "bb_bounce": "Price bounced from lower Bollinger Band with oversold RSI", - "macd_reversal": "MACD histogram turned positive, momentum shift above EMA50", - } - reason = reasons.get(entry_tag, entry_tag or "Signal") - - # Get current indicators for context + # Get current indicators for confidence filter dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) if len(dataframe) > 0: last = dataframe.iloc[-1] - rsi_key = f"rsi_{self.rsi_period.value}" - rsi_val = last.get(rsi_key, 0) - adx_val = last.get("adx", 0) - vol_ratio = last.get("volume_ratio", 0) - macd_hist = last.get("macdhist", 0) else: - rsi_val = adx_val = vol_ratio = macd_hist = 0 last = {} - # Confidence & market context - conf_level, conf_bar, conf_details, conf_numeric = self._calc_confidence(last) - market_ctx = self._market_context(last) + # Confidence & regime filter — reject weak signals + _, _, _, conf_numeric = self._calc_confidence(last) regime = self._get_market_regime(last) - - # --- REJECT WEAK SIGNALS --- min_conf = 6 if "Bear" in regime else 5 if conf_numeric < min_conf: logger.info(f"Rejecting signal for {pair}: confidence {conf_numeric}/10 < {min_conf} (regime: {regime})") return False - # --- Main Telegram Signal --- - msg = ( - f"*TRENDRIDER SIGNAL*\n" - f"{'='*28}\n" - f"*{pair}* | *{side_str}* | {leverage}x\n" - f"{'='*28}\n\n" - f"*Entry:* `{rate:.2f}` USDT\n" - f"*Stop Loss:* `{sl_price:.2f}` ({self.stoploss*100:+.1f}%)\n" - f" R:R = 1:{rr_ratio:.1f}\n\n" - f"*Confidence:* {conf_level}\n" - f" [{conf_bar}]\n" - f" {', '.join(conf_details)}\n\n" - f"*Regime:* {regime}\n" - f"*Indicators:*\n" - f" RSI: {rsi_val:.1f} | ADX: {adx_val:.1f}\n" - f" Volume: {vol_ratio:.2f}x | MACD: {'+' if macd_hist > 0 else '-'}\n\n" - f"*Market:* {market_ctx}\n\n" - f"*Why:* {reason}\n" - f"{'='*28}\n" - f"_TrendRider AI_" - ) - self.dp.send_msg(msg, always_send=True) - - return True - - def confirm_trade_exit(self, pair: str, trade, order_type: str, amount: float, - rate: float, time_in_force: str, exit_reason: str, - current_time: datetime, **kwargs) -> bool: - # Calculate results (LONG only) - profit_pct = ((rate - trade.open_rate) / trade.open_rate) * 100 * trade.leverage - duration_hours = (current_time - trade.open_date_utc).total_seconds() / 3600 - - # Exit reason mapping - exit_reasons = { - "roi": "ROI target reached", - "stop_loss": "Stop Loss hit", - "trailing_stop_loss": "Trailing Stop", - "exit_signal": "Exit signal", - "rsi_overbought": "RSI overbought (>81)", - "ema_bearish_cross": "EMA bearish crossover", - "trend_broken": "Trend broken (below EMA200)", - "force_exit": "Force exit", - "time_exit_24h": "Time exit (24h, low profit)", - } - reason_text = exit_reasons.get(exit_reason, exit_reason) - - # Result line - if profit_pct > 0: - result_line = f"+{profit_pct:.2f}%" - else: - result_line = f"{profit_pct:.2f}%" - - # Duration formatting - if duration_hours < 1: - dur_str = f"{int(duration_hours * 60)}m" - elif duration_hours < 24: - dur_str = f"{duration_hours:.1f}h" - else: - dur_str = f"{duration_hours/24:.1f}d" - - msg = ( - f"*TRADE CLOSED* {'WIN' if profit_pct > 0 else 'LOSS'}\n" - f"{'='*25}\n" - f"*{pair}* | LONG | {trade.leverage}x\n" - f"{'='*25}\n\n" - f"*Entry:* `{trade.open_rate:.2f}`\n" - f"*Exit:* `{rate:.2f}`\n" - f"*Result:* *{result_line}*\n" - f"*Duration:* {dur_str}\n" - f"*Reason:* {reason_text}\n" - f"*Max price:* `{trade.max_rate:.2f}`\n" - f"{'='*25}\n" - f"_TrendRider AI_" - ) - - self.dp.send_msg(msg, always_send=True) return True