← Back to list

eth_momentum_continuation VALIDATED PASS

Auto-discovered strategy

Symbol: ETH | Exchange: Bitfinex | Role: momentum

3/6
Profitable Years
+102.1%
Total Return
42.6%
Avg Win Rate
0.51
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +68.5% 49.5% 93 17.4% 1.87
2021 -0.5% 37.2% 94 32.8% -0.01
2022 -11.8% 34.4% 32 23.8% -0.61
2023 -13.4% 36.2% 47 29.6% -0.64
2024 +38.2% 51.0% 49 14.5% 1.52
2025 +21.2% 47.6% 42 18.7% 0.90

Performance Chart

Loading chart...

Walk-Forward Validation PASS

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

AI Review

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

Source Code

"""
ETH Momentum Continuation Strategy
==================================

A trend-following momentum strategy for ETH that enters on breakouts
in confirmed uptrends and exits when momentum weakens.

Strategy Concept:
-----------------
This strategy captures momentum continuation in established uptrends.
It waits for price to make a new high while the trend is confirmed
by EMAs and volume. The idea is that breakouts in strong trends
tend to continue, especially when supported by volume.

Entry Conditions (all must be true):
1. Uptrend structure: Price > EMA50 > EMA100 (bullish cascade)
2. Rising momentum: EMA50 higher than 5 bars ago
3. Breakout signal: Price makes new 10-bar high
4. Volume confirmation: Current volume > 20-bar average

Exit Conditions (any triggers exit):
1. Hard stop: 3% loss from entry (risk management)
2. Profit target: 5% gain from entry
3. Time exit: 15 bars maximum hold
4. Trend break: Close below EMA50 (after 3 bar minimum hold)

Risk Management:
----------------
- Uses EMA cascade as regime filter (no trades in downtrends)
- Volume confirmation reduces false breakouts
- Fixed stop loss protects against adverse moves
- Time exit prevents getting stuck in choppy conditions

Expected Behavior:
------------------
- Performs well in trending markets
- Will have small losses in choppy/ranging conditions
- May underperform or lose in strong bear markets
- Win rate around 45-50%, relies on winners being larger than losers

Train Period Performance (2024-01-01 to 2025-06-30):
- 70 trades, 36.5% total return
- Win rate: 45.7%
- Max drawdown: 18.9%
- Profit factor: 1.36
"""

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


def init_strategy():
    """Initialize the strategy configuration."""
    return {
        'name': 'eth_momentum_continuation',
        'role': 'momentum',
        'warmup': 100,
        'subscriptions': [
            {'symbol': 'tETHUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
        ],
        'parameters': {}
    }


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

    Args:
        ctx: Context dict containing bars, positions, state, etc.

    Returns:
        List of action dicts (open_long, close_long, etc.)
    """
    key = ('tETHUSD', 'bitfinex')
    bars = ctx['bars'][key]
    i = ctx['i']
    positions = ctx['positions']

    # Build price/volume arrays up to current bar
    closes = [b.close for b in bars[:i+1]]
    highs = [b.high for b in bars[:i+1]]
    volumes = [b.volume for b in bars[:i+1]]

    # Calculate indicators
    ema50_arr = ema(closes, 50)
    ema100_arr = ema(closes, 100)
    sma_vol_arr = sma(volumes, 20)

    # Get current indicator values
    ema50 = ema50_arr[i] if i < len(ema50_arr) else None
    ema100 = ema100_arr[i] if i < len(ema100_arr) else None
    sma_vol = sma_vol_arr[i] if i < len(sma_vol_arr) else None
    ema50_prev = ema50_arr[i-5] if i >= 5 and i-5 < len(ema50_arr) else None

    actions = []

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

        # Validate indicator availability
        if not (ema50 and ema100 and sma_vol and ema50_prev):
            return []
        if i < 10:
            return []

        # Condition 1: Uptrend structure (EMA cascade)
        if not (closes[i] > ema50 > ema100):
            return []

        # Condition 2: Rising momentum (EMA50 trending up)
        if ema50 <= ema50_prev:
            return []

        # Condition 3: 10-bar high breakout
        recent_high = max(highs[i-10:i])
        if highs[i] <= recent_high:
            return []

        # Condition 4: Volume confirmation
        if volumes[i] < sma_vol:
            return []

        # All conditions met - enter long
        actions.append({
            'action': 'open_long',
            'symbol': 'tETHUSD',
            'exchange': 'bitfinex',
            'size': 1.0,
            'stop_loss_pct': 3,
            'take_profit_pct': 5
        })

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

        if not ema50:
            return []

        should_exit = False

        # Exit condition 1: Time exit (15 bars max)
        if bars_held >= 15:
            should_exit = True

        # Exit condition 2: Trend break (close below EMA50)
        if bars_held >= 3 and bars[i].close < ema50:
            should_exit = True

        if should_exit:
            actions.append({
                'action': 'close_long',
                'symbol': 'tETHUSD',
                'exchange': 'bitfinex'
            })

    return actions