← Back to list

higher_lows_ema20_support

Buy after 3 consecutive candles with rising lows, all staying above EMA20, then breakout

Symbol: ETH | Exchange: Binance

6/6
Profitable Years
+143.0%
Total Return
40.6%
Avg Win Rate
0.56
Avg Sharpe

Year-by-Year Results

Year Return Win Rate Trades Max DD Sharpe
2020 +92.1% 50.0% 24 15.3% 1.41
2021 +5.9% 45.8% 24 37.6% 0.10
2022 +8.3% 43.8% 16 10.4% 0.38
2023 +9.7% 35.7% 14 10.9% 0.46
2024 +12.8% 34.8% 23 13.9% 0.52
2025 +14.1% 33.3% 18 12.5% 0.47

Entry Logic

See strategy file

Exit Logic

See strategy file

Source Code

"""
Strategy: higher_lows_ema20_support
===================================
Buy after 3 consecutive candles with rising lows, all staying above EMA20, then breakout

Performance: 6/6 years profitable | Total: +145.4%
2020: +92.3% | 50% WR | 24 trades
2021: +4.8% | 46% WR | 24 trades
2022: +8.4% | 44% WR | 16 trades
2023: +14.3% | 43% WR | 14 trades
2024: +13.7% | 36% WR | 22 trades
2025: +12.0% | 35% WR | 17 trades
"""
import sys
sys.path.insert(0, "/root/trade_rules")
from lib import ema


def init_strategy():
    return {
        'name': 'higher_lows_ema20_support',
        'subscriptions': [
            {'symbol': 'ETHUSDT', 'exchange': 'binance', 'timeframe': '4h'},
        ],
        'parameters': {}
    }


def process_time_step(ctx):
    key = ('ETHUSDT', 'binance')
    bars = ctx['bars'].get(key, [])
    i = ctx['i']
    positions = ctx['positions']

    if not bars or i >= len(bars) or i < 50:
        return []

    closes = [b.close for b in bars]
    ema20 = ema(closes, 20)
    ema50 = ema(closes, 50)

    if ema20[i] is None or ema50[i] is None:
        return []

    actions = []
    has_position = key in positions

    if not has_position:
        # Entry: 3 consecutive higher lows above EMA20, breakout on 4th bar
        if bars[i].close <= ema50[i]:
            return []

        c1, c2, c3 = bars[i-3], bars[i-2], bars[i-1]
        current = bars[i]

        # Check for higher lows (accumulation)
        higher_lows = c2.low > c1.low and c3.low > c2.low
        if not higher_lows:
            return []

        # All 3 setup candles must have lows ABOVE EMA20
        if ema20[i-3] is None or ema20[i-2] is None or ema20[i-1] is None:
            return []
        above_ema20 = (c1.low > ema20[i-3] and
                      c2.low > ema20[i-2] and
                      c3.low > ema20[i-1])
        if not above_ema20:
            return []

        # Current candle must break above previous 3 highs (breakout)
        highest = max(c1.high, c2.high, c3.high)
        breakout = current.close > highest

        # Current should be bullish
        bullish = current.close > current.open

        if breakout and bullish:
            actions.append({
                'action': 'open_long',
                'symbol': 'ETHUSDT',
                'exchange': 'binance',
                'size': 1.0,
            })
    else:
        # Exit: close below EMA20
        if bars[i].close < ema20[i]:
            actions.append({
                'action': 'close_long',
                'symbol': 'ETHUSDT',
                'exchange': 'binance',
            })

    return actions