Freqtrade Strategy

EMASkipPump

5m candlesDip buyingSpot, long only

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

EMASkipPump turned $1,000 into $199 (−80.1%) 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 80.2%, deep enough that most people would have switched it off. With a 48% win rate and a profit factor of 0.42, losses outweighed gains. Once the balance fell below the $200 stake, it could no longer open trades and stopped.

EMASkipPump account balance compared with holding Bitcoin$0$1,000$2,000$3,000$4,000JulOctJan 2024AprJulOctJan 2025AprEMASkipPump $199BTC hold $3,880
Account balance from $1,000 (closed trades, daily). Dashed line: $1,000 held in BTC over the same days.
Total return−80.1%$199 final balance
Max drawdown80.2%1 Jun 2023 to 27 Oct 2023
Win rate47.5%742 wins / 820 losses
Profit factor0.42Gross wins ÷ gross losses
Trades1,5622.1 per day
Average trade−0.26%after fees
Average hold3h 3mwinners 1h 54m, losers 4h 5m
Annualised (CAGR)−55.4%Sharpe -8.75 · Sortino -8.01

How EMASkipPump trades

EMASkipPump buys when price makes a 12-candle low below both short EMAs and the lower Bollinger Band, but skips candles with a volume spike (a likely pump or dump). It sells on the opposite setup: a 12-candle high above the upper band.

The volume filter is the idea in the name. A 5% stoploss and a 10% take-profit frame each trade.

Rules at a glance

Indicators
EMA 5 / 12 / 21, Bollinger Bands, Rolling min / max
Take-profit (minimal ROI)
10% from entry
Stoploss
−5%
Trailing stop
Off
Exit signal
Used

Results by month

Profit or loss in USDT for trades closed in each month.

JFMAMJJASOND2023-340-137-205-57-62

Results by coin

PairTradesWin rateProfit (USDT)Avg. hold
DOGE/USDT13359.4%−44.852h 51m
BTC/USDT18740.6%−50.003h 11m
ETH/USDT15745.9%−52.873h 4m
DOT/USDT10448.1%−56.933h 1m
AVAX/USDT11645.7%−77.173h 1m
XRP/USDT21049.5%−95.153h 2m
BNB/USDT18138.1%−99.193h 3m
LINK/USDT14049.3%−102.043h 9m
ADA/USDT15245.4%−110.372h 55m
SOL/USDT18255.5%−112.363h 4m

Why trades closed

Exit reasonTradesWin rateProfit (USDT)
Exit signal150649.3%−221.03
Stoploss560.0%−579.90

The code

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

Entry rules
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

    dataframe.loc[
        (dataframe['volume'] < (dataframe['volume'].rolling(window=30).mean().shift(1) * 20)) &
        (dataframe['close'] < dataframe['ema_{}'.format(self.EMA_SHORT_TERM)]) &
        (dataframe['close'] < dataframe['ema_{}'.format(self.EMA_MEDIUM_TERM)]) &
        (dataframe['close'] == dataframe['min']) &
        (dataframe['close'] <= dataframe['bb_lowerband']),
        'enter_long'
    ] = 1

    return dataframe
Exit rules
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

    dataframe.loc[
        (dataframe['close'] > dataframe['ema_{}'.format(self.EMA_SHORT_TERM)]) &
        (dataframe['close'] > dataframe['ema_{}'.format(self.EMA_MEDIUM_TERM)]) &
        (dataframe['close'] >= dataframe['max']) &
        (dataframe['close'] >= dataframe['bb_upperband']),
        'exit_long'
    ] = 1

    return dataframe
Indicators
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

    dataframe['ema_{}'.format(self.EMA_SHORT_TERM)] = ta.EMA(
        dataframe, timeperiod=self.EMA_SHORT_TERM
    )
    dataframe['ema_{}'.format(self.EMA_MEDIUM_TERM)] = ta.EMA(
        dataframe, timeperiod=self.EMA_MEDIUM_TERM
    )
    dataframe['ema_{}'.format(self.EMA_LONG_TERM)] = ta.EMA(
        dataframe, timeperiod=self.EMA_LONG_TERM
    )

    bollinger = qtpylib.bollinger_bands(
        qtpylib.typical_price(dataframe), window=20, stds=2
    )
    dataframe['bb_lowerband'] = bollinger['lower']
    dataframe['bb_middleband'] = bollinger['mid']
    dataframe['bb_upperband'] = bollinger['upper']

    dataframe['min'] = ta.MIN(dataframe, timeperiod=self.EMA_MEDIUM_TERM)
    dataframe['max'] = ta.MAX(dataframe, timeperiod=self.EMA_MEDIUM_TERM)

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