← Back to list

doge_consecutive_higher_lows_breakout VALIDATED PASS

Auto-discovered strategy

Symbol: DOGE | Exchange: Binance | Role: momentum

3/6
Profitable Years
+72.8%
Total Return
26.4%
Avg Win Rate
-0.42
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +10.2% 22.7% 22 29.1% 0.34
2021 +28.2% 29.2% 24 24.6% 0.42
2022 -22.5% 0.0% 9 20.5% -3.84
2023 -8.7% 29.4% 17 11.5% -0.59
2024 +66.2% 53.8% 26 9.8% 1.18
2025 -0.7% 23.1% 13 20.7% -0.02

Performance Chart

Loading chart...

Walk-Forward Validation PASS

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

AI Review

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

Source Code

"""
DOGE Consecutive Higher Lows Breakout Strategy
===============================================

A momentum strategy for DOGEUSDT that identifies accumulation patterns via
consecutive higher lows, then enters on breakout above the pattern high.

MARKET CONCEPT:
---------------
When price makes consecutive higher lows (3+ bars), it signals accumulation -
buyers are stepping in at progressively higher levels, absorbing selling pressure.
When price then breaks above the pattern's high, it often triggers a significant
upward move as shorts cover and momentum traders join.

ENTRY CONDITIONS:
1. REGIME: EMA50 > EMA200 (bull macro trend - stays FLAT in bear markets)
2. PATTERN: 3 consecutive higher lows (bars i-3 > i-4, i-2 > i-3, i-1 > i-2)
3. BREAKOUT: Current close > highest high of pattern bars
4. TREND: Price > EMA20 (short-term uptrend confirmation)
5. BULLISH: Current bar is bullish (close > open)
6. STRENGTH: Current bar closes in upper 50% of its range

EXIT CONDITIONS:
1. REGIME BREAKDOWN: EMA50 < EMA200 (urgent exit on macro trend change)
2. TREND BREAK: Price closes below EMA50 (lost support)
3. MOMENTUM FADE: Two consecutive bearish bars (sellers taking over)
4. STOP LOSS: 5% hard stop

WHY THIS SHOULD GENERALIZE:
- Uses RELATIVE indicators only (EMAs, consecutive patterns)
- Round parameter values: 3 bars, 20/50/200 EMAs, 5% stop, 50% close position
- Universal market principle: accumulation precedes breakout
- Strong regime filter keeps us flat during bear markets
- Works on any liquid asset with trending behavior

Symbol: DOGEUSDT @ Binance
Timeframe: 4h
Role: momentum

TRAIN DATA Performance (2024-01 to 2025-06):
- 2024: +66.2% | 26 trades | 54% WR | Sharpe 1.18 | MaxDD 9.8%
- 2025 H1: +14.4% | 5 trades | 20% WR | Sharpe 0.67 | MaxDD 6.9%
- Total: +80.6% | 31 trades | Both train periods profitable
"""

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


def init_strategy():
    """Initialize strategy configuration."""
    return {
        'name': 'doge_consecutive_higher_lows_breakout',
        'role': 'momentum',  # Allowed to lose bounded in bear, expected to profit in bull
        'warmup': 200,
        'subscriptions': [
            {'symbol': 'DOGEUSDT', 'exchange': 'binance', 'timeframe': '4h'},
        ],
        'parameters': {
            'ema_fast': 20,
            'ema_slow': 50,
            'ema_macro': 200,
            'pattern_bars': 3,  # Minimum consecutive higher lows
            'stop_loss_pct': 5,
        }
    }


