Combining Bollinger Band Reversal Entries with Trend Filters
Summary
This long-only strategy combines entry conditions inspired by two approaches, BinHV45 and ClucMay72018. One setup looks for an unusually wide Bollinger envelope, a sharp close-to-close move, a small lower tail, and a close below the prior lower band. The other buys when price is below a 50-period exponential moving average and sufficiently below the lower band based on typical price, subject to a volume condition. Positions exit when price rises above the Bollinger middle band.
The example sets a five-minute timeframe, a five percent minimum return target, and a five percent stop loss. Its comments say that prior backtesting favored limiting open trades and adjusting stake size, but provide no dataset, dates, or measured results to assess that claim. The strategy therefore offers concrete indicator rules but no reproducible evidence of profitability. It is also exposed to falling-market risk: both entry patterns buy weakness, and exits depend on a return toward the middle band or the configured risk limits.
Key ideas
- The strategy merges two Bollinger-based approaches into alternative long-entry patterns.
- One entry requires a wide band, a large recent price move, a limited lower tail, and a close below the prior lower band.
- The second entry combines price below a slow moving average and lower Bollinger band with a volume filter.
- Positions exit when price moves above the Bollinger middle band, with configured profit and loss thresholds.
- The source mentions backtesting observations but does not provide enough evidence to evaluate them.
Tags
Full text
# CombinedBinHAndCluc
# CombinedBinHAndCluc
## Source (GPL-3.0)
```python
# --- Do not remove these libs ---
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy as np
# --------------------------------
import talib.abstract as ta
from freqtrade.strategy import IStrategy
from pandas import DataFrame
def bollinger_bands(stock_price, window_size, num_of_std):
rolling_mean = stock_price.rolling(window=window_size).mean()
rolling_std = stock_price.rolling(window=window_size).std()
lower_band = rolling_mean - (rolling_std * num_of_std)
return np.nan_to_num(rolling_mean), np.nan_to_num(lower_band)
class CombinedBinHAndCluc(IStrategy):
# Based on a backtesting:
# - the best perfomance is reached with "max_open_trades" = 2 (in average for any market),
# so it is better to increase "stake_amount" value rather then "max_open_trades" to get more profit
# - if the market is constantly green(like in JAN 2018) the best performance is reached with
# "max_open_trades" = 2 and minimal_roi = 0.01
INTERFACE_VERSION: int = 3
minimal_roi = {
"0": 0.05
}
stoploss = -0.05
timeframe = '5m'
use_exit_signal = True
exit_profit_only = True
ignore_roi_if_entry_signal = False
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# strategy BinHV45
mid, lower = bollinger_bands(dataframe['close'], window_size=40, num_of_std=2)
dataframe['lower'] = lower
dataframe['bbdelta'] = (mid - dataframe['lower']).abs()
dataframe['closedelta'] = (dataframe['close'] - dataframe['close'].shift()).abs()
dataframe['tail'] = (dataframe['close'] - dataframe['low']).abs()
# strategy ClucMay72018
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=50)
dataframe['volume_mean_slow'] = dataframe['volume'].rolling(window=30).mean()
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
( # strategy BinHV45
dataframe['lower'].shift().gt(0) &
dataframe['bbdelta'].gt(dataframe['close'] * 0.008) &
dataframe['closedelta'].gt(dataframe['close'] * 0.0175) &
dataframe['tail'].lt(dataframe['bbdelta'] * 0.25) &
dataframe['close'].lt(dataframe['lower'].shift()) &
dataframe['close'].le(dataframe['close'].shift())
) |
( # strategy ClucMay72018
(dataframe['close'] < dataframe['ema_slow']) &
(dataframe['close'] < 0.985 * dataframe['bb_lowerband']) &
(dataframe['volume'] < (dataframe['volume_mean_slow'].shift(1) * 20))
),
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
"""
dataframe.loc[
(dataframe['close'] > dataframe['bb_middleband']),
'exit_long'
] = 1
return dataframe
```Shown in full with attribution under the source's licence. Licence: GPL-3.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.