← Back to list

eth_sr_ema_bounce VALIDATED FAIL

Auto-discovered strategy

Symbol: ETH | Exchange: Binance | Role: momentum

5/6
Profitable Years
+158.5%
Total Return
27.2%
Avg Win Rate
0.60
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +48.5% 28.1% 64 17.0% 1.12
2021 +57.2% 29.6% 71 31.2% 1.24
2022 +13.8% 22.7% 66 29.8% 0.34
2023 -9.2% 27.9% 68 28.0% -0.30
2024 +23.7% 28.6% 56 28.3% 0.64
2025 +24.6% 26.0% 73 29.9% 0.57

Performance Chart

Loading chart...

Walk-Forward Validation FAIL

0/1 Windows Profitable
-23.1% 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 -23.1% FAIL 2026-01→ongoing +0.0% FAIL

AI Review

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

Source Code

"""
ETH Support/Resistance EMA Bounce Strategy
===========================================

A dual-mode strategy that adapts to market regime by going long on EMA50
support bounces in uptrends and short on EMA50 resistance rejections
in downtrends.

Strategy Concept:
-----------------
The EMA50 acts as a dynamic support/resistance level. In uptrends,
pullbacks to EMA50 are buying opportunities. In downtrends, rallies to
EMA50 are selling opportunities. This strategy captures these bounces
with strict regime filtering.

LONG ENTRIES (Bull Markets - EMA50 > EMA200):
- Low touched EMA50 zone (within 3%)
- Close bounced above EMA50
- Bullish candle (close > open)
- Volume above 20-bar average

SHORT ENTRIES (Bear Markets - EMA50 < EMA200):
- High touched EMA50 zone (within 3%)
- Close rejected below EMA50
- Bearish candle (close < open)
- Volume above 20-bar average

EXITS:
- 2 consecutive closes on wrong side of EMA50
- Stop loss: 3%
- Take profit: 10%

Risk Management:
----------------
- Regime filter prevents fighting the trend
- 3% stop loss limits downside per trade
- 10% take profit captures trend continuation moves
- Volume confirmation reduces false signals

Expected Behavior:
------------------
- Performs well in trending markets (both up and down)
- Will have losing trades in choppy/ranging conditions
- Win rate ~30%, relies on winners (10%) being larger than losers (3%)
- Approximately 2:1 short to long ratio in mixed conditions

Training Period Performance (2024-01-01 to 2025-06-30):
- 2024: +23.7% | 56 trades | 29% WR
- 2025 (partial): +27.4% | 35 trades | 29% WR
- Total: +51.1% | 101 trades | Sharpe 0.95 | Max DD 28.3%
"""

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


def init_strategy():
    """Initialize the strategy configuration."""
    return {
        'name': 'eth_sr_ema_bounce',
        'role': 'momentum',  # Can lose bounded in bear markets
        'warmup': 200,
        'subscriptions': [
            {'symbol': 'ETHUSDT', 'exchange': 'binance', 'timeframe': '4h'},
        ],
        'parameters': {}
    }


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

    Args:
        ctx: Context dict containing bars, positions, state, etc.

    Returns:
        List of action dicts (open_long, close_long, open_short, close_short)
    """
    key = ('ETHUSDT', 'binance')
    bars = ctx['bars'][key]
    i = ctx['i']
    positions = ctx['positions']

    # Build price/volume arrays up to current bar
    closes = [b.close for b in bars[:i+1]]
    lows = [b.low for b in bars[:i+1]]
    highs = [b.high for b in bars[:i+1]]
    opens = [b.open for b in bars[:i+1]]
    volumes = [b.volume for b in bars[:i+1]]

    # Calculate EMAs for regime detection
    ema50 = ema(closes, 50)
    ema200 = ema(closes, 200)

    actions = []

    if key not in positions:
        # Check indicator availability
        if ema50[i] is None or ema200[i] is None:
            return []

        # Determine market regime
        is_bull = ema50[i] > ema200[i]
        is_bear = ema50[i] < ema200[i]

        # Volume baseline
        avg_vol = sum(volumes[i-20:i]) / 20

        if is_bull:
            # LONG ENTRY: Pullback to EMA50 with bounce

            # Condition 1: Low touched EMA50 zone (within 3%)
            if lows[i] > ema50[i] * 1.03:
                return []

            # Condition 2: Close bounced above EMA50
            if closes[i] <= ema50[i]:
                return []

            # Condition 3: Bullish candle
            if closes[i] <= opens[i]:
                return []

            # Condition 4: Volume confirmation
            if volumes[i] < avg_vol:
                return []

            # All conditions met - open long
            actions.append({
                'action': 'open_long',
                'symbol': 'ETHUSDT',
                'exchange': 'binance',
                'size': 1.0,
                'stop_loss_pct': 3.0,
                'take_profit_pct': 10.0,
            })

        elif is_bear:
            # SHORT ENTRY: Rally to EMA50 with rejection

            # Condition 1: High touched EMA50 zone (within 3%)
            if highs[i] < ema50[i] * 0.97:
                return []

            # Condition 2: Close rejected below EMA50
            if closes[i] >= ema50[i]:
                return []

            # Condition 3: Bearish candle
            if closes[i] >= opens[i]:
                return []

            # Condition 4: Volume confirmation
            if volumes[i] < avg_vol:
                return []

            # All conditions met - open short
            actions.append({
                'action': 'open_short',
                'symbol': 'ETHUSDT',
                'exchange': 'binance',
                'size': 1.0,
                'stop_loss_pct': 3.0,
                'take_profit_pct': 10.0,
            })

    else:
        # EXIT LOGIC
        pos = positions[key]
        bars_held = i - pos.entry_bar

        should_exit = False

        if pos.side == 'long':
            # Exit long: 2 consecutive closes below EMA50
            if bars_held >= 2 and closes[i] < ema50[i] and closes[i-1] < ema50[i-1]:
                should_exit = True
        else:
            # Exit short: 2 consecutive closes above EMA50
            if bars_held >= 2 and closes[i] > ema50[i] and closes[i-1] > ema50[i-1]:
                should_exit = True

        if should_exit:
            action_type = 'close_long' if pos.side == 'long' else 'close_short'
            actions.append({
                'action': action_type,
                'symbol': 'ETHUSDT',
                'exchange': 'binance',
            })

    return actions


if __name__ == '__main__':
    # Test the strategy
    from strategy import backtest_strategy
    results, profitable, _ = backtest_strategy(init_strategy, process_time_step)