← Back to list

ema200_rising_cascade VALIDATED FAIL

Auto-discovered strategy

Symbol: BTC | Exchange: Bitfinex | Role: momentum

3/6
Profitable Years
+50.7%
Total Return
33.4%
Avg Win Rate
0.40
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +18.9% 40.0% 10 21.0% 0.68
2021 +0.0% 25.0% 12 15.5% 0.00
2022 -5.0% 0.0% 1 5.0% 0.00
2023 -3.6% 28.6% 7 14.4% -0.20
2024 +30.3% 57.1% 7 9.4% 1.22
2025 +10.0% 50.0% 2 5.0% 0.71

Performance Chart

Loading chart...

Walk-Forward Validation FAIL

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

AI Review

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

Source Code

"""
Strategy: ema200_rising_cascade
===============================
Ultra-conservative bull-only strategy with EMA200 rising filter.

Core Concept:
- Stay FLAT unless macro trend is VERY strong bull
- Only enter when perfect EMA cascade alignment
- Quick exits on any regime weakness

Entry Conditions (ALL must be true):
1. EMA50 > EMA200 (golden cross)
2. EMA200 rising 4%+ over 50 bars (strong bull momentum)
3. EMA50 rising 1%+ over 20 bars (momentum confirmation)
4. Price > EMA20 > EMA50 > EMA200 (perfect cascade)
5. Green candle (buying pressure)
6. Volume > 1.2x 20-bar average
7. Price within 10% of EMA50 (not overextended)

Exit Conditions (ANY triggers exit):
1. EMA50 < EMA200 (death cross)
2. EMA200 rise < 2% (bull momentum fading)
3. Price < EMA50 * 0.97 (3% below EMA50)

Risk Management:
- Stop Loss: 5%
- Take Profit: 15% (3:1 R/R)

Performance (Training Data 2024-2025H1):
- 2024: +30.3% | 7 trades | 57% WR | 9.4% DD
- 2025H1: +10.0% | 2 trades | 50% WR | 5.0% DD
- Total: +40.3% | 9.4% max drawdown

Symbol: tBTCUSD | Exchange: bitfinex | Timeframe: 4h
"""

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


