← Back to list

sol_rapid_fade_rsi VALIDATED PASS

Auto-discovered strategy

Symbol: SOL | Exchange: Binance | Role: mean_reversion

5/6
Profitable Years
+228.8%
Total Return
56.7%
Avg Win Rate
1.44
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +46.3% 62.5% 24 7.8% 2.04
2021 +78.6% 66.7% 33 12.3% 2.95
2022 +71.1% 58.1% 43 9.0% 2.36
2023 -5.8% 43.3% 30 21.9% -0.27
2024 +17.3% 51.5% 33 24.2% 0.67
2025 +21.3% 58.1% 31 12.2% 0.91

Performance Chart

Loading chart...

Walk-Forward Validation PASS

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

AI Review

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

Source Code

"""
SOL Rapid Fade RSI - Mean Reversion Strategy
=============================================

A mean reversion strategy for SOLUSDT that fades rapid price drops when RSI
becomes oversold. Uses a RELAXED chop filter (15% EMA spread) to ensure
sufficient trade opportunities while still avoiding extreme trending conditions.

Strategy Logic:
- REGIME FILTER: Only trade when EMA50/EMA200 spread < 15% (relaxed)
- ENTRY: Fade rapid drops when RSI was oversold in last 4 bars
  - RSI was < 35 in last 4 bars (including current)
  - RSI now turning up (current > previous)
  - RSI still < 45 (room for mean reversion)
  - 4-bar price drop > 4% (rapid move occurred)
- EXIT: Mean reversion targets
  - RSI > 55 (back to neutral)
  - Profit > 3% AND RSI > 45 (lock in gains)
  - 12-bar time stop (quick mean reversion)
- RISK: 4% stop loss, 6% take profit

Role: mean_reversion
- Designed for ranging/choppy markets
- Validation allows up to -8% loss, 25% drawdown

Universal Principles (no overfitting):
- Uses round parameter values (14 RSI, 50/200 EMAs, 15% threshold)
- No specific price levels or dates referenced
- Based on universal principle: RSI mean reversion after sharp drops

Train Performance:
- 2024: +17.3% | 33 trades | 52% WR | 0.67 Sharpe | 24.2% DD
- 2025H1: +14.7% | 15 trades | 60% WR | 0.86 Sharpe | 11.5% DD
- Total: +32.0% | 48 trades
"""

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

# Module-level indicator cache
_indicators = {}


def init_strategy():
    """Initialize strategy configuration."""
    _indicators.clear()
    return {
        'name': 'sol_rapid_fade_rsi',
        'role': 'mean_reversion',  # Sets validation gates
        'warmup': 200,
        'subscriptions': [
            {'symbol': 'SOLUSDT', 'exchange': 'binance', 'timeframe': '4h'},
        ],
        'parameters': {
            'rsi_period': 14,
            'rsi_oversold': 35,
            'rsi_neutral': 55,
            'rsi_room': 45,
            'ema_short': 50,
            'ema_long': 200,
            'chop_threshold': 15,  # Relaxed from 5% (SOL more volatile)
            'drop_lookback': 4,
            'drop_threshold': 4,  # % drop in lookback period
            'stop_loss_pct': 4,
            'take_profit_pct': 6,
            'profit_lock': 3,
            'time_exit_bars': 12,
        }
    }


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

    Entry Logic:
    1. EMA50/200 spread < 15% (relaxed chop filter for SOL volatility)
    2. RSI was < 35 in last 4 bars (oversold after rapid move)
    3. RSI now turning up (current > previous)
    4. RSI still < 45 (room for mean reversion)
    5. 4-bar price drop > 4% (confirms rapid move occurred)

    Exit Logic:
    1. RSI > 55 (mean reverted to neutral)
    2. Profit > 3% AND RSI > 45 (lock in gains early)
    3. Held >= 12 bars (time exit - mean reversion should be quick)
    """
    key = ('SOLUSDT', 'binance')
    bars = ctx['bars'][key]
    i = ctx['i']
    positions = ctx['positions']

    # Need enough bars for 200 EMA - now handled by framework via 'warmup' field
    # if i < 200:
    #     return []

    # Compute indicators once per backtest run
    if key not in _indicators:
        closes = [b.close for b in bars]
        _indicators[key] = {
            'ema50': ema(closes, 50),
            'ema200': ema(closes, 200),
            'rsi': rsi(closes, 14),
        }

    ind = _indicators[key]
    ema50 = ind['ema50']
    ema200 = ind['ema200']
    rsi_vals = ind['rsi']

    # Safety check for indicator availability
    if i >= len(ema50) or ema50[i] is None or ema200[i] is None or rsi_vals[i] is None:
        return []

    actions = []
    curr = bars[i]
    price = curr.close

    # === RELAXED REGIME FILTER ===
    # Allow trades when EMA spread < 15% (relaxed for SOL's higher volatility)
    ema_spread = abs(ema50[i] - ema200[i]) / ema200[i] * 100
    in_range = ema_spread < 15

    curr_rsi = rsi_vals[i]
    prev_rsi = rsi_vals[i-1] if rsi_vals[i-1] is not None else 50

    if key not in positions:
        # === ENTRY: FADE RAPID DROP ===

        # Check if RSI was oversold in last 4 bars (including current)
        was_oversold = any(
            rsi_vals[j] is not None and rsi_vals[j] < 35
            for j in range(max(0, i-4), i+1)
        )

        # RSI now turning up (confirmation of bounce)
        rsi_turn = curr_rsi > prev_rsi

        # Room to run - RSI still below neutral
        room_to_run = curr_rsi < 45

        # Confirm rapid move occurred (4-bar drop > 4%)
        price_4bar = bars[i-4].close
        change_4bar = (price - price_4bar) / price_4bar * 100
        had_drop = change_4bar < -4

        if in_range and was_oversold and rsi_turn and room_to_run and had_drop:
            actions.append({
                'action': 'open_long',
                'symbol': 'SOLUSDT',
                'exchange': 'binance',
                'size': 1.0,
                'stop_loss_pct': 4,
                'take_profit_pct': 6,
            })

    else:
        # === EXIT CONDITIONS ===
        pos = positions[key]
        bars_held = i - pos.entry_bar
        current_pnl = (price - pos.entry_price) / pos.entry_price * 100

        # 1. RSI back to neutral - mean reversion complete
        rsi_normal = curr_rsi > 55

        # 2. Lock in decent profit when RSI recovering
        decent_profit = current_pnl > 3 and curr_rsi > 45

        # 3. Time exit - mean reversion should be quick
        time_exit = bars_held >= 12

        if rsi_normal or decent_profit or time_exit:
            actions.append({
                'action': 'close_long',
                'symbol': 'SOLUSDT',
                'exchange': 'binance',
            })

    return actions


# Entry/Exit logic documentation for database
ENTRY_LOGIC = """
REGIME FILTER (RELAXED for SOL):
  - EMA50/EMA200 spread < 15% (not extreme trending)

ENTRY CONDITIONS (ALL must be true):
  - RSI(14) was < 35 in last 4 bars (oversold after rapid move)
  - RSI now turning up (current > previous)
  - RSI still < 45 (room for mean reversion)
  - 4-bar price drop > 4% (confirms rapid move)
"""

EXIT_LOGIC = """
EXIT CONDITIONS (ANY triggers exit):
  1. RSI > 55 (mean reverted to neutral)
  2. Profit > 3% AND RSI > 45 (lock in gains)
  3. Held >= 12 bars (time exit)

STOPS:
  - Stop loss: 4%
  - Take profit: 6%
"""