← Back to list

eth_ema_alignment_balanced VALIDATED FAIL

Auto-discovered strategy

Symbol: ETH | Exchange: Bitfinex | Role: momentum

3/6
Profitable Years
+12.2%
Total Return
31.9%
Avg Win Rate
-0.05
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +20.2% 44.0% 25 13.3% 0.73
2021 +12.0% 37.0% 27 25.2% 0.40
2022 -20.0% 18.2% 11 25.2% -1.30
2023 -14.4% 22.2% 18 20.0% -0.71
2024 +15.2% 38.9% 18 28.4% 0.62
2025 -0.8% 31.2% 16 22.4% -0.04

Performance Chart

Loading chart...

Walk-Forward Validation FAIL

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

AI Review Score: 35/100

overfitting inconsistent execution
## Critical Issues ### 1. Severe Regime Inconsistency (Major Red Flag) The year-by-year results reveal **extreme inconsistency** across market regimes: - **Bull years (2020, 2021, 2024):** Strong positive returns (+12% to +20%) - **Bear/chop years (2022, 2023):** Severe losses (-14% to -20%) - **Recent period (2025):** Near breakeven This is a **textbook momentum trap**: the strategy works beautifully in sustained uptrends but collapses when the regime shifts. A momentum role strategy should handle bear markets poorly but not catastrophically (-15% threshold exists for a reason). The -20% drawdown in bear conditions exceeds acceptable risk. **Validation period failure** (Sharpe -4.63) confirms this is not a robust system—it likely encountered a regime it couldn't handle. ### 2. Overfitting Through Condition Stacking The strategy uses **7 distinct entry conditions**: 1. Score must equal exactly 4 (rigid threshold) 2. Previous score must be <4 (transition detection) 3. EMA50 slope >0.5% (specific threshold) 4. Slope measured over exactly 20 bars 5. Price extension <8% above EMA50 (specific threshold) 6. Requires 4-way EMA alignment (20/50/100/200) 7. Implicit warmup requirements This exceeds the **5-6 condition limit** specified in the rules. More critically, the thresholds (0.5%, 8%, exactly 20 bars, score transition from 3→4) appear curve-fitted to historical data rather than derived from market principles. ### 3. Execution Realism Issues **Fresh alignment detection is problematic:** ```python fresh_alignment = score == 4 and prev_score < 4 ``` This requires detecting the exact bar where alignment transitions. In live markets: - EMAs recalculate continuously within bars - 4h timeframe means you're trying to catch a specific 4-hour window - Slippage during breakouts can be severe - The strategy doesn't account for this timing precision requirement The 8% take-profit on a 4h momentum strategy is reasonable, but the entry timing precision creates unrealistic fill assumptions. ### 4. Magic Number Concerns While the strategy uses round parameters (4, 8, 20, 50, 100, 200), the **thresholds appear optimized**: - Why 0.5% slope vs 0.4% or 0.6%? - Why 8% extension vs 7% or 10%? - Why score transition from <4 to 4, not from ≤2 to 4? These feel curve-fitted to the training data rather than universal momentum principles. ## Positive Elements - **No lookahead bias:** Uses only past data, trades on next bar - **Relative indicators:** No absolute price levels or dates - **Risk management:** Has stop-loss and take-profit - **Code quality:** Clean, readable, well-documented - **Trade count:** Sufficient for statistical validity (24 total trades) ## Why This Fails A true momentum strategy should: 1. **Adapt to regime changes** (this one breaks in bear markets) 2. **Use simple, universal rules** (this one stacks 7 specific conditions) 3. **Work across decades** (this one works in 3/6 years) The core issue: **the strategy was optimized for specific historical patterns rather than fundamental momentum principles**. When those patterns didn't repeat in validation, it collapsed. ## Verdict **Score: 35/100 - Poor** The strategy is fundamentally unsound for production use. While it shows strong returns in favorable regimes, it: - Fails catastrophically in unfavorable conditions - Uses too many specific conditions (complexity breeds overfitting) - Relies on precise entry timing that may not be executable - Shows no evidence of robustness across diverse market conditions The validation failure (Sharpe -4.63) is the market telling you this doesn't generalize.
Reviewed: 2026-01-15T09:14:41.853385

