Strategy004
Tested 1 Jun 2023 – 30 May 2025
Rank 15 of 27 by return
Strategy004 turned $1,000 into $778 (−22.2%) over 729 days, while holding Bitcoin returned +288.1% and an equal mix of the ten coins +194.8%. Its worst drawdown was a severe 50.2%, deep enough that most people would have switched it off. It won 93% of its trades yet still lost money, because its few losing trades were much larger than its many small wins.
How Strategy004 trades
Strategy004 buys when a strong or established trend (ADX above 50, or slow ADX above 26) coincides with oversold CCI and both a fast and slow stochastic turning up from low levels. It exits when the trend weakens and the stochastic is overbought.
Mixes trend strength with oversold timing, which is a reasonable idea. The 10% stoploss and stepped 5%-to-1% ROI define the payoff.
Rules at a glance
- Indicators
- ADX, CCI, Fast and slow Stochastic, EMA 5
- Take-profit (minimal ROI)
- 5% from entry; 4% after 20m; 3% after 30m; 1% after 1h 0m
- Stoploss
- −10%
- Trailing stop
- Off
- Exit signal
- Used
Results by month
Profit or loss in USDT for trades closed in each month.
Results by coin
| Pair | Trades | Win rate | Profit (USDT) | Avg. hold |
|---|---|---|---|---|
| XRP/USDT | 141 | 95.7% | +83.97 | 11h 29m |
| BTC/USDT | 168 | 97.0% | +30.42 | 18h 45m |
| ADA/USDT | 164 | 92.7% | +15.33 | 7h 50m |
| SOL/USDT | 159 | 91.8% | −2.60 | 9h 4m |
| DOGE/USDT | 159 | 91.2% | −11.39 | 13h 46m |
| BNB/USDT | 159 | 94.3% | −20.22 | 14h 50m |
| AVAX/USDT | 163 | 91.4% | −26.11 | 9h 50m |
| DOT/USDT | 155 | 91.6% | −42.67 | 14h 50m |
| LINK/USDT | 143 | 90.9% | −49.23 | 10h 7m |
| ETH/USDT | 167 | 89.2% | −199.48 | 1d 2h |
Why trades closed
| Exit reason | Trades | Win rate | Profit (USDT) |
|---|---|---|---|
| Take-profit (ROI) | 625 | 100.0% | +1,634.65 |
| Exit signal | 836 | 100.0% | +526.07 |
| Stoploss | 117 | 0.0% | −2,382.69 |
The code
Strategy004 comes from the community freqtrade-strategies repository (GPL-3.0). These are the parts that decide when it trades; the full file is 158 lines.
Entry rules
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( ( (dataframe['adx'] > 50) | (dataframe['slowadx'] > 26) ) & (dataframe['cci'] < -100) & ( (dataframe['fastk-previous'] < 20) & (dataframe['fastd-previous'] < 20) ) & ( (dataframe['slowfastk-previous'] < 30) & (dataframe['slowfastd-previous'] < 30) ) & (dataframe['fastk-previous'] < dataframe['fastd-previous']) & (dataframe['fastk'] > dataframe['fastd']) & (dataframe['mean-volume'] > 0.75) & (dataframe['close'] > 0.00000100) ), 'enter_long'] = 1 return dataframe
Exit rules
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( (dataframe['slowadx'] < 25) & ((dataframe['fastk'] > 70) | (dataframe['fastd'] > 70)) & (dataframe['fastk-previous'] < dataframe['fastd-previous']) & (dataframe['close'] > dataframe['ema5']) ), 'exit_long'] = 1 return dataframe
Indicators
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # ADX dataframe['adx'] = ta.ADX(dataframe) dataframe['slowadx'] = ta.ADX(dataframe, 35) # Commodity Channel Index: values Oversold:<-100, Overbought:>100 dataframe['cci'] = ta.CCI(dataframe) # Stoch stoch = ta.STOCHF(dataframe, 5) dataframe['fastd'] = stoch['fastd'] dataframe['fastk'] = stoch['fastk'] dataframe['fastk-previous'] = dataframe.fastk.shift(1) dataframe['fastd-previous'] = dataframe.fastd.shift(1) # Slow Stoch slowstoch = ta.STOCHF(dataframe, 50) dataframe['slowfastd'] = slowstoch['fastd'] dataframe['slowfastk'] = slowstoch['fastk'] dataframe['slowfastk-previous'] = dataframe.slowfastk.shift(1) dataframe['slowfastd-previous'] = dataframe.slowfastd.shift(1) # EMA - Exponential Moving Average dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) # get the rolling volume mean for the last hour (12x5) # Note: dataframe['volume'].mean() uses the whole dataframe in # backtesting hence will have lookahead, but would be fine for dry/live use dataframe['mean-volume'] = dataframe['volume'].rolling(12).mean() return dataframe
Reproduce this test
With freqtrade installed (see our Docker guide), download the data and run the same backtest:
freqtrade download-data --exchange binance --pairs ADA/USDT AVAX/USDT BNB/USDT BTC/USDT DOGE/USDT DOT/USDT ETH/USDT LINK/USDT SOL/USDT XRP/USDT --timeframes 5m --timerange 20230501-20250530
freqtrade backtesting --strategy Strategy004 --timeframe 5m \
--timerange 20230601-20250530 --dry-run-wallet 1000 --stake-amount 200 --max-open-trades 5 --fee 0.001
Results can differ slightly with a different data source or freqtrade version. See how we test.