Reinforced Quickie: Trend-Filtered Mean-Reversion Entries
Summary
This Freqtrade strategy seeks long entries after short-term weakness while using a higher timeframe trend filter. On the five-minute chart, it looks for either a close near a recent low and below the lower Bollinger Band, or a multi-bar decline followed by a reversal with low CCI, RSI, and money flow. It resamples the data to establish a broader moving-average trend and requires that trend to be rising and below price, while also applying a volume spike safeguard.
Exits trigger when price reaches upper-band and recent-high conditions with elevated money flow, or after a run of bullish candles with high RSI. The code also sets a 1% minimal ROI and a 5% stop loss. These rules are presented as source code, not supported by backtest results or rationale for parameter choices. Its implementation and performance therefore require independent review, including validation of resampling behavior and trading costs.
Key ideas
- The strategy searches for short-term pullbacks and reversal patterns for long entries.\nA resampled moving average is used to filter entries by broader trend direction.\nEntry conditions combine Bollinger Bands, moving averages, CCI, RSI, money flow, and volume.\nExits use overbought conditions or a sequence of bullish candles.\nThe code gives ROI and stop-loss settings but no backtest evidence.
Tags
Full text
# ReinforcedQuickie.py
```py
# --- Do not remove these libs ---
from freqtrade.strategy import IStrategy
from typing import Dict, List
from functools import reduce
from pandas import DataFrame
# --------------------------------
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from typing import Dict, List
from functools import reduce
from pandas import DataFrame, DatetimeIndex, merge
# --------------------------------
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy # noqa
class ReinforcedQuickie(IStrategy):
"""
author@: Gert Wohlgemuth
works on new objectify branch!
idea:
only buy on an upward tending market
"""
INTERFACE_VERSION: int = 3
# Minimal ROI designed for the strategy.
# This attribute will be overridden if the config file contains "minimal_roi"
minimal_roi = {
"0": 0.01
}
# Optimal stoploss designed for the strategy
# This attribute will be overridden if the config file contains "stoploss"
stoploss = -0.05
# Optimal timeframe for the strategy
timeframe = '5m'
# resample factor to establish our general trend. Basically don't buy if a trend is not given
resample_factor = 12
EMA_SHORT_TERM = 5
EMA_MEDIUM_TERM = 12
EMA_LONG_TERM = 21
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe = self.resample(dataframe, self.timeframe, self.resample_factor)
##################################################################################
# buy and sell indicators
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)
dataframe['cci'] = ta.CCI(dataframe)
dataframe['mfi'] = ta.MFI(dataframe)
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=7)
dataframe['average'] = (dataframe['close'] + dataframe['open'] + dataframe['high'] + dataframe['low']) / 4
##################################################################################
# required for graphing
bollinger = qtpylib.bollinger_bands(dataframe['close'], window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_upperband'] = bollinger['upper']
dataframe['bb_middleband'] = bollinger['mid']
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
dataframe.loc[
(
(
(
(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'])
)
|
# simple v bottom shape (lopsided to the left to increase reactivity)
# which has to be below a very slow average
# this pattern only catches a few, but normally very good buy points
(
(dataframe['average'].shift(5) > dataframe['average'].shift(4))
& (dataframe['average'].shift(4) > dataframe['average'].shift(3))
& (dataframe['average'].shift(3) > dataframe['average'].shift(2))
& (dataframe['average'].shift(2) > dataframe['average'].shift(1))
& (dataframe['average'].shift(1) < dataframe['average'].shift(0))
& (dataframe['low'].shift(1) < dataframe['bb_middleband'])
& (dataframe['cci'].shift(1) < -100)
& (dataframe['rsi'].shift(1) < 30)
& (dataframe['mfi'].shift(1) < 30)
)
)
# safeguard against down trending markets and a pump and dump
&
(
(dataframe['volume'] < (dataframe['volume'].rolling(window=30).mean().shift(1) * 20)) &
(dataframe['resample_sma'] < dataframe['close']) &
(dataframe['resample_sma'].shift(1) < dataframe['resample_sma'])
)
)
,
'enter_long'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
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']) &
(dataframe['mfi'] > 80)
) |
# always sell on eight green candles
# with a high rsi
(
(dataframe['open'] < dataframe['close']) &
(dataframe['open'].shift(1) < dataframe['close'].shift(1)) &
(dataframe['open'].shift(2) < dataframe['close'].shift(2)) &
(dataframe['open'].shift(3) < dataframe['close'].shift(3)) &
(dataframe['open'].shift(4) < dataframe['close'].shift(4)) &
(dataframe['open'].shift(5) < dataframe['close'].shift(5)) &
(dataframe['open'].shift(6) < dataframe['close'].shift(6)) &
(dataframe['open'].shift(7) < dataframe['close'].shift(7)) &
(dataframe['rsi'] > 70)
)
,
'exit_long'
] = 1
return dataframe
def resample(self, dataframe, interval, factor):
# defines the reinforcement logic
# resampled dataframe to establish if we are in an uptrend, downtrend or sideways trend
df = dataframe.copy()
df = df.set_index(DatetimeIndex(df['date']))
ohlc_dict = {
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last'
}
df = df.resample(str(int(interval[:-1]) * factor) + 'min',
label="right").agg(ohlc_dict).dropna(how='any')
df['resample_sma'] = ta.SMA(df, timeperiod=25, price='close')
df = df.drop(columns=['open', 'high', 'low', 'close'])
df = df.resample(interval[:-1] + 'min')
df = df.interpolate(method='time')
df['date'] = df.index
df.index = range(len(df))
dataframe = merge(dataframe, df, on='date', how='left')
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.