← Back to list

eth_ema_stack_momentum VALIDATED PASS

Auto-discovered strategy

Symbol: ETH | Exchange: Bitfinex | Role: momentum

4/6
Profitable Years
+38.0%
Total Return
43.0%
Avg Win Rate
-0.15
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +15.8% 44.0% 25 14.4% 0.57
2021 +9.4% 47.1% 17 8.7% 0.42
2022 -24.8% 14.3% 7 22.6% -4.19
2023 -13.7% 38.1% 21 19.2% -0.82
2024 +19.0% 56.2% 16 12.1% 1.04
2025 +32.4% 58.3% 12 5.8% 2.11

Performance Chart

Loading chart...

Walk-Forward Validation PASS

1/1 Windows Profitable
+9.6% 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 +9.6% OK 2026-01→ongoing +0.0% PASS

AI Review

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

Source Code

"""
ETH EMA Stack Momentum Strategy
===============================

A momentum strategy for tETHUSD that enters when price shows strong
trend confirmation across multiple EMA timeframes with rising slopes.

CONCEPT:
When all EMAs (20, 50, 200) are in a "stacked" bullish configuration
AND all are rising, this indicates strong institutional conviction.
We enter when price shows momentum (consecutive higher closes) within
a healthy distance zone from the trend (1.5-8% above EMA50).

This differs from other EMA strategies by focusing on:
1. SLOPE AGREEMENT: All 3 major EMAs must be rising (not just aligned)
2. DISTANCE ZONE: Price must be in a "goldilocks" zone from EMA50
3. MOMENTUM CONFIRMATION: Consecutive higher closes required

ENTRY CONDITIONS:
1. Full EMA cascade: Price > EMA20 > EMA50 > EMA200
2. All EMAs rising:
   - EMA20 higher than 5 bars ago
   - EMA50 higher than 10 bars ago
   - EMA200 higher than 20 bars ago
3. Price momentum: 2 consecutive higher closes
4. Distance zone: 1.5-8% above EMA50 (not too close, not extended)
5. Bullish bar: Close > Open, close in upper 50% of range
6. Bar quality: Range > 0.5x ATR(20)
7. Volume: Above 20-bar average

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

TRAIN RESULTS (2024-01 to 2025-06):
  2024:   +19.0% | 16 trades | 56% WR | 8.8% max DD
  2025H1: +2.5%  |  6 trades | 33% WR | 5.8% max DD
  Total:  +21.5%

ROBUSTNESS FEATURES:
- Uses only relative indicators (EMAs, ATR, volume ratios)
- Round parameters: 5, 10, 15, 20, 50, 200
- All EMAs must be rising - strong trend confirmation
- Distance zone prevents chasing extended moves
- Momentum confirmation reduces false signals

Symbol: tETHUSD
Exchange: bitfinex
Timeframe: 4h
Role: momentum
"""

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


def init_strategy():
    """Initialize the EMA Stack Momentum strategy."""
    return {
        'name': 'eth_ema_stack_momentum',
        'role': 'momentum',
        'warmup': 200,  # EMA200 needs 200 bars
        'subscriptions': [
            {'symbol': 'tETHUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
        ],
        'parameters': {}
    }


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

    Entry Logic:
    - Full EMA cascade alignment (Price > EMA20 > EMA50 > EMA200)
    - All EMAs rising (slope check)
    - Price momentum (consecutive higher closes)
    - Distance zone (1.5-8% above EMA50)
    - Bullish bar confirmation
    - Volume above average

    Exit Logic:
    - Close below EMA50
    - EMA20 < EMA50 crossover
    - Time exit (15 bars)
    - Stop loss 5%, Take profit 8%
    """
    key = ('tETHUSD', 'bitfinex')
    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)
    vol_ma = sma(volumes, 20)
    atr_vals = atr(highs, lows, closes, 20)

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

    if i < 20:
        return []

    actions = []
    has_position = key in positions

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

        # 1. FULL EMA CASCADE: Price > EMA20 > EMA50 > EMA200
        if not (closes[i] > ema20[i] > ema50[i] > ema200[i]):
            return []

        # 2. ALL EMAs RISING (slope confirmation)
        ema20_rising = ema20[i] > ema20[i-5] if ema20[i-5] else False
        ema50_rising = ema50[i] > ema50[i-10] if ema50[i-10] else False
        ema200_rising = ema200[i] > ema200[i-20] if ema200[i-20] else False

        if not (ema20_rising and ema50_rising and ema200_rising):
            return []

        # 3. MOMENTUM: 2 consecutive higher closes
        if closes[i] <= closes[i-1] or closes[i-1] <= closes[i-2]:
            return []

        # 4. DISTANCE ZONE: 1.5-8% above EMA50
        dist_ema50 = (closes[i] - ema50[i]) / ema50[i] * 100
        if dist_ema50 < 1.5 or dist_ema50 > 8.0:
            return []

        # 5. BULLISH BAR: Close > Open and in upper 50% of range
        if bars[i].close <= bars[i].open:
            return []
        bar_range = highs[i] - lows[i]
        if bar_range > 0:
            close_pos = (closes[i] - lows[i]) / bar_range
            if close_pos < 0.5:
                return []

        # 6. BAR QUALITY: Range > 0.5x ATR
        if bar_range < atr_vals[i] * 0.5:
            return []

        # 7. VOLUME: Above 20-bar average
        if volumes[i] < vol_ma[i]:
            return []

        # All conditions met - ENTER LONG
        actions.append({
            'action': 'open_long',
            'symbol': 'tETHUSD',
            'exchange': 'bitfinex',
            'size': 1.0,
            'stop_loss_pct': 5.0,   # 5% stop loss
            'take_profit_pct': 8.0,  # 8% 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)
        ema_breakdown = ema20[i] < ema50[i]

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

        if below_ema50 or ema_breakdown or time_exit:
            actions.append({
                'action': 'close_long',
                'symbol': 'tETHUSD',
                'exchange': 'bitfinex',
            })

    return actions


# Allow direct execution for testing
if __name__ == "__main__":
    from strategy import backtest_strategy
    print("Testing ETH EMA Stack Momentum Strategy...")
    results, profitable, _ = backtest_strategy(init_strategy, process_time_step, verbose=True)