Smoothed Indicator and Candle Patterns for Long Entries and Exits
Summary
SmoothOperator is an experimental five-minute long strategy that combines common indicators and candle patterns in an attempt to identify bottoms and incomplete peaks. Entry conditions include a short V-shaped rebound after falling prices with oversold CCI and RSI readings, deeply oversold combinations involving CCI, RSI, and MFI, or very low MFI and CCI with RSI below MFI. A general condition requires the latest close to exceed the prior close. The strategy also calculates Bollinger Bands, MACD, and several moving averages; the core combined oscillator smooths RSI, MFI, and CCI before applying another smoothing step.
Exits are based on a turning point in the smoothed oscillator above 100, a run of green candles, or jointly elevated CCI and RSI. The configuration includes a 5% stop loss and a 10% minimum ROI threshold, but the source labels the strategy as experimental and cautions against use. No backtest evidence is provided. These many threshold and pattern rules may be sensitive to market and parameter choices, and the described logic only sets long entries and exits.
Key ideas
- The strategy combines smoothed RSI, MFI, and CCI with candle patterns to identify potential long entries.
- Entry rules include rebound shapes and several combinations of oversold indicator readings.
- Exit signals include a peak-like turn in the combined oscillator, consecutive green candles, or overbought CCI and RSI.
- The configuration specifies a five-minute timeframe, a 5% stop loss, and a 10% minimum ROI threshold.
- The source presents the strategy as experimental and supplies no performance evidence.
Tags
Full text
# SmoothOperator
# SmoothOperator
author@: Gert Wohlgemuth
idea:
The concept is about combining several common indicators, with a heavily smoothing, while trying to detect
a none completed peak shape.
############################################################################### ############################################################################### ############################################################################### ###############################################################################
## Source (GPL-3.0)
```python
# --- 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
import numpy # noqa
# DO NOT USE, just playing with smooting and graphs!
class SmoothOperator(IStrategy):
"""
author@: Gert Wohlgemuth
idea:
The concept is about combining several common indicators, with a heavily smoothing, while trying to detect
a none completed peak shape.
"""
INTERFACE_VERSION: int = 3
# Minimal ROI designed for the strategy.
# we only sell after 100%, unless our sell points are found before
minimal_roi = {
"0": 0.10
}
# Optimal stoploss designed for the strategy
# This attribute will be overridden if the config file contains "stoploss"
# should be converted to a trailing stop loss
stoploss = -0.05
# Optimal timeframe for the strategy
timeframe = '5m'
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
##################################################################################
# required for entry and exit
# CCI
dataframe['cci'] = ta.CCI(dataframe, timeperiod=20)
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
dataframe['adx'] = ta.ADX(dataframe)
dataframe['mfi'] = ta.MFI(dataframe)
dataframe['mfi_smooth'] = ta.EMA(dataframe, timeperiod=11, price='mfi')
dataframe['cci_smooth'] = ta.EMA(dataframe, timeperiod=11, price='cci')
dataframe['rsi_smooth'] = ta.EMA(dataframe, timeperiod=11, price='rsi')
##################################################################################
# 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
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
##################################################################################
# required for entry
bollinger = qtpylib.bollinger_bands(dataframe['close'], window=20, stds=1.6)
dataframe['entry_bb_lowerband'] = bollinger['lower']
dataframe['entry_bb_upperband'] = bollinger['upper']
dataframe['entry_bb_middleband'] = bollinger['mid']
dataframe['bpercent'] = (dataframe['close'] - dataframe['bb_lowerband']) / (
dataframe['bb_upperband'] - dataframe['bb_lowerband']) * 100
dataframe['bsharp'] = (dataframe['bb_upperband'] - dataframe['bb_lowerband']) / (
dataframe['bb_middleband'])
# these seem to be kind useful to measure when bands widen
# but than they are directly based on the moving average
dataframe['bsharp_slow'] = ta.SMA(dataframe, price='bsharp', timeperiod=11)
dataframe['bsharp_medium'] = ta.SMA(dataframe, price='bsharp', timeperiod=8)
dataframe['bsharp_fast'] = ta.SMA(dataframe, price='bsharp', timeperiod=5)
##################################################################################
# rsi and mfi are slightly weighted
dataframe['mfi_rsi_cci_smooth'] = (dataframe['rsi_smooth'] * 1.125 + dataframe['mfi_smooth'] * 1.125 +
dataframe[
'cci_smooth']) / 3
dataframe['mfi_rsi_cci_smooth'] = ta.TEMA(dataframe, timeperiod=21, price='mfi_rsi_cci_smooth')
# playgound
dataframe['candle_size'] = (dataframe['close'] - dataframe['open']) * (
dataframe['close'] - dataframe['open']) / 2
# helps with pattern recognition
dataframe['average'] = (dataframe['close'] + dataframe['open'] + dataframe['high'] + dataframe['low']) / 4
dataframe['sma_slow'] = ta.SMA(dataframe, timeperiod=200, price='close')
dataframe['sma_medium'] = ta.SMA(dataframe, timeperiod=100, price='close')
dataframe['sma_fast'] = ta.SMA(dataframe, timeperiod=50, price='close')
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
# protection against pump and dump
# (dataframe['volume'] < (dataframe['volume'].rolling(window=30).mean().shift(1) * 20))
#
# & (dataframe['macd'] < dataframe['macdsignal'])
# & (dataframe['macd'] > 0)
# # spike below entry band for 3 consecutive ticks
# & (dataframe['low'] < dataframe['entry_bb_lowerband'])
# & (dataframe['low'].shift(1) < dataframe['bb_lowerband'].shift(1))
# & (dataframe['low'].shift(2) < dataframe['bb_lowerband'].shift(2))
# # pattern recognition
# & (
# (dataframe['close'] > dataframe['open'])
# | (dataframe['CDLHAMMER'] == 100)
# | (dataframe['CDLINVERTEDHAMMER'] == 100)
# | (dataframe['CDLDRAGONFLYDOJI'] == 100)
# )
# bottom curve detection
# & (dataframe['mfi_rsi_cci_smooth'] < 0)
#
# |
(
# 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)
)
|
# buy in very oversold conditions
(
(dataframe['low'] < dataframe['bb_middleband'])
& (dataframe['cci'] < -200)
& (dataframe['rsi'] < 30)
& (dataframe['mfi'] < 30)
)
|
# etc tends to trade like this
# over very long periods of slowly building up coins
# does not happen often, but once in a while
(
(dataframe['mfi'] < 10)
& (dataframe['cci'] < -150)
& (dataframe['rsi'] < dataframe['mfi'])
)
)
&
# ensure we have an overall uptrend
(dataframe['close'] > dataframe['close'].shift())
),
'enter_long'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# different strategy used for sell points, due to be able to duplicate it to 100%
dataframe.loc[
(
(
# This generates very nice sale points, and mostly sit's one stop behind
# the top of the peak
(
(dataframe['mfi_rsi_cci_smooth'] > 100)
& (dataframe['mfi_rsi_cci_smooth'].shift(1) > dataframe['mfi_rsi_cci_smooth'])
& (dataframe['mfi_rsi_cci_smooth'].shift(2) < dataframe['mfi_rsi_cci_smooth'].shift(1))
& (dataframe['mfi_rsi_cci_smooth'].shift(3) < dataframe['mfi_rsi_cci_smooth'].shift(2))
)
|
# This helps with very long, sideways trends, to get out of a market before
# it dumps
(
StrategyHelper.eight_green_candles(dataframe)
)
|
# in case of very overbought market, like some one pumping
# sell
(
(dataframe['cci'] > 200)
& (dataframe['rsi'] > 70)
)
)
),
'exit_long'] = 1
return dataframe
class StrategyHelper:
"""
simple helper class to predefine a couple of patterns for our
strategy
"""
@staticmethod
def seven_green_candles(dataframe):
"""
evaluates if we are having 7 green candles in a row
:param self:
:param dataframe:
:return:
"""
return (
(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))
)
@staticmethod
def eight_green_candles(dataframe):
"""
evaluates if we are having 8 green candles in a row
:param self:
:param dataframe:
:return:
"""
return (
(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['open'].shift(8) < dataframe['close'].shift(8))
)
@staticmethod
def eight_red_candles(dataframe, shift=0):
"""
evaluates if we are having 8 red candles in a row
:param self:
:param dataframe:
:param shift: shift the pattern by n
:return:
"""
return (
(dataframe['open'].shift(shift) > dataframe['close'].shift(shift)) &
(dataframe['open'].shift(1 + shift) > dataframe['close'].shift(1 + shift)) &
(dataframe['open'].shift(2 + shift) > dataframe['close'].shift(2 + shift)) &
(dataframe['open'].shift(3 + shift) > dataframe['close'].shift(3 + shift)) &
(dataframe['open'].shift(4 + shift) > dataframe['close'].shift(4 + shift)) &
(dataframe['open'].shift(5 + shift) > dataframe['close'].shift(5 + shift)) &
(dataframe['open'].shift(6 + shift) > dataframe['close'].shift(6 + shift)) &
(dataframe['open'].shift(7 + shift) > dataframe['close'].shift(7 + shift)) &
(dataframe['open'].shift(8 + shift) > dataframe['close'].shift(8 + shift))
)
@staticmethod
def four_green_one_red_candle(dataframe):
"""
evaluates if we are having a red candle and 4 previous green
:param self:
:param dataframe:
:return:
"""
return (
(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))
)
@staticmethod
def four_red_one_green_candle(dataframe):
"""
evaluates if we are having a green candle and 4 previous red
:param self:
:param dataframe:
:return:
"""
return (
(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))
)
```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.