← Back to list

eth_ema_momentum_breakout VALIDATED PASS

Auto-discovered strategy

Symbol: ETH | Exchange: Binance | Role: momentum

5/6
Profitable Years
+127.1%
Total Return
44.9%
Avg Win Rate
0.49
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +53.3% 46.2% 52 20.7% 1.18
2021 +43.1% 52.8% 36 19.3% 1.25
2022 +0.3% 46.2% 13 17.2% 0.01
2023 -24.9% 34.6% 26 28.4% -1.44
2024 +16.8% 44.0% 25 29.3% 0.59
2025 +38.7% 45.8% 24 19.6% 1.35

Performance Chart

Loading chart...

Walk-Forward Validation PASS

1/1 Windows Profitable
-4.0% OOS Return
0.00 Median Sharpe
0.000 Score
Window Train Period Val Period Val Return Val Test Period Test Return Status
WF-1 2024-01→2025-06 2025-07→2025-12 -4.0% OK 2026-01→ongoing +0.0% PASS

AI Review

Not yet reviewed. Run: ./review_strategy.sh eth_ema_momentum_breakout

Source Code

"""
ETH EMA Momentum Breakout Strategy
===================================

Momentum continuation strategy for ETHUSDT that trades breakouts
in confirmed uptrends with multiple confirmation signals.

CONCEPT:
In strong bull markets (EMA50 > EMA200), when price shows momentum
continuation through consecutive higher closes AND breaks above
recent highs with good volume, this signals trend continuation.

ENTRY CONDITIONS (8 total):
1. Bull regime: EMA50 > EMA200 (macro trend filter)
2. EMA alignment: EMA20 > EMA50 (short-term trend strong)
3. Price above EMA20 (momentum intact)
4. Momentum: 2 consecutive higher closes
5. Breakout: Close above 5-bar high
6. Bar quality: Range > 0.7x ATR (not a doji)
7. Volume: Above 90% of 20-bar average
8. Bullish bar: Close > Open
9. EMA50 rising (trend confirmation)

EXIT CONDITIONS:
1. Close below EMA50 (trend weakening)
2. EMA20 crosses below EMA50 (bearish cross)
3. Time exit: 20 bars maximum hold
4. Stop loss: 5%
5. Take profit: 9%

TRAIN RESULTS (2024-01 to 2025-06):
  2024:   +16.8% | ~24 trades | Sharpe ~0.6
  2025H1: +13.4% | ~8 trades  | Sharpe ~0.7
  Total:  +30.1%

ROBUSTNESS FEATURES:
- Uses only relative indicators (EMAs, ATR, volume ratios)
- Round parameters: 5, 20, 50, 200, 5%, 9%
- Bull-market filter prevents trades in downtrends
- Multiple confirmation signals reduce false breakouts
- Balanced TP/SL for consistent risk/reward

Symbol: ETHUSDT
Exchange: binance
Timeframe: 4h
Role: momentum
"""

import sys
sys.path.insert(0, '/root/trade_rules')
from lib import ema, sma, atr


def init_strategy():
    return {
        'name': 'eth_ema_momentum_breakout',
        'role': 'momentum',
        'warmup': 200,  # EMA200 needs 200 bars
        'subscriptions': [
            {'symbol': 'ETHUSDT', 'exchange': 'binance', 'timeframe': '4h'},
        ],
        'parameters': {}
    }


def process_time_step(ctx):
    """
    Process each time step and return trading actions.

    Entry Logic:
    - Bull regime (EMA50 > EMA200)
    - EMA cascade alignment (EMA20 > EMA50)
    - Price above EMA20
    - Momentum: 2 consecutive higher closes
    - Breakout above 5-bar high
    - Bar quality check (range > 0.7x ATR)
    - Volume confirmation
    - Bullish close
    - EMA50 rising

    Exit Logic:
    - Close below EMA50
    - EMA20 crosses below EMA50
    - Time exit (20 bars)
    - Stop loss 5%, Take profit 9%
    """
    key = ('ETHUSDT', 'binance')
    bars = ctx['bars'].get(key, [])
    i = ctx['i']
    positions = ctx['positions']

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

    closes = [b.close for b in bars]
    highs = [b.high for b in bars]
    lows = [b.low for b in bars]
    volumes = [b.volume for b in bars]

    # Calculate indicators
    ema20 = ema(closes, 20)
    ema50 = ema(closes, 50)
    ema200 = ema(closes, 200)
    atr_vals = atr(highs, lows, closes, 20)
    vol_ma = sma(volumes, 20)

    # Ensure all indicators are valid
    if any(x[i] is None for x in [ema20, ema50, ema200, atr_vals, vol_ma]):
        return []

    actions = []
    has_position = key in positions

    if not has_position:
        # ==================== ENTRY CONDITIONS ====================

        # 1. REGIME: Bull market (EMA50 > EMA200)
        bull_regime = ema50[i] > ema200[i]
        if not bull_regime:
            return []

        # 2. EMA ALIGNMENT: EMA20 > EMA50
        ema_aligned = ema20[i] > ema50[i]
        if not ema_aligned:
            return []

        # 3. PRICE ABOVE EMA20
        above_ema = closes[i] > ema20[i]
        if not above_ema:
            return []

        # 4. MOMENTUM: 2 consecutive higher closes
        if i < 2:
            return []
        momentum = closes[i] > closes[i-1] > closes[i-2]
        if not momentum:
            return []

        # 5. BREAKOUT: Close above 5-bar high
        high_5 = max(highs[max(i-5, 0):i])
        breakout = closes[i] > high_5
        if not breakout:
            return []

        # 6. BAR QUALITY: Range > 0.7x ATR (not a doji)
        bar_range = highs[i] - lows[i]
        decent_bar = bar_range > atr_vals[i] * 0.7
        if not decent_bar:
            return []

        # 7. VOLUME: Above 90% of 20-bar average
        vol_ok = volumes[i] > vol_ma[i] * 0.9
        if not vol_ok:
            return []

        # 8. BULLISH BAR: Close > Open
        bullish = bars[i].close > bars[i].open
        if not bullish:
            return []

        # 9. EMA50 RISING (over 10 bars)
        ema50_rising = ema50[i] > ema50[i-10] if i >= 10 else False
        if not ema50_rising:
            return []

        # All conditions met - ENTER LONG
        actions.append({
            'action': 'open_long',
            'symbol': 'ETHUSDT',
            'exchange': 'binance',
            'size': 1.0,
            'stop_loss_pct': 5.0,   # 5% stop loss
            'take_profit_pct': 9.0, # 9% take profit
        })
    else:
        # ==================== EXIT CONDITIONS ====================
        pos = positions[key]
        bars_held = i - pos.entry_bar

        # 1. Close below EMA50 (trend weakening)
        below_ema50 = closes[i] < ema50[i]

        # 2. EMA20 crosses below EMA50 (bearish cross)
        bearish_cross = ema20[i] < ema50[i]

        # 3. Time exit: 20 bars maximum hold
        time_exit = bars_held >= 20

        if below_ema50 or bearish_cross or time_exit:
            actions.append({
                'action': 'close_long',
                'symbol': 'ETHUSDT',
                'exchange': 'binance',
            })

    return actions


# Allow direct execution for testing
if __name__ == "__main__":
    from strategy import backtest_strategy
    results, profitable, _ = backtest_strategy(init_strategy, process_time_step, verbose=True)