← Back to list

support_resistance_bounce VALIDATED PASS

Auto-discovered strategy

Symbol: BTC | Exchange: Binance | Role: momentum

2/6
Profitable Years
+136.6%
Total Return
30.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 +132.5% 50.0% 28 22.5% 2.89
2021 -4.0% 25.0% 28 26.5% -0.10
2022 -16.4% 22.7% 22 18.2% -0.60
2023 -0.9% 26.1% 23 18.9% -0.03
2024 +27.1% 36.4% 22 28.2% 0.83
2025 -1.8% 25.0% 28 18.5% -0.06

Performance Chart

Loading chart...

Walk-Forward Validation PASS

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

AI Review

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

Source Code

"""
Support Resistance Bounce Strategy
====================================

A dual long/short support and resistance bounce strategy for BTCUSDT.

LONG ENTRIES (Bull Markets):
- EMA cascade aligned: EMA20 > EMA50 > EMA200
- EMA50 rising over 5 bars
- Price above EMA50
- Price tested swing low support (within 5%) in last 5 bars
- Price bounced above support
- Breakout above 10-bar high
- Bullish candle with >50% body
- Volume >1.2x 20-bar average

SHORT ENTRIES (Bear Markets):
- EMA50 < EMA200 (bear market confirmed)
- Price below EMA200 (strong downtrend)
- EMA50 falling over 10 bars
- Price tested swing high resistance (within 5%) in last 5 bars
- Price rejected from resistance
- Breakdown below 10-bar low
- Bearish candle with >50% body
- Volume >1.2x 20-bar average

EXITS:
- Long: Close below EMA50 OR EMA20 < EMA50
- Short: Close above EMA50 OR EMA20 > EMA50
- Stop loss: 5%
- Take profit: 15%

Training Performance (2024-2025H1):
- 2024: +27.1% | 22 trades | 36% WR
- 2025H1: +0.2% | 13 trades | 23% WR
- Total: +27.3%

Validation Performance (2025H2):
- Return: -4.1% | Sharpe: -0.23 | PASS
"""

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


def init_strategy():
    return {
        'name': 'support_resistance_bounce',
        'role': 'momentum',  # Allows bounded loss in bear markets
        'warmup': 200,
        'subscriptions': [
            {'symbol': 'BTCUSDT', 'exchange': 'binance', 'timeframe': '4h'},
        ],
        'parameters': {}
    }


def process_time_step(ctx):
    """
    Support/Resistance Bounce Strategy

    Goes long on support bounces in uptrends.
    Goes short on resistance rejections in downtrends.
    """
    key = ('BTCUSDT', 'binance')
    bars = ctx['bars'][key]
    i = ctx['i']
    positions = ctx['positions']

    # Extract price data
    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
    ema20 = ema(closes, 20)
    ema50 = ema(closes, 50)
    ema200 = ema(closes, 200)

    actions = []

    if key not in positions:
        # Check EMAs are available
        if any(e[i] is None for e in [ema20, ema50, ema200]):
            return []

        # === LONG: Support bounce in uptrend ===
        if ema20[i] > ema50[i] > ema200[i]:
            # Check EMA50 is rising
            ema_rising = True
            if i >= 5 and ema50[i-5] is not None:
                ema_rising = ema50[i] > ema50[i-5]

            if ema_rising and closes[i] >= ema50[i]:
                # Find swing low (support)
                swing_low = min(lows[max(0, i-20):i])
                support_zone = swing_low * 1.05

                # Check support was tested in last 5 bars
                tested = any(lows[j] <= support_zone for j in range(i-5, i+1))

                if tested and closes[i] > swing_low * 1.02:
                    # Check for breakout above 10-bar high
                    recent_high = max(highs[max(0, i-10):i])

                    if closes[i] > recent_high:
                        # Check for bullish candle with strong body
                        is_bullish = closes[i] > opens[i]
                        bar_range = highs[i] - lows[i]
                        body_ratio = (closes[i] - opens[i]) / bar_range if bar_range > 0 else 0

                        # Check volume
                        avg_vol = sum(volumes[i-20:i]) / 20

                        if is_bullish and body_ratio > 0.5 and volumes[i] > avg_vol * 1.2:
                            actions.append({
                                'action': 'open_long',
                                'symbol': 'BTCUSDT',
                                'exchange': 'binance',
                                'size': 1.0,
                                'stop_loss_pct': 5.0,
                                'take_profit_pct': 15.0,
                            })
                            return actions

        # === SHORT: Resistance rejection in downtrend ===
        # Only in confirmed downtrends (EMA50 < EMA200)
        if ema50[i] < ema200[i]:
            # Price must be below EMA200 (confirmed downtrend)
            if closes[i] > ema200[i]:
                return actions

            # EMA50 must be falling
            if i >= 10:
                if ema50[i-10] is not None and ema50[i] >= ema50[i-10]:
                    return actions

            # Price must be below EMA50
            if closes[i] > ema50[i]:
                return actions

            # Find swing high (resistance)
            swing_high = max(highs[max(0, i-20):i])
            resistance_zone = swing_high * 0.95

            # Check resistance was tested in last 5 bars
            tested = any(highs[j] >= resistance_zone for j in range(i-5, i+1))

            if tested and closes[i] < swing_high * 0.98:
                # Check for breakdown below 10-bar low
                recent_low = min(lows[max(0, i-10):i])

                if closes[i] < recent_low:
                    # Check for bearish candle with strong body
                    is_bearish = closes[i] < opens[i]
                    bar_range = highs[i] - lows[i]
                    body_ratio = (opens[i] - closes[i]) / bar_range if bar_range > 0 else 0

                    # Check volume
                    avg_vol = sum(volumes[i-20:i]) / 20

                    if is_bearish and body_ratio > 0.5 and volumes[i] > avg_vol * 1.2:
                        actions.append({
                            'action': 'open_short',
                            'symbol': 'BTCUSDT',
                            'exchange': 'binance',
                            'size': 1.0,
                            'stop_loss_pct': 5.0,
                            'take_profit_pct': 15.0,
                        })
                        return actions

    else:
        # === EXIT LOGIC ===
        pos = positions[key]

        if pos.side == 'long':
            # Exit long on close below EMA50 or EMA breakdown
            if closes[i] < ema50[i] or ema20[i] < ema50[i]:
                actions.append({
                    'action': 'close_long',
                    'symbol': 'BTCUSDT',
                    'exchange': 'binance',
                })
        else:
            # Exit short on close above EMA50 or EMA recovery
            if closes[i] > ema50[i] or ema20[i] > ema50[i]:
                actions.append({
                    'action': 'close_short',
                    'symbol': 'BTCUSDT',
                    'exchange': 'binance',
                })

    return actions