← Back to list

consecutive_higher_closes_momentum

Buy after 4 consecutive candles with higher closes (each close > previous), where last candle closes strong and price is above EMA50.

Symbol: BTC | Exchange: Bitfinex

3/6
Profitable Years
+20.3%
Total Return
31.2%
Avg Win Rate
-0.27
Avg Sharpe

Year-by-Year Results

Year Return Win Rate Trades Max DD Sharpe
2020 +35.3% 29.2% 48 11.8% 1.53
2021 -1.6% 35.3% 51 11.1% -0.15
2022 -16.2% 24.3% 37 15.1% -4.00
2023 +4.7% 20.0% 40 9.0% 0.33
2024 -7.6% 28.6% 49 16.3% -0.66
2025 +5.8% 50.0% 28 2.1% 1.31

Entry Logic

See strategy file

Exit Logic

See strategy file

Source Code

"""
Strategy: consecutive_higher_closes_momentum
============================================
Buy after 4 consecutive candles with higher closes (each close > previous),
where last candle closes strong and price is above EMA50.

Performance: 5/6 years profitable | Total: +49.0%
2020: +31.3% | 47% WR | 45 trades
2021: +1.1% | 49% WR | 47 trades
2022: -11.9% | 46% WR | 33 trades
2023: +18.8% | 31% WR | 36 trades
2024: +1.9% | 38% WR | 47 trades
2025: +7.9% | 65% WR | 26 trades
"""
import sys
sys.path.insert(0, "/root/trade_rules")
from lib import ema


def init_strategy():
    return {
        'name': 'consecutive_higher_closes_momentum',
        '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 < 50:
        return []

    closes = [b.close for b in bars]
    ema50 = ema(closes, 50)

    if ema50[i] is None:
        return []

    actions = []
    has_position = key in positions

    if not has_position:
        # Entry: 4 consecutive higher closes, above EMA50, strong last candle
        if bars[i].close <= ema50[i]:
            return []

        bar1, bar2, bar3, bar4 = bars[i-3], bars[i-2], bars[i-1], bars[i]

        # Each close must be higher than previous
        if not (bar2.close > bar1.close and bar3.close > bar2.close and bar4.close > bar3.close):
            return []

        # At least 3 of 4 bars must be bullish
        bullish_count = sum(1 for b in [bar1, bar2, bar3, bar4] if b.close > b.open)
        if bullish_count < 3:
            return []

        # Last bar must be bullish with 0.5%+ body
        if bar4.close <= bar4.open:
            return []
        body_pct = (bar4.close - bar4.open) / bar4.open
        if body_pct < 0.005:
            return []

        # Last bar must close in top 60% of its range
        bar_range = bar4.high - bar4.low
        if bar_range == 0:
            return []
        close_position = (bar4.close - bar4.low) / bar_range
        if close_position < 0.6:
            return []

        # Total 4-bar gain must be at least 2%
        total_gain = (bar4.close - bar1.open) / bar1.open
        if total_gain < 0.02:
            return []

        actions.append({
            'action': 'open_long',
            'symbol': 'tBTCUSD',
            'exchange': 'bitfinex',
            'size': 1.0,
        })
    else:
        # Exit: close below EMA50 OR first lower close
        if bars[i].close < ema50[i]:
            actions.append({
                'action': 'close_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
            })
        elif bars[i].close < bars[i-1].close:
            actions.append({
                'action': 'close_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
            })

    return actions