RSI and TEMA Signals with Bollinger Band Filters
Summary
This sample hourly strategy calculates a broad set of indicators, including RSI, ADX, stochastic readings, MACD, money flow, Bollinger Bands, Parabolic SAR, TEMA, and a Hilbert-transform cycle measure. Its actual entry and exit rules use RSI crossings around 30 or 70, the position of TEMA relative to the Bollinger middle band, TEMA’s recent direction, and nonzero volume.
The long entry follows an RSI cross above 30 when TEMA is at or below the band midpoint and rising. The short entry uses a cross above 70 when TEMA is above the midpoint and falling. The exit rules mirror those same conditions for the opposite position. The sample sets a 5% stop loss, allows shorting, disables trailing stops, and sets a very high minimal return threshold. Many calculated indicators do not affect the signals. The document provides no backtest results or rationale establishing whether these rules have an edge, and the mirrored entry and exit conditions may cause the same signal to serve multiple roles.
Key ideas
- The sample operates on hourly candles and calculates several common technical indicators.
- Long entries require an RSI cross above 30 alongside rising TEMA at or below the Bollinger midpoint.
- Short entries require an RSI cross above 70 alongside falling TEMA above the midpoint.
- Exit conditions reuse the corresponding RSI and TEMA patterns used by the opposite entry.
- The strategy permits shorting and sets a 5% stop loss, but includes no performance evidence.
Tags
Full text
# FSampleStrategy.py
```py
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these libs ---
import numpy as np # noqa
import pandas as pd # noqa
from pandas import DataFrame
from freqtrade.strategy import (
BooleanParameter,
CategoricalParameter,
DecimalParameter,
IStrategy,
IntParameter,
)
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
# This class is a sample. Feel free to customize it.
class FSampleStrategy(IStrategy):
INTERFACE_VERSION = 3
timeframe = "1h"
# Minimal ROI designed for the strategy.
# This attribute will be overridden if the config file contains "minimal_roi".
# minimal_roi = {"60": 0.1, "30": 0.2, "0": 0.2}
minimal_roi = {"0": 1}
stoploss = -0.05
can_short = True
# Trailing stoploss
trailing_stop = False
# trailing_only_offset_is_reached = False
# trailing_stop_positive = 0.01
# trailing_stop_positive_offset = 0.0 # Disabled / not configured
# Run "populate_indicators()" only for new candle.
process_only_new_candles = True
# Number of candles the strategy requires before producing valid signals
startup_candle_count: int = 30
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe["adx"] = ta.ADX(dataframe)
# RSI
dataframe["rsi"] = ta.RSI(dataframe)
# Stochastic Fast
stoch_fast = ta.STOCHF(dataframe)
dataframe["fastd"] = stoch_fast["fastd"]
dataframe["fastk"] = stoch_fast["fastk"]
# MACD
macd = ta.MACD(dataframe)
dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"]
dataframe["macdhist"] = macd["macdhist"]
# MFI
dataframe["mfi"] = ta.MFI(dataframe)
# 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"]
dataframe["bb_percent"] = (dataframe["close"] - dataframe["bb_lowerband"]) / (
dataframe["bb_upperband"] - dataframe["bb_lowerband"]
)
dataframe["bb_width"] = (
dataframe["bb_upperband"] - dataframe["bb_lowerband"]
) / dataframe["bb_middleband"]
# Parabolic SAR
dataframe["sar"] = ta.SAR(dataframe)
# TEMA - Triple Exponential Moving Average
dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
# Cycle Indicator
# ------------------------------------
# Hilbert Transform Indicator - SineWave
hilbert = ta.HT_SINE(dataframe)
dataframe["htsine"] = hilbert["sine"]
dataframe["htleadsine"] = hilbert["leadsine"]
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
# Signal: RSI crosses above 30
(qtpylib.crossed_above(dataframe["rsi"], 30))
& (dataframe["tema"] <= dataframe["bb_middleband"])
& ( # Guard: tema below BB middle
dataframe["tema"] > dataframe["tema"].shift(1)
)
& ( # Guard: tema is raising
dataframe["volume"] > 0
) # Make sure Volume is not 0
),
"enter_long",
] = 1
dataframe.loc[
(
# Signal: RSI crosses above 70
(qtpylib.crossed_above(dataframe["rsi"], 70))
& (dataframe["tema"] > dataframe["bb_middleband"])
& ( # Guard: tema above BB middle
dataframe["tema"] < dataframe["tema"].shift(1)
)
& ( # Guard: tema is falling
dataframe["volume"] > 0
) # Make sure Volume is not 0
),
"enter_short",
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
# Signal: RSI crosses above 70
(qtpylib.crossed_above(dataframe["rsi"], 70))
& (dataframe["tema"] > dataframe["bb_middleband"])
& ( # Guard: tema above BB middle
dataframe["tema"] < dataframe["tema"].shift(1)
)
& ( # Guard: tema is falling
dataframe["volume"] > 0
) # Make sure Volume is not 0
),
"exit_long",
] = 1
dataframe.loc[
(
# Signal: RSI crosses above 30
(qtpylib.crossed_above(dataframe["rsi"], 30))
&
# Guard: tema below BB middle
(dataframe["tema"] <= dataframe["bb_middleband"])
& (dataframe["tema"] > dataframe["tema"].shift(1))
& ( # Guard: tema is raising
dataframe["volume"] > 0
) # Make sure Volume is not 0
),
"exit_short",
] = 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.