TEMA (10/50) Crossover + Stochastic Oversold + Volume Filter SPOT only — halal algo trading, no leverage.
Timeframe
4h
Direction
Long Only
Stoploss
-2.2%
Trailing Stop
Yes
ROI
0m: 4.0%, 30m: 3.0%, 60m: 2.0%, 120m: 1.0%
Interface Version
3
Startup Candles
N/A
Indicators
5
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
"""
Strategy07_TEMA
===============
A Triple Exponential Moving Average (TEMA) crossover strategy with
Stochastic Oscillator confirmation, designed for SPOT crypto trading.
Halal-compliant: SPOT only — no shorting, no margin, no futures.
Concept
-------
TEMA eliminates the lag inherent in simple and double EMAs by applying
a triple-smoothing formula:
TEMA = 3 × EMA1 - 3 × EMA2 + EMA3
where EMA1 = EMA(close), EMA2 = EMA(EMA1), EMA3 = EMA(EMA2).
TEMA responds to price changes faster than EMA or DEMA while keeping
noise lower than a raw price signal. The crossover of a fast TEMA (10)
over a slow TEMA (50) identifies trend direction changes early.
Entry Logic (3-step confirmation)
-----------------------------------
Step 1 — Crossover detection:
Fast TEMA (10) crosses ABOVE slow TEMA (50).
This is the initial trend-flip signal.
Step 2 — Bullish candle confirmation (2 bars):
The 2 candles immediately following the crossover must BOTH close
bullish (close > open). This filters out whipsaws — if the crossover
was genuine, price should continue higher with bullish momentum.
Step 3 — Volume and Stochastic filters:
• Volume > 20-period average → confirms real buying interest.
• Stochastic %K (14,3,3) < 20 → price is oversold at crossover,
providing a better risk/reward entry point.
Exit Logic (2-bar confirmation)
---------------------------------
Fast TEMA crosses BELOW slow TEMA AND the next 2 candles are BOTH
bearish (close < open). Mirror of entry — avoids exiting on brief
pullbacks within a still-valid uptrend.
Risk Parameters (from hyperopt)
---------------------------------
stoploss : -2.2 % (tight — TEMA crossover strategy trades
frequently; small losses cut quickly)
trailing_stop : True
ROI table : 4 % (0 min) → 3 % (30 min) → 2 % (60 min) → 1 % (120 min)
Timeframe : 4h
Author : Halal Algo Batch 2
"""
from freqtrade.strategy import IStrategy
import numpy as np
import pandas as pd
import pandas_ta as ta
from pandas import DataFrame
# ---------------------------------------------------------------------------
# Utility helpers
# ---------------------------------------------------------------------------
def crossed_above(s1: pd.Series, s2: pd.Series) -> pd.Series:
"""Return True on the bar where s1 crosses above s2."""
return (s1 > s2) & (s1.shift(1) <= s2.shift(1))
def crossed_below(s1: pd.Series, s2: pd.Series) -> pd.Series:
"""Return True on the bar where s1 crosses below s2."""
return (s1 < s2) & (s1.shift(1) >= s2.shift(1))
# ---------------------------------------------------------------------------
# Strategy
# ---------------------------------------------------------------------------
class Strategy07_TEMA(IStrategy):
"""
TEMA (10/50) Crossover + Stochastic Oversold + Volume Filter
SPOT only — halal algo trading, no leverage.
"""
# ------------------------------------------------------------------
# Freqtrade metadata
# ------------------------------------------------------------------
INTERFACE_VERSION = 3
timeframe = "4h"
can_short = False # SPOT only
# TEMA(50) needs ~150 candles to converge; add buffer for Stochastic(14).
startup_candle_count: int = 200
# ------------------------------------------------------------------
# ROI
# ------------------------------------------------------------------
minimal_roi = {
"0": 0.04, # 4 % immediately
"30": 0.03, # 3 % after 30 min (0.5 x 4h bar)
"60": 0.02, # 2 % after 60 min
"120": 0.01, # 1 % after 120 min
}
# ------------------------------------------------------------------
# Stop-loss & trailing stop
# ------------------------------------------------------------------
stoploss = -0.022 # -2.2 % tight stop (hyperopt result)
trailing_stop = True
trailing_stop_positive = 0.01 # activate trailing stop once +1 % profit
trailing_stop_positive_offset = 0.02
trailing_only_offset_is_reached = True
# ------------------------------------------------------------------
# Misc
# ------------------------------------------------------------------
process_only_new_candles = True
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = False
# ------------------------------------------------------------------
# Indicator calculation
# ------------------------------------------------------------------
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Compute TEMA fast/slow, Stochastic oscillator, and volume MA.
TEMA (Triple Exponential Moving Average)
-----------------------------------------
pandas_ta computes TEMA using the exact triple-EMA formula.
Fast (10) reacts sharply to recent price moves.
Slow (50) provides a stable trend baseline.
Stochastic Oscillator (14, 3, 3)
----------------------------------
%K : raw stochastic — position of close within the 14-bar range.
%D : 3-period SMA of %K (signal line).
Values < 20 indicate oversold; values > 80 indicate overbought.
We use %K < 20 as an entry filter for better risk/reward entries.
Crossover signals
------------------
tema_cross_up : fast TEMA crosses above slow TEMA (potential entry)
tema_cross_down : fast TEMA crosses below slow TEMA (potential exit)
2-bar confirmation columns
---------------------------
bull_confirm : 2 consecutive bullish candles (close > open) after crossover
bear_confirm : 2 consecutive bearish candles (close < open) after crossover
"""
# ── TEMA indicators ─────────────────────────────────────────────
dataframe["tema_fast"] = ta.tema(dataframe["close"], length=10)
dataframe["tema_slow"] = ta.tema(dataframe["close"], length=50)
# ── Crossover events ────────────────────────────────────────────
dataframe["tema_cross_up"] = crossed_above(dataframe["tema_fast"], dataframe["tema_slow"])
dataframe["tema_cross_down"] = crossed_below(dataframe["tema_fast"], dataframe["tema_slow"])
# ── 2-bar bullish confirmation after crossover ──────────────────
# A "bullish candle" is one where close > open.
candle_bull = (dataframe["close"] > dataframe["open"]).astype(int)
# We want: crossover happened 2 bars ago, AND candles at t-1 AND t are bull.
# This checks that the 2 bars immediately AFTER the cross are both bullish.
# At the time of signal generation (bar t):
# - bar t-2 is when the cross happened
# - bar t-1 must be bullish
# - bar t must be bullish
dataframe["bull_confirm"] = (
dataframe["tema_cross_up"].shift(2).fillna(False).astype(bool) &
(candle_bull.shift(1) == 1) &
(candle_bull == 1)
)
# ── 2-bar bearish confirmation after crossover ──────────────────
candle_bear = (dataframe["close"] < dataframe["open"]).astype(int)
dataframe["bear_confirm"] = (
dataframe["tema_cross_down"].shift(2).fillna(False).astype(bool) &
(candle_bear.shift(1) == 1) &
(candle_bear == 1)
)
# ── Stochastic Oscillator (14, 3, 3) ────────────────────────────
stoch = ta.stoch(
dataframe["high"], dataframe["low"], dataframe["close"],
k=14, d=3, smooth_k=3
)
# pandas_ta column names: STOCHk_14_3_3, STOCHd_14_3_3
dataframe["slowk"] = stoch["STOCHk_14_3_3"]
dataframe["slowd"] = stoch["STOCHd_14_3_3"]
# ── Volume filter : 20-period SMA of volume ─────────────────────
dataframe["vol_ma20"] = ta.sma(dataframe["volume"], length=20)
# --- NaN Safety: convert any None to NaN so pandas comparisons work ---
for col in dataframe.columns:
if col not in ['open', 'high', 'low', 'close', 'volume', 'date']:
try:
dataframe[col] = pd.to_numeric(dataframe[col], errors='coerce')
except (ValueError, TypeError):
pass
return dataframe
# ------------------------------------------------------------------
# Entry signal
# ------------------------------------------------------------------
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Long entry conditions (ALL must be true simultaneously):
1. bull_confirm = True
→ TEMA fast crossed above TEMA slow exactly 2 bars ago, AND
both the bar after the cross AND the current bar are bullish
(close > open). Two confirmed bullish candles validate the
crossover is not a whipsaw.
2. Volume > 20-period average
→ Confirms real buying interest is behind the move.
3. Stochastic %K < 20 (oversold)
→ Entry price is at a relatively low point within recent range.
This improves risk/reward and reduces the chance of buying
at a local top right after the TEMA crossover.
4. Volume > 0 (data quality guard).
"""
conditions = (
dataframe["bull_confirm"] &
(dataframe["volume"] > dataframe["vol_ma20"]) &
(dataframe["slowk"] < 20) &
(dataframe["volume"] > 0)
)
dataframe.loc[conditions, "enter_long"] = 1
return dataframe
# ------------------------------------------------------------------
# Exit signal
# ------------------------------------------------------------------
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Long exit conditions (ALL must be true simultaneously):
1. bear_confirm = True
→ TEMA fast crossed below TEMA slow exactly 2 bars ago, AND
both the bar after the cross AND the current bar are bearish
(close < open). Two confirmed bearish candles validate the
crossover is a genuine trend reversal, not a brief pullback.
2. Volume > 0 (data quality guard).
Note: The tight -2.2 % stoploss and trailing stop provide additional
protection for sharp drops that do not wait for a clean TEMA crossover.
"""
conditions = (
dataframe["bear_confirm"] &
(dataframe["volume"] > 0)
)
dataframe.loc[conditions, "exit_long"] = 1
return dataframe