Triple Supertrend + 200 EMA + Volume Filter SPOT only — halal algo trading, no leverage.
Timeframe
4h
Direction
Long Only
Stoploss
-26.5%
Trailing Stop
Yes
ROI
0m: 10.0%, 120m: 5.0%, 240m: 3.0%, 480m: 1.0%
Interface Version
3
Startup Candles
N/A
Indicators
4
freqtrade/freqtrade-strategies
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
"""
Strategy05_Supertrend
=====================
A triple Supertrend trend-following strategy designed for SPOT crypto trading.
Halal-compliant: SPOT only — no shorting, no margin, no futures.
Concept
-------
Three Supertrend indicators are stacked at increasing ATR periods and multipliers,
creating a "tunnel" of support confirmation:
ST1 : ATR=10, mult=2.0 (fastest — used for exit trigger)
ST2 : ATR=20, mult=4.0 (medium)
ST3 : ATR=30, mult=6.0 (slowest — strongest trend filter)
Entry only fires when price is bullish across ALL three Supertrends AND sits above
the 200 EMA macro trend filter AND volume is above the 20-period average.
This triple confirmation dramatically reduces false signals.
Exit fires as soon as price drops below the FASTEST Supertrend (ST1), capturing
the earliest warning of trend exhaustion without waiting for full trend reversal.
Backtest Reference (in-sample)
--------------------------------
Sharpe Ratio : 1.11
Total Return : 275 %
Max Drawdown : -49 %
Risk Parameters (from hyperopt)
---------------------------------
stoploss : -26.5 %
trailing_stop : True
trailing_stop_positive : 3.0 % (locks in profit once ≥ 3 % gain)
ROI table : 10 % (0 min) → 5 % (120 min) → 3 % (240 min) → 1 % (480 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 Strategy05_Supertrend(IStrategy):
"""
Triple Supertrend + 200 EMA + Volume Filter
SPOT only — halal algo trading, no leverage.
"""
# ------------------------------------------------------------------
# Freqtrade metadata
# ------------------------------------------------------------------
INTERFACE_VERSION = 3
timeframe = "4h"
can_short = False # SPOT only
# Minimum number of candles required before strategy produces signals.
# Longest indicator period is the 200 EMA; add a buffer.
startup_candle_count: int = 220
# ------------------------------------------------------------------
# ROI — exit when profit target is hit
# ------------------------------------------------------------------
minimal_roi = {
"0": 0.10, # 10 % immediately
"120": 0.05, # 5 % after 120 min (2 x 4h bars)
"240": 0.03, # 3 % after 240 min
"480": 0.01, # 1 % after 480 min (5 x 4h bars)
}
# ------------------------------------------------------------------
# Stop-loss & trailing stop
# ------------------------------------------------------------------
stoploss = -0.265 # -26.5 % hard stop (hyperopt result)
trailing_stop = True
trailing_stop_positive = 0.03 # activate trailing stop once +3 % profit
trailing_stop_positive_offset = 0.05 # offset before trailing activates
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 all indicators needed by entry/exit logic.
Supertrend column naming (pandas_ta convention):
SUPERT_<length>_<multiplier> — the Supertrend line value
SUPERTd_<length>_<multiplier> — direction: +1 bullish, -1 bearish
SUPERTl_<length>_<multiplier> — long (support) line
SUPERTs_<length>_<multiplier> — short (resistance) line
"""
# ── Supertrend 1 : ATR=10, mult=2.0 (fastest) ──────────────────
st1 = ta.supertrend(
dataframe["high"], dataframe["low"], dataframe["close"],
length=10, multiplier=2.0
)
dataframe["st1_val"] = st1["SUPERT_10_2.0"]
dataframe["st1_dir"] = st1["SUPERTd_10_2.0"] # +1 = bullish
# ── Supertrend 2 : ATR=20, mult=4.0 (medium) ───────────────────
st2 = ta.supertrend(
dataframe["high"], dataframe["low"], dataframe["close"],
length=20, multiplier=4.0
)
dataframe["st2_val"] = st2["SUPERT_20_4.0"]
dataframe["st2_dir"] = st2["SUPERTd_20_4.0"]
# ── Supertrend 3 : ATR=30, mult=6.0 (slowest) ──────────────────
st3 = ta.supertrend(
dataframe["high"], dataframe["low"], dataframe["close"],
length=30, multiplier=6.0
)
dataframe["st3_val"] = st3["SUPERT_30_6.0"]
dataframe["st3_dir"] = st3["SUPERTd_30_6.0"]
# ── Macro trend filter : 200 EMA ────────────────────────────────
dataframe["ema200"] = ta.ema(dataframe["close"], length=200)
# ── Volume filter : 20-period simple moving average 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. ST1 is bullish (direction = +1)
2. ST2 is bullish (direction = +1)
3. ST3 is bullish (direction = +1)
4. Close price is above the 200 EMA → macro uptrend confirmed
5. Volume exceeds 20-period average → real buying interest
The three Supertrend confirmations filter out choppy / sideways markets.
The 200 EMA ensures we only trade in the macro bull regime.
The volume filter avoids thin-market false breakouts.
"""
conditions = (
(dataframe["st1_dir"] == 1) &
(dataframe["st2_dir"] == 1) &
(dataframe["st3_dir"] == 1) &
(dataframe["close"] > dataframe["ema200"]) &
(dataframe["volume"] > dataframe["vol_ma20"]) &
(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 condition:
Price closes BELOW the fastest Supertrend line (ST1, ATR=10/mult=2.0),
indicated by ST1 direction flipping to -1 (bearish).
Using the fastest Supertrend as the exit trigger provides the earliest
warning of trend deterioration, helping to protect profits before the
slower indicators also flip bearish.
"""
conditions = (
(dataframe["st1_dir"] == -1) &
(dataframe["volume"] > 0)
)
dataframe.loc[conditions, "exit_long"] = 1
return dataframe