BbandRsi
Tested 1 Jun 2023 – 30 May 2025
Rank 11 of 27 by return
BbandRsi turned $1,000 into $1,079 (+7.9%) over 729 days, while holding Bitcoin returned +288.1% and an equal mix of the ten coins +194.8%. Its worst drawdown was 32.7%, a substantial dip to sit through. It won 64% of trades for a profit factor of 1.03.
How BbandRsi trades
BbandRsi buys oversold dips: RSI below 30 and price below the lower Bollinger Band on the 1-hour chart. It sells once RSI climbs above 70, or when the 10% take-profit is reached.
Buying weakness works well in ranging markets and poorly in steady downtrends, where RSI can stay below 30 for days. The 25% stoploss leaves a lot of room for a bad trade to grow.
Rules at a glance
- Indicators
- RSI 14, Bollinger Bands 20/2
- Take-profit (minimal ROI)
- 10% from entry
- Stoploss
- −25%
- 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 |
|---|---|---|---|---|
| BNB/USDT | 49 | 73.5% | +144.13 | 4d 18h |
| SOL/USDT | 36 | 61.1% | +43.93 | 4d 14h |
| ETH/USDT | 52 | 65.4% | +42.77 | 4d 13h |
| BTC/USDT | 50 | 60.0% | +39.78 | 4d 20h |
| ADA/USDT | 39 | 69.2% | +25.02 | 4d 20h |
| XRP/USDT | 43 | 67.4% | +19.20 | 5d 8h |
| LINK/USDT | 28 | 71.4% | +17.67 | 4d 4h |
| DOT/USDT | 32 | 59.4% | −16.28 | 5d 17h |
| AVAX/USDT | 31 | 48.4% | −72.78 | 5d 9h |
| DOGE/USDT | 34 | 55.9% | −164.25 | 4d 16h |
Why trades closed
| Exit reason | Trades | Win rate | Profit (USDT) |
|---|---|---|---|
| Take-profit (ROI) | 39 | 100.0% | +780.70 |
| Exit signal | 326 | 65.0% | +574.77 |
| Still open at end of test | 4 | 0.0% | −17.78 |
| Stoploss | 25 | 0.0% | −1,258.51 |
The code
BbandRsi comes from the community freqtrade-strategies repository (GPL-3.0). These are the parts that decide when it trades; the full file is 64 lines.
Entry rules
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( (dataframe['rsi'] < 30) & (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['rsi'] > 70) ), 'exit_long'] = 1 return dataframe
Indicators
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) # Bollinger bands 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'] 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 1h --timerange 20230501-20250530
freqtrade backtesting --strategy BbandRsi --timeframe 1h \
--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.