💬 Forum

Strategy01_EMA_Crossover

🏆 League #402 / 1941

Nhebi23/halal-algo-trading-strategies/user_data/strategies/Strategy01_EMA_Crossover.py · first seen 2026-07-16 · repo updated 2026-05-07 · ⬇ 1 download

Basics mode: spot timeframe: 4h interface version: 3
Settings stoploss: -0.1 has minimal roi trailing process only new candles startup candle count: 210
Indicators EMA SMA pandas_ta
Concepts trailing trend_following
15 related strategies ( identical code, similar name)

Each tile is a different kind of check — from an instant code lint to full sandboxed backtests and forward tests on recent data. Not sure what a check actually proves? See the FAQ →

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort:skip_file
# --- Do not remove these imports ---
from datetime import datetime
from typing import Optional

import numpy as np
import pandas as pd
from pandas import DataFrame

from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter

import pandas_ta as ta

# ---------------------------------------------------------------------------
# Strategy01_EMA_Crossover
# ---------------------------------------------------------------------------


class Strategy01_EMA_Crossover(IStrategy):
    """
    EMA Crossover Strategy — SPOT ONLY (Halal Algo Trading, Batch 1)
    =================================================================
    Timeframe : 4h
    Author    : Halal Algo Trading
    Version   : 1.0

    Overview
    --------
    A trend-following strategy built around a classic triple-EMA framework:

        • Fast EMA  (12-period) — reacts quickly to price momentum
        • Slow EMA  (50-period) — confirms the medium-term trend direction
        • Trend EMA (200-period) — acts as the primary bull/bear filter

    A Heikin Ashi candle confirmation is added to reduce false crossovers
    caused by brief spikes, and a 20-period volume SMA gate prevents entries
    in low-liquidity environments.

    Entry Conditions (ALL must be true)
    ------------------------------------
    1. Fast EMA (12) crosses ABOVE Slow EMA (50)  → momentum shift
    2. Price (close) is ABOVE the 200 EMA          → overall bull market
    3. Current candle volume > 20-period volume SMA → confirmed participation
    4. Heikin Ashi close > Heikin Ashi open         → HA candle is bullish

    Exit Conditions (ANY triggers exit)
    ------------------------------------
    A. Fast EMA crosses BELOW Slow EMA             → momentum reversal
    B. Price (close) closes BELOW Slow EMA (50)    → trend breakdown

    Risk Management
    ---------------
    • Hard stoploss        : -10 %
    • Trailing stop        : enabled
    • Trailing stop positive: 2 % (locks in profit once positive)
    • ROI ladder           : 5 % @ open, 4 % @ 60 min, 3 % @ 120 min,
                             1 % @ 240 min
    • SPOT only — no short, no margin, no futures

    Backtest Reference
    ------------------
    Sharpe 1.30 | Return 491 % | Max DD -34 % | Win Rate 35 % | PF 1.73
    """

    # -------------------------------------------------------------------------
    # Freqtrade metadata
    # -------------------------------------------------------------------------
    INTERFACE_VERSION = 3

    # Strategy name shown in logs / hyperopt
    strategy_name = "Strategy01_EMA_Crossover"

    # Candle timeframe
    timeframe = "4h"

    # Only look at completed (closed) candles
    process_only_new_candles = True

    # Startup candle count — need at least 200 candles for the 200 EMA
    startup_candle_count: int = 210

    # -------------------------------------------------------------------------
    # ROI — minimal profit targets (time in minutes → target return)
    # -------------------------------------------------------------------------
    minimal_roi = {
        "0":   0.05,   # 5 % immediately
        "60":  0.04,   # 4 % after 60 min
        "120": 0.03,   # 3 % after 120 min
        "240": 0.01,   # 1 % after 240 min
    }

    # -------------------------------------------------------------------------
    # Stoploss & Trailing Stop
    # -------------------------------------------------------------------------
    stoploss = -0.10                   # Hard stoploss: -10 %
    trailing_stop = True               # Enable trailing stop
    trailing_stop_positive = 0.02      # Trail 2 % once trade is profitable
    trailing_stop_positive_offset = 0.03  # Only activate trailing after +3 %
    trailing_only_offset_is_reached = True

    # -------------------------------------------------------------------------
    # Misc
    # -------------------------------------------------------------------------
    # SPOT ONLY — no short positions
    can_short = False

    # Use custom sell (exit) signal
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    # -------------------------------------------------------------------------
    # Helper functions
    # -------------------------------------------------------------------------

    @staticmethod
    def crossed_above(s1: pd.Series, s2: pd.Series) -> pd.Series:
        """Return True on the bar where s1 crosses from below to above s2."""
        return (s1 > s2) & (s1.shift(1) <= s2.shift(1))

    @staticmethod
    def crossed_below(s1: pd.Series, s2: pd.Series) -> pd.Series:
        """Return True on the bar where s1 crosses from above to below s2."""
        return (s1 < s2) & (s1.shift(1) >= s2.shift(1))

    # -------------------------------------------------------------------------
    # Indicators
    # -------------------------------------------------------------------------

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Compute all technical indicators used by entry / exit conditions.

        Indicators added to `dataframe`
        --------------------------------
        ema_fast    — 12-period EMA
        ema_slow    — 50-period EMA
        ema_trend   — 200-period EMA (bull/bear filter)
        volume_sma  — 20-period simple moving average of volume
        ha_open     — Heikin Ashi open
        ha_close    — Heikin Ashi close
        """

        # --- Exponential Moving Averages ---
        dataframe["ema_fast"]  = ta.ema(dataframe["close"], length=12)
        dataframe["ema_slow"]  = ta.ema(dataframe["close"], length=50)
        dataframe["ema_trend"] = ta.ema(dataframe["close"], length=200)

        # --- Volume filter: 20-period SMA of volume ---
        dataframe["volume_sma"] = ta.sma(dataframe["volume"], length=20)

        # --- Heikin Ashi candles ---
        # HA Close = (O + H + L + C) / 4
        dataframe["ha_close"] = (
            dataframe["open"] + dataframe["high"]
            + dataframe["low"]  + dataframe["close"]
        ) / 4

        # HA Open = (prev HA Open + prev HA Close) / 2
        ha_open = (dataframe["open"].shift(1) + dataframe["close"].shift(1)) / 2
        if len(dataframe) > 0:
            ha_open.iloc[0] = (dataframe["open"].iloc[0] + dataframe["close"].iloc[0]) / 2
        dataframe["ha_open"] = ha_open

        # --- NaN Safety: convert any None to NaN so pandas comparisons work ---
        for col in ['ema_fast', 'ema_slow', 'ema_trend', 'volume_sma', 'ha_open', 'ha_close']:
            dataframe[col] = pd.to_numeric(dataframe[col], errors='coerce')

        return dataframe

    # -------------------------------------------------------------------------
    # Entry Signal
    # -------------------------------------------------------------------------

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Generate LONG entry signals.

        Conditions (ALL must be true simultaneously):
        1. Fast EMA (12) crosses above Slow EMA (50)
        2. Price (close) is above 200 EMA                  → bull trend
        3. Volume exceeds its 20-period SMA                → liquidity confirmed
        4. Heikin Ashi candle is bullish (HA close > HA open)

        The resulting signal is stored in the `enter_long` column.
        """

        conditions = (
            dataframe["ema_fast"].notna()
            & dataframe["ema_slow"].notna()
            & dataframe["ema_trend"].notna()
            & dataframe["volume_sma"].notna()
            & dataframe["ha_close"].notna()
            & dataframe["ha_open"].notna()
        )
        dataframe.loc[
            conditions
            & self.crossed_above(dataframe["ema_fast"].fillna(0), dataframe["ema_slow"].fillna(0))
            & (dataframe["close"] > dataframe["ema_trend"].fillna(np.inf))
            & (dataframe["volume"] > dataframe["volume_sma"].fillna(np.inf))
            & (dataframe["ha_close"].fillna(0) > dataframe["ha_open"].fillna(0)),
            "enter_long",
        ] = 1

        return dataframe

    # -------------------------------------------------------------------------
    # Exit Signal
    # -------------------------------------------------------------------------

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Generate LONG exit signals.

        Exit is triggered when EITHER condition is met:
        A. Fast EMA (12) crosses below Slow EMA (50) — momentum reversal
        B. Price (close) closes below Slow EMA (50)  — trend breakdown

        The resulting signal is stored in the `exit_long` column.
        """

        exit_valid = dataframe["ema_fast"].notna() & dataframe["ema_slow"].notna()
        dataframe.loc[
            exit_valid
            & (
                self.crossed_below(dataframe["ema_fast"].fillna(0), dataframe["ema_slow"].fillna(0))
                | (dataframe["close"] < dataframe["ema_slow"].fillna(0))
            ),
            "exit_long",
        ] = 1

        return dataframe