Freqtrade Strategy

Strategy003

5m candlesMean reversionSpot, long only

Tested 1 Jun 2023 – 30 May 2025
Rank 18 of 27 by return

Strategy003 turned $1,000 into $610 (−39.0%) 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 51.6%, deep enough that most people would have switched it off. It won 92% of its trades yet still lost money, because its few losing trades were much larger than its many small wins.

Strategy003 account balance compared with holding Bitcoin$0$1,000$2,000$3,000$4,000JulOctJan 2024AprJulOctJan 2025AprStrategy003 $610BTC hold $3,880
Account balance from $1,000 (closed trades, daily). Dashed line: $1,000 held in BTC over the same days.
Total return−39.0%$610 final balance
Max drawdown51.6%12 Mar 2024 to 29 Mar 2025
Win rate92.0%965 wins / 84 losses
Profit factor0.77Gross wins ÷ gross losses
Trades1,0491.4 per day
Average trade−0.19%after fees
Average hold19h 25mwinners 15h 19m, losers 2d 18h
Annualised (CAGR)−21.9%Sharpe -1.72 · Sortino -5.80

How Strategy003 trades

Strategy003 stacks several oversold filters: RSI under 28, Fisher RSI under -0.94, MFI under 16 and price below its SMA, while the larger trend is up (EMA 50 above EMA 100) or a short EMA cross has just happened. It shares Strategy002's SAR exit.

Very strict entries again mean a small sample of trades. Check the per-pair table before drawing conclusions.

Rules at a glance

Indicators
RSI, Fisher RSI, MFI, Fast Stochastic, EMA 5 / 10 / 50 / 100
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.

JFMAMJJASOND2023+32+48+6-10+89+25-22024-56+48-43-51+18-106-63-89-19-100-3+402025-65-24-89+36-14

Results by coin

PairTradesWin rateProfit (USDT)Avg. hold
ETH/USDT13096.2%+29.3020h 54m
BTC/USDT9594.7%+17.061d 2h
SOL/USDT8793.1%+8.8612h 28m
BNB/USDT12195.0%−13.2122h 47m
AVAX/USDT12291.8%−23.4114h 9m
XRP/USDT10493.3%−26.8018h 32m
DOGE/USDT9190.1%−52.7417h 35m
ADA/USDT10989.9%−75.2216h 54m
LINK/USDT8888.6%−85.0417h 56m
DOT/USDT10285.3%−169.101d 1h

Why trades closed

Exit reasonTradesWin rateProfit (USDT)
Take-profit (ROI)465100.0%+1,047.10
Exit signal500100.0%+257.05
Still open at end of test10.0%−4.12
Stoploss830.0%−1,690.33

The code

Strategy003 comes from the community freqtrade-strategies repository (GPL-3.0). These are the parts that decide when it trades; the full file is 153 lines.

Entry rules
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    dataframe.loc[
        (
            (dataframe['rsi'] < 28) &
            (dataframe['rsi'] > 0) &
            (dataframe['close'] < dataframe['sma']) &
            (dataframe['fisher_rsi'] < -0.94) &
            (dataframe['mfi'] < 16.0) &
            (
                (dataframe['ema50'] > dataframe['ema100']) |
                (qtpylib.crossed_above(dataframe['ema5'], dataframe['ema10']))
            ) &
            (dataframe['fastd'] > dataframe['fastk']) &
            (dataframe['fastd'] > 0)
        ),
        'enter_long'] = 1

    return dataframe
Exit rules
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    dataframe.loc[
        (
            (dataframe['sar'] > dataframe['close']) &
            (dataframe['fisher_rsi'] > 0.3)
        ),
        'exit_long'] = 1
    return dataframe
Indicators
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

    # MFI
    dataframe['mfi'] = ta.MFI(dataframe)

    # Stoch fast
    stoch_fast = ta.STOCHF(dataframe)
    dataframe['fastd'] = stoch_fast['fastd']
    dataframe['fastk'] = stoch_fast['fastk']

    # RSI
    dataframe['rsi'] = ta.RSI(dataframe)

    # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
    rsi = 0.1 * (dataframe['rsi'] - 50)
    dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)

    # Bollinger bands
    bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
    dataframe['bb_lowerband'] = bollinger['lower']

    # EMA - Exponential Moving Average
    dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
    dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
    dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
    dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)

    # SAR Parabol
    dataframe['sar'] = ta.SAR(dataframe)

    # SMA - Simple Moving Average
    dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)

    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 Strategy003 --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.