Source Code

"""
ETH EMA Alignment Momentum Strategy
====================================

A momentum strategy that enters when ETH price and EMAs are fully aligned
in an uptrend, with balanced risk/reward through capped gains.

CONCEPT:
Uses a 4-point EMA alignment score to identify strong uptrends:
- Price > EMA20 (+1)
- EMA20 > EMA50 (+1)
- EMA50 > EMA100 (+1)
- EMA100 > EMA200 (+1)

ENTRY CONDITIONS:
- Perfect alignment score (4/4) just formed
- EMA50 slope positive (>0.5% over 20 bars)
- Price not overextended (<8% above EMA50)

EXIT CONDITIONS:
- Take profit: 8% (capped to distribute gains)
- Stop loss: 4%
- Signal: Alignment score ≤ 2 OR EMA50 slope negative

ROBUSTNESS FEATURES:
- Uses only relative indicators (EMAs, slopes, percentages)
- Round parameter values only (4, 8, 20, 50, 100, 200)
- Regime filter prevents trading in downtrends
- Capped TP prevents over-reliance on few big winners

Train Results (2024-2025H1):
- 2024: +15.2% | 18 trades | 39% WR
- 2025 (partial): +12.0% | 6 trades | 50% WR
- Total: +27.2% | Max DD: 28.4%
"""

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


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


def process_time_step(ctx):
    """
    Process each time step to generate trading signals.

    Uses EMA alignment score and slope confirmation for entries,
    with capped TP/SL for balanced returns.
    """
    key = ('tETHUSD', 'bitfinex')
    bars = ctx['bars'][key]
    i = ctx['i']
    positions = ctx['positions']
    state = ctx['state']

    actions = []

    # Calculate EMAs
    closes = [b.close for b in bars[:i+1]]
    ema20 = ema(closes, 20)
    ema50 = ema(closes, 50)
    ema100 = ema(closes, 100)
    ema200 = ema(closes, 200)

    price = bars[i].close
    e20 = ema20[-1]
    e50 = ema50[-1]
    e100 = ema100[-1]
    e200 = ema200[-1]

    # Need all EMAs calculated
    if None in [e20, e50, e100, e200] or i < 20:
        return []

    # Calculate alignment score (0-4)
    score = sum([
        price > e20,
        e20 > e50,
        e50 > e100,
        e100 > e200
    ])

    # EMA50 slope over 20 bars
    e50_20_ago = ema50[-21] if len(ema50) > 20 else e50
    ema50_slope = (e50 - e50_20_ago) / e50_20_ago * 100 if e50_20_ago else 0

    # Track previous score for transition detection
    prev_score = state.get('prev_score', 0)
    state['prev_score'] = score

    # Price extension from EMA50
    price_ext = (price - e50) / e50 * 100

    if key not in positions:
        # ENTRY CONDITIONS:
        # 1. Fresh alignment (score just became 4)
        # 2. EMA50 slope confirms uptrend (> 0.5%)
        # 3. Not overextended (< 8% above EMA50)

        fresh_alignment = score == 4 and prev_score < 4
        slope_confirms = ema50_slope > 0.5
        not_extended = price_ext < 8

        if fresh_alignment and slope_confirms and not_extended:
            actions.append({
                'action': 'open_long',
                'symbol': 'tETHUSD',
                'exchange': 'bitfinex',
                'size': 1.0,
                'stop_loss_pct': 4,
                'take_profit_pct': 8,
            })
            state['entry_price'] = price
    else:
        # EXIT CONDITIONS (signal-based, TP/SL handled by framework):
        # 1. Alignment breakdown (score ≤ 2)
        # 2. EMA50 slope turns negative

        breakdown = score <= 2
        slope_negative = ema50_slope < 0

        if breakdown or slope_negative:
            actions.append({
                'action': 'close_long',
                'symbol': 'tETHUSD',
                'exchange': 'bitfinex',
            })
            state.pop('entry_price', None)

    return actions