def init_strategy():
    return {
        'name': 'ema200_rising_cascade',
        'role': 'momentum',
        'warmup': 220,
        'subscriptions': [
            {'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
        ],
        'parameters': {}
    }


def process_time_step(ctx):
    key = ('tBTCUSD', 'bitfinex')
    bars = ctx['bars'][key]
    i = ctx['i']
    positions = ctx['positions']

    # Need 220+ bars for EMA200 + lookback
    if i < 220:
        return []

    closes = [b.close for b in bars]
    volumes = [b.volume for b in bars]

    # Calculate EMAs
    ema20 = ema(closes, 20)
    ema50 = ema(closes, 50)
    ema200 = ema(closes, 200)

    # Safety check for None values
    if None in [ema20[i], ema50[i], ema200[i], ema200[i-50], ema50[i-20]]:
        return []

    # REGIME FILTERS (ultra conservative)
    # 1. Golden cross: EMA50 > EMA200
    golden_cross = ema50[i] > ema200[i]

    # 2. EMA200 rising strongly (4%+ over 50 bars)
    ema200_pct = (ema200[i] - ema200[i-50]) / ema200[i-50] * 100
    ema200_strong = ema200_pct > 4.0

    # 3. EMA50 also rising (momentum confirmation)
    ema50_pct = (ema50[i] - ema50[i-20]) / ema50[i-20] * 100
    ema50_rising = ema50_pct > 1.0

    # Combined regime check
    strong_bull = golden_cross and ema200_strong and ema50_rising

    price = bars[i].close

    actions = []

    if key not in positions:
        # STAY FLAT unless in perfect bull conditions
        if not strong_bull:
            return []

        # ENTRY CONDITIONS
        # Perfect EMA cascade alignment
        ema_cascade = price > ema20[i] > ema50[i] > ema200[i]

        # Green candle (buying pressure)
        green = bars[i].close > bars[i].open

        # Volume confirmation (above average)
        avg_vol = sum(volumes[i-20:i]) / 20
        vol_confirm = volumes[i] > avg_vol * 1.2

        # Not overextended (within 10% of EMA50)
        not_extended = (price - ema50[i]) / ema50[i] < 0.10

        # All conditions must be met
        if ema_cascade and green and vol_confirm and not_extended:
            actions.append({
                'action': 'open_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
                'size': 1.0,
                'stop_loss_pct': 5.0,    # Tight stop
                'take_profit_pct': 15.0,  # 3:1 R/R
            })
    else:
        # EXIT CONDITIONS (quick exit on any weakness)
        cascade_break = ema50[i] < ema200[i]
        ema200_weakening = ema200_pct < 2.0  # Bull momentum fading
        price_weak = price < ema50[i] * 0.97  # 3% below EMA50

        if cascade_break or ema200_weakening or price_weak:
            actions.append({
                'action': 'close_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
            })

    return actions


# For backwards compatibility with old rule format
def create_rule(bars):
    """Create entry/exit functions for this rule."""
    closes = [b.close for b in bars]
    volumes = [b.volume for b in bars]
    ema20 = ema(closes, 20)
    ema50 = ema(closes, 50)
    ema200 = ema(closes, 200)

    def entry_long(bars, i):
        if i < 220:
            return False

        # Recalculate for current window
        closes = [b.close for b in bars]
        volumes = [b.volume for b in bars]
        ema20 = ema(closes, 20)
        ema50 = ema(closes, 50)
        ema200 = ema(closes, 200)

        if None in [ema20[i], ema50[i], ema200[i], ema200[i-50], ema50[i-20]]:
            return False

        # Regime
        golden_cross = ema50[i] > ema200[i]
        ema200_pct = (ema200[i] - ema200[i-50]) / ema200[i-50] * 100
        ema200_strong = ema200_pct > 4.0
        ema50_pct = (ema50[i] - ema50[i-20]) / ema50[i-20] * 100
        ema50_rising = ema50_pct > 1.0

        if not (golden_cross and ema200_strong and ema50_rising):
            return False

        price = bars[i].close
        ema_cascade = price > ema20[i] > ema50[i] > ema200[i]
        green = bars[i].close > bars[i].open
        avg_vol = sum(volumes[i-20:i]) / 20
        vol_confirm = volumes[i] > avg_vol * 1.2
        not_extended = (price - ema50[i]) / ema50[i] < 0.10

        return ema_cascade and green and vol_confirm and not_extended

    def exit_long(bars, i):
        if i < 220:
            return False

        closes = [b.close for b in bars]
        ema50 = ema(closes, 50)
        ema200 = ema(closes, 200)

        if None in [ema50[i], ema200[i], ema200[i-50]]:
            return False

        ema200_pct = (ema200[i] - ema200[i-50]) / ema200[i-50] * 100
        price = bars[i].close

        cascade_break = ema50[i] < ema200[i]
        ema200_weakening = ema200_pct < 2.0
        price_weak = price < ema50[i] * 0.97

        return cascade_break or ema200_weakening or price_weak

    return {
        'entry_long': entry_long,
        'exit_long': exit_long,
        'symbol': 'tBTCUSD',
        'exchange': 'bitfinex',
        'stop_loss_pct': 5.0,
        'take_profit_pct': 15.0,
    }


ENTRY_LOGIC = """
# Regime Filter (ALL required):
1. EMA50 > EMA200 (golden cross)
2. EMA200 rising 4%+ over 50 bars
3. EMA50 rising 1%+ over 20 bars

# Entry Signal (ALL required):
1. Price > EMA20 > EMA50 > EMA200 (cascade)
2. Green candle
3. Volume > 1.2x average
4. Price within 10% of EMA50
"""

EXIT_LOGIC = """
# Exit on ANY:
1. EMA50 < EMA200 (death cross)
2. EMA200 rise < 2% (momentum fading)
3. Price < EMA50 * 0.97 (3% below support)
"""