← Back to list

doge_ema_cascade_pullback VALIDATED PASS

Auto-discovered strategy

Symbol: DOGE | Exchange: Bitfinex | Role: momentum

3/6
Profitable Years
+44.5%
Total Return
35.1%
Avg Win Rate
0.11
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +0.0% 0.0% 0 0.0% 0.00
2021 -31.7% 35.5% 31 43.3% -1.16
2022 +6.0% 45.7% 46 30.3% 0.20
2023 +4.7% 39.0% 82 34.5% 0.13
2024 +68.4% 50.5% 91 24.4% 1.63
2025 -2.9% 40.0% 40 18.5% -0.11

Performance Chart

Loading chart...

Walk-Forward Validation PASS

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

AI Review

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

Source Code

"""
DOGE EMA Cascade Pullback Strategy
===================================

A momentum trend-following strategy that buys pullbacks to EMA20
during strong uptrends (when EMA20 > EMA50 > EMA200).

Core Logic:
- Entry: In strong uptrend (EMA cascade intact), buy when price pulls
  back to touch the EMA20 and bounces (closes above it)
- Exit: ATR-based stop loss (1.5x ATR), 5% take profit, trend break
  (close below EMA50), or 20-bar time stop

Bear Market Protection:
- Strategy stays FLAT when EMA cascade is broken
- No entries when EMA50 < EMA200 (macro downtrend)
- Automatic trend-break exit prevents holding through crashes

Parameters (all round numbers):
- EMA periods: 20, 50, 200
- ATR period: 20
- ATR multiplier: 1.5 (for stop loss)
- Take profit: 5%
- Time stop: 20 bars
- Pullback tolerance: 1%

Symbol: tDOGE:USD (Bitfinex)
Timeframe: 4h
Role: momentum
"""

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


def init_strategy():
    return {
        'name': 'doge_ema_cascade_pullback',
        'role': 'momentum',  # Can lose up to -15% in bear, DD < 40%
        'warmup': 200,       # Need 200 bars for EMA200
        'subscriptions': [
            {'symbol': 'tDOGE:USD', 'exchange': 'bitfinex', 'timeframe': '4h'},
        ],
        'parameters': {}
    }


def process_time_step(ctx):
    """
    Process each time step for the EMA Cascade Pullback strategy.

    Entry conditions (all must be true):
    1. EMA cascade intact: EMA20 > EMA50 > EMA200 (strong uptrend)
    2. Price above EMA200 (not in crash territory)
    3. Price pulls back to touch EMA20 (within 1%)
    4. Price closes above EMA20 (bounce confirmed)

    Exit conditions:
    1. Take profit at 5%
    2. Stop loss at 1.5x ATR (typically 4-8%)
    3. Price closes below EMA50 (trend break)
    4. Time stop at 20 bars
    """
    key = ('tDOGE:USD', 'bitfinex')
    bars = ctx['bars'][key]
    i = ctx['i']
    positions = ctx['positions']
    state = ctx['state']

    # Parameters - all round numbers to avoid overfitting
    EMA_FAST = 20
    EMA_MED = 50
    EMA_SLOW = 200
    ATR_PERIOD = 20
    ATR_MULT = 1.5
    TAKE_PROFIT_PCT = 5
    TIME_STOP_BARS = 20
    PULLBACK_TOLERANCE = 0.01  # 1%

    # Calculate indicators using available data up to current bar
    closes = [b.close for b in bars[:i+1]]
    highs = [b.high for b in bars[:i+1]]
    lows = [b.low for b in bars[:i+1]]

    ema_fast = ema(closes, EMA_FAST)
    ema_med = ema(closes, EMA_MED)
    ema_slow = ema(closes, EMA_SLOW)
    atr_vals = atr(highs, lows, closes, ATR_PERIOD)

    actions = []

    if key not in positions:
        # Check entry conditions
        if (ema_fast[-1] and ema_med[-1] and ema_slow[-1] and atr_vals[-1]):
            # 1. EMA cascade intact (strong uptrend)
            cascade_intact = (ema_fast[-1] > ema_med[-1] > ema_slow[-1])

            # 2. Price above EMA200
            price_above_slow = closes[-1] > ema_slow[-1]

            # 3. Price pulled back to touch EMA20
            pullback_to_fast = lows[-1] <= ema_fast[-1] * (1 + PULLBACK_TOLERANCE)

            # 4. Price closed above EMA20 (bounce)
            bounce_confirmed = closes[-1] >= ema_fast[-1] * (1 - PULLBACK_TOLERANCE)

            if cascade_intact and price_above_slow and pullback_to_fast and bounce_confirmed:
                # Calculate dynamic stop based on ATR
                stop_loss_price = closes[-1] - ATR_MULT * atr_vals[-1]
                stop_loss_pct = (closes[-1] - stop_loss_price) / closes[-1] * 100

                # Clamp stop loss to reasonable range (2-10%)
                stop_loss_pct = max(2, min(stop_loss_pct, 10))

                actions.append({
                    'action': 'open_long',
                    'symbol': 'tDOGE:USD',
                    'exchange': 'bitfinex',
                    'size': 1.0,
                    'take_profit_pct': TAKE_PROFIT_PCT,
                    'stop_loss_pct': stop_loss_pct,
                })
                state['entry_bar'] = i
    else:
        # Check exit conditions (besides TP/SL which framework handles)
        bars_held = i - state.get('entry_bar', i)

        # Time stop - exit if held too long without reaching TP/SL
        if bars_held >= TIME_STOP_BARS:
            actions.append({
                'action': 'close_long',
                'symbol': 'tDOGE:USD',
                'exchange': 'bitfinex',
            })
        # Trend break - exit if price closes below EMA50
        elif ema_med[-1] and closes[-1] < ema_med[-1]:
            actions.append({
                'action': 'close_long',
                'symbol': 'tDOGE:USD',
                'exchange': 'bitfinex',
            })

    return actions


# Self-test when run directly
if __name__ == '__main__':
    from strategy import backtest_strategy
    results, profitable, _ = backtest_strategy(init_strategy, process_time_step, verbose=True)
    print(f"\nProfitable training periods: {profitable}/2")