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) <noreply@anthropic.com>
This commit is contained in:
darkvolg
2026-04-16 09:22:11 +03:00
co-authored by Claude Opus 4.6
parent 24179a3e9a
commit 405be8e0e8
2 changed files with 11 additions and 122 deletions
+1 -1
View File
@@ -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.
+10 -121
View File
@@ -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