← Back to list

ema_cascade_breakout VALIDATED FAIL

Auto-discovered strategy

Symbol: BTC | Exchange: Bitfinex | Role: momentum

6/6
Profitable Years
+106.1%
Total Return
37.2%
Avg Win Rate
0.64
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +38.4% 34.5% 29 20.4% 0.95
2021 +6.3% 26.1% 23 24.5% 0.18
2022 +1.4% 40.0% 5 5.8% 0.16
2023 +5.1% 36.8% 19 11.4% 0.21
2024 +40.8% 36.0% 25 19.6% 1.29
2025 +14.0% 50.0% 10 8.4% 1.05

Performance Chart

Loading chart...

Walk-Forward Validation FAIL

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

AI Review

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

Source Code

"""
EMA Cascade Breakout Strategy
==============================
A momentum trend-following strategy using EMA cascade alignment
with volume-confirmed breakouts.

Exchange: bitfinex
Symbol: tBTCUSD
Role: momentum

Strategy Logic:
---------------
ENTRY (all conditions must be met):
1. Regime Filter: EMA50 > EMA200 (macro uptrend - golden cross)
2. EMA Cascade: EMA20 > EMA50 (momentum aligned)
3. Breakout: Price closes above 10-bar high
4. Volume: Current volume > 1.2x average (conviction)

EXIT (any condition triggers exit):
1. Price closes below 10-bar low (breakdown)
2. EMA20 < EMA50 (cascade breaks, momentum fading)
3. EMA50 < EMA200 (regime turns bearish)

Risk Management:
- Stop Loss: 5% (tight risk control)
- Take Profit: 15% (let winners run)

Key Design Principles:
- REGIME FILTER prevents trading in bear markets
- Uses RELATIVE indicators only (EMAs, N-bar high/low)
- ROUND parameter values (10, 20, 50, 200)
- Volume confirmation reduces false breakouts

Train Period Performance (2024-01-01 to 2025-06-30):
- 2024: +40.8%, 25 trades, 36% WR, Sharpe 1.29, Max DD 19.6%
- 2025 H1: +13.7%, 4 trades, 75% WR, Sharpe 1.65
- Total: +54.5%
"""

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


def init_strategy():
    """Initialize the EMA Cascade Breakout strategy."""
    return {
        'name': 'ema_cascade_breakout',
        'role': 'momentum',
        'warmup': 200,
        'role': 'momentum',  # Momentum strategy - allowed to lose bounded in bear
        'subscriptions': [
            {'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
        ],
        'parameters': {
            # EMA periods (all round numbers)
            'ema_fast': 20,
            'ema_slow': 50,
            'ema_trend': 200,
            # Breakout lookback
            'breakout_period': 10,
            # Volume multiplier
            'volume_mult': 1.2,
            # Risk management
            'stop_loss_pct': 5.0,
            'take_profit_pct': 15.0,
        }
    }


def process_time_step(ctx):
    """Process each time step and return trading actions."""
    key = ('tBTCUSD', 'bitfinex')
    bars = ctx['bars'][key]
    i = ctx['i']
    positions = ctx['positions']
    state = ctx['state']

    # Need enough history for EMA200
    if i < 200:
        return []

    # Pre-calculate indicators (cache in state for efficiency)
    if 'ema20' not in state or len(state['ema20']) != len(bars):
        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]
        state['ema20'] = ema(closes, 20)
        state['ema50'] = ema(closes, 50)
        state['ema200'] = ema(closes, 200)
        state['vol_sma'] = sma(volumes, 20)
        state['highs'] = highs
        state['lows'] = lows

    # Current indicator values
    e20 = state['ema20'][i]
    e50 = state['ema50'][i]
    e200 = state['ema200'][i]
    vol_avg = state['vol_sma'][i]

    bar = bars[i]
    actions = []

    # Recent high/low for breakout detection (10-bar lookback)
    lookback = 10
    recent_high = max(state['highs'][i-lookback:i])
    recent_low = min(state['lows'][i-lookback:i])

    if key not in positions:
        # === ENTRY LOGIC ===

        # REGIME FILTER (CRITICAL): Only trade in bull markets
        # EMA50 > EMA200 is the "golden cross" - standard trend filter
        # This prevents entering during bear markets
        regime_bullish = e50 > e200
        if not regime_bullish:
            return []  # Stay FLAT in downtrends

        # EMA CASCADE: Confirms momentum alignment at multiple timeframes
        # EMA20 > EMA50 shows short-term strength within the uptrend
        cascade_aligned = e20 > e50

        # BREAKOUT: Price closes above recent 10-bar high
        # This catches momentum continuation after consolidation
        breakout = bar.close > recent_high

        # VOLUME: Above-average volume confirms conviction
        # Breakouts without volume often fail
        volume_surge = bar.volume > vol_avg * 1.2

        if cascade_aligned and breakout and volume_surge:
            actions.append({
                'action': 'open_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
                'size': 1.0,
                'stop_loss_pct': 5.0,     # Tight stop for risk control
                'take_profit_pct': 15.0,  # Let winners run
            })
    else:
        # === EXIT LOGIC ===

        # Exit 1: Price breaks down below recent 10-bar low
        # Shows buyers have lost control
        breakdown = bar.close < recent_low

        # Exit 2: EMA cascade breaks (momentum fading)
        # EMA20 crossing below EMA50 is early warning
        cascade_broken = e20 < e50

        # Exit 3: Regime turns bearish (macro reversal)
        # EMA50 crossing below EMA200 is major trend change
        regime_bearish = e50 < e200

        if breakdown or cascade_broken or regime_bearish:
            actions.append({
                'action': 'close_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
            })

    return actions


# For testing
if __name__ == '__main__':
    from strategy import backtest_strategy
    print("Backtesting EMA Cascade Breakout strategy...")
    results, profitable, _ = backtest_strategy(init_strategy, process_time_step)