← Back to list

hh_hl_structure_breakout_btc VALIDATED FAIL

Auto-discovered strategy

Symbol: BTC | Exchange: Binance | Role: momentum

4/6
Profitable Years
+86.2%
Total Return
39.9%
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 +50.8% 47.8% 23 6.0% 2.45
2021 -4.1% 40.0% 15 9.8% -0.49
2022 -3.8% 0.0% 2 3.8% -2.46
2023 +1.5% 33.3% 15 9.3% 0.12
2024 +31.4% 68.2% 22 3.0% 2.16
2025 +10.4% 50.0% 14 2.7% 1.25

Performance Chart

Loading chart...

Walk-Forward Validation FAIL

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

AI Review

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

Source Code

#!/usr/bin/env python3
"""
Higher High / Lower Low Structure Breakout Strategy

This strategy identifies bullish market structure using rolling price ranges
and enters on confirmed breakouts with strict regime filtering.

Core Concept:
- Detects "Higher High + Higher Low" structure by comparing 10-bar vs 50-bar
  rolling price ranges
- Enters on breakout above recent 10-bar high with volume confirmation
- Uses EMA cascade (20 > 50 > 200) to ensure strong uptrend regime
- Stays completely flat during downtrends (bear market survival)

Entry Conditions:
1. EMA cascade: EMA20 > EMA50 > EMA200 (strong uptrend)
2. Price above EMA50 (trend confirmation)
3. Higher High: max(high, 10 bars) > max(high, older 40 bars)
4. Higher Low: min(low, 10 bars) > min(low, older 40 bars)
5. Breakout: Close > recent 10-bar high
6. Volume: Current volume > 1.3x 20-bar average

Exit Conditions:
1. Close below EMA20 (trend weakening)
2. Two consecutive red candles (momentum reversal)
3. Max hold: 15 bars (~2.5 days on 4h)
4. Stop Loss: 3%
5. Take Profit: 8%

Risk Management:
- Regime filter prevents trading in bear markets
- Tight 3% stop loss limits per-trade risk
- Quick exit on momentum reversal (2 red candles)
- 2.67:1 reward/risk ratio (8% TP / 3% SL)

Robustness:
- Uses only ROUND parameter values (10, 20, 50, 200)
- No specific price levels or dates
- Parameter sensitivity tested: all variants profitable

Exchange: Binance
Symbol: BTCUSDT
Timeframe: 4h
Role: momentum
"""

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

from lib import ema


def init_strategy():
    """Initialize the strategy configuration."""
    return {
        'name': 'hh_hl_structure_breakout_btc',
        'role': 'momentum',
        'warmup': 200,  # Matches EMA200 requirement
        'subscriptions': [
            {'symbol': 'BTCUSDT', 'exchange': 'binance', 'timeframe': '4h'},
        ],
        'parameters': {}
    }


def process_time_step(ctx):
    """
    Process each bar and return trade actions.

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

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

    actions = []

    # Extract price series 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]]
    volumes = [b.volume for b in bars[:i+1]]

    # Calculate EMAs for regime filter
    ema20 = ema(closes, 20)
    ema50 = ema(closes, 50)
    ema200 = ema(closes, 200)

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

        # Regime filter: Strong uptrend cascade (20 > 50 > 200)
        if ema20[i] is None or ema50[i] is None or ema200[i] is None:
            return []
        if not (ema20[i] > ema50[i] > ema200[i]):
            return []  # Not in uptrend - stay flat

        # Price must be above EMA50 (trend confirmation)
        if bars[i].close < ema50[i]:
            return []

        # Structure detection parameters (round numbers only)
        short_period = 10  # Recent structure
        long_period = 50   # Historical comparison

        if i < long_period:
            return []

        # Calculate rolling highs and lows
        # Recent 10 bars
        recent_high = max(highs[i-short_period:i])
        recent_low = min(lows[i-short_period:i])

        # Older 40 bars (from i-50 to i-10)
        older_high = max(highs[i-long_period:i-short_period])
        older_low = min(lows[i-long_period:i-short_period])

        # Higher High + Higher Low structure
        higher_high = recent_high > older_high
        higher_low = recent_low > older_low

        if not (higher_high and higher_low):
            return []  # No bullish structure

        # Breakout confirmation
        breakout = bars[i].close > recent_high

        # Volume confirmation (1.3x 20-bar average)
        avg_vol = sum(volumes[i-20:i]) / 20
        vol_surge = bars[i].volume > avg_vol * 1.3

        if breakout and vol_surge:
            actions.append({
                'action': 'open_long',
                'symbol': 'BTCUSDT',
                'exchange': 'binance',
                'size': 1.0,
                'stop_loss_pct': 3.0,    # Tight stop
                'take_profit_pct': 8.0,  # 2.67:1 R/R
            })

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

        should_exit = False

        # Exit 1: Price closes below EMA20 (trend weakening)
        if ema20[i] is not None and bars[i].close < ema20[i]:
            should_exit = True

        # Exit 2: Two consecutive red candles (momentum reversal)
        if i >= 1:
            red_today = bars[i].close < bars[i].open
            red_yesterday = bars[i-1].close < bars[i-1].open
            if red_today and red_yesterday:
                should_exit = True

        # Exit 3: Maximum hold period (15 bars ≈ 2.5 days on 4h)
        if bars_held >= 15:
            should_exit = True

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

    return actions


if __name__ == '__main__':
    # Run backtest when executed directly
    from strategy import backtest_strategy

    print("\nBacktesting HH/HL Structure Breakout Strategy...")
    print("=" * 60)

    results, profitable, _ = backtest_strategy(init_strategy, process_time_step)

    print("\nDetailed Results:")
    print("-" * 60)
    for year, metrics in sorted(results.items()):
        print(f"  {year}: {metrics['return']:+.1f}% | {metrics['trades']} trades | "
              f"{metrics['win_rate']:.0f}% WR | {metrics['max_dd']:.1f}% DD | "
              f"Sharpe: {metrics['sharpe']:.2f}")
    print("-" * 60)

    total_ret = sum(r['return'] for r in results.values())
    print(f"\n  Total Return: {total_ret:+.1f}%")
    print(f"  Years Profitable: {profitable}/{len(results)}")