TrendRider
♡
Basics
mode: spot
4h
Settings
custom stoploss
protections
hyperopt
hyperopt params: 5
Indicators
ADX
ATR
EMA
Concepts
breakout
risk_management
trend_following
Other
shared
15 related strategies (⧉ identical code, ≈ similar name)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | """TrendRider — trend-following breakout strategy (for the `trend` regime). Entry (long only, S1): - 1h close breaks above the Donchian(20) high of the previous 20 candles - 4h EMA50 > 4h EMA200 (higher-timeframe uptrend filter) - 1h ADX(14) > 20 (trend strength filter) Risk / exit: - initial stop: 2 x ATR(14) below entry (hard outer stop -10%, S5) - after price moves +1R in our favour: trail at 1.5 x ATR below price - exit signal: 1h close below EMA20 Position sizing and the entry gate live in SantinelaBase (riskguard). Every hyperopt-tunable parameter carries an explicit bounded space. """ from __future__ import annotations from datetime import datetime from pandas import DataFrame from freqtrade.strategy import DecimalParameter, IntParameter, informative from freqtrade.strategy import stoploss_from_absolute from shared import indicators as ind from shared.santinela_base import SantinelaBase class TrendRider(SantinelaBase): # --- hyperopt spaces (bounded by design; see §6.2) --- donchian_len = IntParameter(15, 40, default=20, space="buy", optimize=True) adx_min = IntParameter(15, 35, default=20, space="buy", optimize=True) stop_atr = DecimalParameter(1.5, 3.0, default=2.0, decimals=1, space="sell", optimize=True) trail_atr = DecimalParameter(1.0, 2.5, default=1.5, decimals=1, space="sell", optimize=True) ema_exit_len = IntParameter(10, 30, default=20, space="sell", optimize=True) @property def stop_atr_mult(self) -> float: # used by SantinelaBase sizing return float(self.stop_atr.value) # Defense-in-depth mirrors of S6-S8 at the freqtrade level. # RiskGuard remains the authority; these just add a second net. @property def protections(self): return [ {"method": "CooldownPeriod", "stop_duration_candles": 2}, { "method": "StoplossGuard", # ~S8 mirror "lookback_period_candles": 48, "trade_limit": 6, "stop_duration_candles": 12, "only_per_pair": False, }, { "method": "MaxDrawdown", # ~S6/S7 mirror "lookback_period_candles": 24, "trade_limit": 4, "max_allowed_drawdown": 0.05, "stop_duration_candles": 24, }, ] @informative("4h") def populate_indicators_4h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe["ema50"] = ind.ema(dataframe, 50) dataframe["ema200"] = ind.ema(dataframe, 200) return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe["atr"] = ind.atr(dataframe, 14) dataframe["adx"] = ind.adx(dataframe, 14) dataframe["donchian_high"] = ind.donchian_high( dataframe, self.donchian_len.value ) dataframe["ema_exit"] = ind.ema(dataframe, self.ema_exit_len.value) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ (dataframe["close"] > dataframe["donchian_high"]) & (dataframe["ema50_4h"] > dataframe["ema200_4h"]) & (dataframe["adx"] > self.adx_min.value) & (dataframe["volume"] > 0), ["enter_long", "enter_tag"], ] = (1, "donchian_breakout") return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ (dataframe["close"] < dataframe["ema_exit"]) & (dataframe["volume"] > 0), ["exit_long", "exit_tag"], ] = (1, "close_below_ema") return dataframe def custom_stoploss( self, pair: str, trade, current_time: datetime, current_rate: float, current_profit: float, after_fill: bool, **kwargs, ) -> float | None: entry_atr = self._atr_at(pair, trade.open_date_utc) if entry_atr <= 0: return None # keep hard outer stop (S5) initial_risk = self.stop_atr.value * entry_atr / trade.open_rate if current_profit >= initial_risk: # +1R reached -> ATR trail current_atr = self._atr_at(pair, current_time) or entry_atr stop_price = current_rate - self.trail_atr.value * current_atr else: stop_price = trade.open_rate - self.stop_atr.value * entry_atr # freqtrade only ever tightens the stop; returning a looser value # than the current stop is ignored (S5: tighten, never remove). return stoploss_from_absolute( stop_price, current_rate, is_short=trade.is_short ) |
Strategy League — fixed backtest that feeds the ranking
Failed — strategy imports unavailable module: shared
figuration.configuration - INFO - Using user-data directory: /freqle/user_data ... 2026-07-27 15:41:05,016 - freqtrade.configuration.configuration - INFO - Using data directory: /freqle/user_data/data/binance ... 2026-07-27 15:41:05,016 - freqtrade.configuration.configuration - INFO - Parameter --export detected: none ... 2026-07-27 15:41:05,017 - freqtrade.configuration.configuration - INFO - Parameter --cache=none detected ... 2026-07-27 15:41:05,017 - freqtrade.configuration.configuration - INFO - Filter trades by timerange: 20210101-20260101 2026-07-27 15:41:05,018 - freqtrade.exchange.check_exchange - INFO - Checking exchange... 2026-07-27 15:41:05,025 - freqtrade.exchange.check_exchange - INFO - Exchange "binance" is officially supported by the Freqtrade development team. 2026-07-27 15:41:05,025 - freqtrade.configuration.configuration - INFO - Using pairlist from configuration. 2026-07-27 15:41:05,025 - freqtrade.configuration.config_validation - INFO - Validating configuration ... 2026-07-27 15:41:05,027 - freqtrade.exchange.exchange - INFO - Instance is running with dry_run enabled 2026-07-27 15:41:05,027 - freqtrade.exchange.exchange - INFO - Using CCXT 4.5.61 2026-07-27 15:41:05,039 - freqtrade.exchange.exchange - INFO - Using Exchange "Binance" 2026-07-27 15:41:05,223 - freqtrade.resolvers.exchange_resolver - INFO - Using resolved exchange 'Binance'... 2026-07-27 15:41:05,224 - freqtrade.resolvers.iresolver - WARNING - Could not import /freqle/user_data/strategies/TrendRider.py due to 'No module named 'shared'' 2026-07-27 15:41:05,226 - freqtrade.resolvers.iresolver - WARNING - Could not import /freqle/user_data/strategies/TrendRider.py due to 'No module named 'shared'' 2026-07-27 15:41:05,227 - freqtrade.resolvers.iresolver - WARNING - Could not import /freqle/user_data/strategies/TrendRider.py due to 'No module named 'shared'' ft_backtest wrapper failed: Impossible to load Strategy 'TrendRider'. This class does not exist or contains Python code errors.
Backtests — over a market period
Backtest this strategy over a chosen crypto-cycle period. These don't affect the League ranking, and need that period's candle data downloaded.
Log in or sign up to run backtests.
| Period | Range | Total % | Win % | Max DD | Trades | |
|---|---|---|---|---|---|---|
| 2020 · DeFi Summer & Pre-Halving Rally | 20200101-20210101 | not run | ||||
| 2021 · Institutional Bull Market | 20210101-20220101 | not run | ||||
| 2022 · Post-Bull Crash & Macro Tightening | 20220101-20230101 | not run | ||||
| 2023–2024 · Recovery & ETF Anticipation | 20230101-20250101 | not run | ||||
| 2025–2026 · Current Cycle | 20250101-20260101 | not run | ||||
Walk forward
Out-of-sample backtest on recent data · 33 pairs · 20260101-20260701.
Backtest trust check
no lookahead-bias patterns detected
ran by Ron · took s
Lookahead analysis
freqtrade lookahead-analysis: detects strategies peeking at future candles.