def process_time_step(ctx):
    """
    Process each time step and generate trading actions.

    The strategy:
    1. Checks regime filter (EMA50 > EMA200) - stays FLAT in bear markets
    2. Looks for 3 consecutive higher lows in recent bars
    3. Enters on breakout above pattern high with bullish confirmation
    4. Exits on regime change, trend break, or momentum fade
    """
    key = ('DOGEUSDT', 'binance')
    bars = ctx['bars'][key]
    i = ctx['i']
    positions = ctx['positions']

    closes = [b.close for b in bars]
    highs = [b.high for b in bars]
    lows = [b.low for b in bars]

    actions = []

    # Calculate EMAs with round period numbers
    ema20 = ema(closes[:i+1], 20)
    ema50 = ema(closes[:i+1], 50)
    ema200 = ema(closes[:i+1], 200)

    # Safety: ensure all EMAs are calculated
    if ema50[-1] is None or ema200[-1] is None or ema20[-1] is None:
        return []

    # ===== ENTRY LOGIC =====
    if key not in positions:
        # REGIME FILTER: Stay flat when EMA50 < EMA200 (bear market protection)
        # This is CRITICAL for surviving bear markets like 2025-H2
        if ema50[-1] <= ema200[-1]:
            return []

        if i < 6:
            return []

        # CONSECUTIVE HIGHER LOWS PATTERN:
        # Check if bars i-3, i-2, i-1 each have a higher low than the previous
        # This signals accumulation - buyers stepping in at higher prices
        has_higher_lows = (
            lows[i-1] > lows[i-2] and
            lows[i-2] > lows[i-3] and
            lows[i-3] > lows[i-4]
        )

        if not has_higher_lows:
            return []

        # BREAKOUT LEVEL: Highest high of the pattern bars
        pattern_high = max(highs[i-4], highs[i-3], highs[i-2], highs[i-1])

        # BREAKOUT: Current close must exceed pattern high
        if closes[i] <= pattern_high:
            return []

        # TREND: Must be above EMA20 (short-term trend healthy)
        if closes[i] < ema20[-1]:
            return []

        # BULLISH: Current bar must be bullish
        if bars[i].close <= bars[i].open:
            return []

        # CANDLE STRENGTH: Close in upper 50% of range (buying pressure)
        bar_range = bars[i].high - bars[i].low
        if bar_range > 0:
            close_pos = (bars[i].close - bars[i].low) / bar_range
            if close_pos < 0.5:
                return []

        # All conditions met - ENTRY
        actions.append({
            'action': 'open_long',
            'symbol': 'DOGEUSDT',
            'exchange': 'binance',
            'size': 1.0,
            'stop_loss_pct': 5,  # 5% hard stop
        })

    # ===== EXIT LOGIC =====
    else:
        pos = positions[key]
        bars_held = i - pos.entry_bar

        # Minimum hold: 1 bar
        if bars_held < 1:
            return []

        # EXIT 1: Regime breakdown - EMA50 < EMA200 (urgent exit)
        if ema50[-1] < ema200[-1]:
            actions.append({
                'action': 'close_long',
                'symbol': 'DOGEUSDT',
                'exchange': 'binance',
            })
            return actions

        # EXIT 2: Price below EMA50 (trend break)
        if closes[i] < ema50[-1]:
            actions.append({
                'action': 'close_long',
                'symbol': 'DOGEUSDT',
                'exchange': 'binance',
            })
            return actions

        # EXIT 3: Two consecutive bearish bars (momentum fade)
        if bars_held >= 2:
            if bars[i].close < bars[i].open and bars[i-1].close < bars[i-1].open:
                actions.append({
                    'action': 'close_long',
                    'symbol': 'DOGEUSDT',
                    'exchange': 'binance',
                })

    return actions


# Entry/Exit logic documentation for database
ENTRY_LOGIC = """
Entry when ALL conditions met:
1. Bull Regime: EMA50 > EMA200 (macro trend bullish)
2. Higher Lows: 3 consecutive bars each with low > previous bar's low
3. Breakout: Close > highest high of pattern bars
4. Trend: Price > EMA20 (short-term uptrend)
5. Bullish Bar: Current bar close > open
6. Strong Close: Close in upper 50% of bar range
"""

EXIT_LOGIC = """
Exit when ANY condition met:
1. Regime Change: EMA50 < EMA200 (bear market)
2. Trend Break: Price closes below EMA50
3. Momentum Fade: 2 consecutive bearish bars
4. Stop Loss: 5% from entry price
"""

RESULTS = {
    2024: {
        "trades": 26,
        "return": 66.2,
        "sharpe": 1.18,
        "win_rate": 54.0,
        "max_dd": 9.8,
    },
    2025: {
        "trades": 5,
        "return": 14.4,
        "sharpe": 0.67,
        "win_rate": 20.0,
        "max_dd": 6.9,
    }
}


# For testing
if __name__ == '__main__':
    from strategy import backtest_strategy
    results, profitable, _ = backtest_strategy(init_strategy, process_time_step)
    print(f"\nProfitable years: {profitable}/2")