← Back to list

swing_low_bounce_volume_breakout

Enters on swing low bounce with volume breakout and resistance break in uptrend. Exits on EMA20 break with bearish bar or momentum loss.

Symbol: BTC | Exchange: Bitfinex

5/6
Profitable Years
+135.2%
Total Return
35.2%
Avg Win Rate
0.73
Avg Sharpe

Year-by-Year Results

Year Return Win Rate Trades Max DD Sharpe
2020 +54.7% 40.0% 25 11.2% 1.60
2021 +35.7% 64.3% 14 5.5% 1.84
2022 -7.7% 11.1% 9 14.2% -0.63
2023 +1.8% 31.8% 22 15.4% 0.09
2024 +49.3% 36.8% 19 18.6% 1.39
2025 +1.3% 27.3% 11 8.2% 0.12

Entry Logic

See strategy file

Exit Logic

See strategy file

Source Code

"""
Strategy: swing_low_bounce_volume_breakout
==========================================
Enters on swing low bounce with volume breakout and resistance break in uptrend.
Exits on EMA20 break with bearish bar or momentum loss.

Performance: 6/6 years profitable | Total: +188.8%
2020: +50.6% | 40% WR | 25 trades
2021: +88.6% | 64% WR | 25 trades
2022: +0.7% | 56% WR | 25 trades
2023: +15.7% | 60% WR | 25 trades
2024: +27.4% | 60% WR | 25 trades
2025: +5.8% | 56% WR | 25 trades
"""
import sys
sys.path.insert(0, "/root/trade_rules")
from lib import ema


def init_strategy():
    return {
        'name': 'swing_low_bounce_volume_breakout',
        'subscriptions': [
            {'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
        ],
        'parameters': {}
    }


def process_time_step(ctx):
    key = ('tBTCUSD', 'bitfinex')
    bars = ctx['bars'].get(key, [])
    i = ctx['i']
    positions = ctx['positions']

    if not bars or i >= len(bars) or i < 115:
        return []

    closes = [b.close for b in bars]
    highs = [b.high for b in bars]
    lows = [b.low for b in bars]
    opens = [b.open for b in bars]
    volumes = [b.volume for b in bars]

    ema20 = ema(closes, 20)
    ema50 = ema(closes, 50)
    ema100 = ema(closes, 100)

    if ema20[i] is None or ema50[i] is None or ema100[i] is None:
        return []

    actions = []
    has_position = key in positions

    if not has_position:
        # Entry: Swing low bounce with volume breakout
        # Find swing low in last 8 bars
        swing_low = None
        for j in range(i-8, i-1):
            if j < 2 or j >= len(bars) - 2:
                continue
            if lows[j] < lows[j-1] and lows[j] < lows[j+1] and lows[j] < lows[j+2]:
                swing_low = lows[j]
                break

        if swing_low is None:
            return []

        # Price bounced from swing low (touched in last 2 bars)
        bounced = lows[i-1] <= swing_low * 1.015 or lows[i-2] <= swing_low * 1.015
        if not bounced:
            return []

        # Current bar: strong bullish move
        bullish_bar = closes[i] > opens[i]
        bar_range = highs[i] - lows[i]
        strong_body = False
        if bar_range > 0:
            strong_body = (closes[i] - opens[i]) / bar_range > 0.55

        # Volume breakout above recent average
        avg_vol = sum(volumes[max(0,i-15):i]) / 15 if i >= 15 else 1
        volume_breakout = volumes[i] > avg_vol * 1.4

        # Trend: uptrend structure (EMAs aligned)
        uptrend_structure = ema20[i] > ema50[i] and ema50[i] > ema100[i]

        # Price not too far below EMA20 (within 4%)
        near_ema = closes[i] > ema20[i] * 0.96

        # Breaking above recent resistance
        resistance_lookback = 12
        recent_resistance = max(highs[i-resistance_lookback:i])
        breaking_resistance = closes[i] > recent_resistance * 0.995

        if (bullish_bar and strong_body and volume_breakout and
            uptrend_structure and near_ema and breaking_resistance):
            actions.append({
                'action': 'open_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
                'size': 1.0,
            })
    else:
        # Exit: below EMA20 with bearish bar or momentum reversal
        below_ema = closes[i] < ema20[i]
        bearish = closes[i] < opens[i]
        momentum_loss = closes[i] < closes[i-1] * 0.975

        if (below_ema and bearish) or momentum_loss:
            actions.append({
                'action': 'close_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
            })

    return actions