← Back to list

eth_hl_gap_fill VALIDATED FAIL

Auto-discovered strategy

Symbol: ETH | Exchange: Bitfinex | Role: momentum

4/6
Profitable Years
+90.7%
Total Return
35.9%
Avg Win Rate
0.13
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +34.1% 30.6% 62 28.5% 0.68
2021 +58.8% 40.4% 47 18.8% 1.21
2022 -19.3% 43.5% 23 27.9% -1.18
2023 -25.8% 31.6% 38 26.2% -1.22
2024 +19.3% 40.0% 30 18.6% 0.77
2025 +23.6% 29.0% 31 22.0% 0.53

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

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

Source Code

"""
ETH Higher Low Gap Fill Strategy
================================

Trades the structural gap fill pattern on ETH/USD. When price forms a higher low
pattern (bullish structure) and then breaks out above the range, this indicates
the "gap" between the prior resistance and current price is being filled with
strong momentum.

The strategy only trades in confirmed uptrends (EMA50 > EMA200) to avoid
counter-trend entries during bear markets.

Entry Logic:
- EMA50 > EMA200 (uptrend filter)
- Find swing low in last 5 bars
- Confirm it's higher than swing low 5-10 bars prior (higher low pattern)
- Current bar breaks above the range high formed between the two lows
- Current bar is bullish (close > open)

Exit Logic:
- Close below EMA50 (momentum lost)
- 25 bars elapsed (100 hours max hold)
- Trend break (EMA50 < EMA200)
- 5% stop loss

This is a momentum strategy that captures the continuation after a healthy
pullback in an uptrend. The "gap fill" refers to price filling the structural
gap created by the higher low formation.
"""

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


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


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

    # Initialize state
    if 'entry_bar' not in state:
        state['entry_bar'] = None

    actions = []

    # Compute indicators
    closes = [bars[j].close for j in range(i+1)]
    highs = [bars[j].high for j in range(i+1)]
    lows = [bars[j].low for j in range(i+1)]

    ema50 = ema(closes, 50)
    ema200 = ema(closes, 200)

    if ema50[i] is None or ema200[i] is None:
        return actions

    # Regime filter: must be in uptrend
    in_uptrend = ema50[i] > ema200[i]

    # Lookback period for swing detection
    lb = 5

    if key not in positions:
        if in_uptrend and i >= lb * 2:
            # Find recent swing low (last 5 bars)
            recent_low = min(lows[i-lb:i])
            recent_low_idx = None
            for j in range(i-lb, i):
                if lows[j] == recent_low:
                    recent_low_idx = j
                    break

            if recent_low_idx is None or recent_low_idx < lb:
                return actions

            # Find prior swing low (5-10 bars before recent)
            prior_low = min(lows[recent_low_idx-lb:recent_low_idx])

            # Higher low pattern - structural gap formation
            if recent_low > prior_low:
                # Range high between the two lows
                range_high = max(highs[recent_low_idx-lb:recent_low_idx+1])

                # Current bar breaks above range (fills the structural gap)
                if bars[i].high > range_high:
                    # Confirm bullish bar
                    if bars[i].close > bars[i].open:
                        state['entry_bar'] = i

                        actions.append({
                            'action': 'open_long',
                            'symbol': 'tETHUSD',
                            'exchange': 'bitfinex',
                            'size': 1.0,
                            'stop_loss_pct': 5.0,
                        })
    else:
        if state['entry_bar'] is None:
            return actions

        bars_held = i - state['entry_bar']

        # Exit: Close below EMA50 (momentum lost)
        if bars[i].close < ema50[i]:
            actions.append({
                'action': 'close_long',
                'symbol': 'tETHUSD',
                'exchange': 'bitfinex',
            })
            state['entry_bar'] = None
            return actions

        # Exit: Timeout (25 bars = 100 hours)
        if bars_held >= 25:
            actions.append({
                'action': 'close_long',
                'symbol': 'tETHUSD',
                'exchange': 'bitfinex',
            })
            state['entry_bar'] = None
            return actions

        # Exit: Trend break
        if not in_uptrend:
            actions.append({
                'action': 'close_long',
                'symbol': 'tETHUSD',
                'exchange': 'bitfinex',
            })
            state['entry_bar'] = None

    return actions


# For standalone testing
if __name__ == '__main__':
    from strategy import backtest_strategy
    results, profitable, _ = backtest_strategy(init_strategy, process_time_step)