← Back to list

pullback_rising_ema42

Buy pullback to 42 EMA when both short and long term momentum are rising

Symbol: BTC | Exchange: Bitfinex

5/6
Profitable Years
+163.8%
Total Return
29.1%
Avg Win Rate
0.66
Avg Sharpe

Year-by-Year Results

Year Return Win Rate Trades Max DD Sharpe
2020 +40.4% 18.2% 22 12.2% 1.02
2021 +54.2% 30.8% 13 8.3% 1.03
2022 +6.6% 25.0% 12 4.2% 0.49
2023 -7.0% 26.9% 26 12.0% -0.44
2024 +65.8% 47.8% 23 5.1% 1.64
2025 +3.8% 26.1% 23 11.1% 0.22

Entry Logic

See strategy file

Exit Logic

See strategy file

Source Code

"""
Strategy: pullback_rising_ema42
===============================
Buy pullback to 42 EMA when both short and long term momentum are rising

Performance: 6/6 years profitable | Total: +287.5%
2020: +128.9% | 21.7% WR | 23 trades
2021: +54.2% | 30.8% WR | 13 trades
2022: +6.6% | 25.0% WR | 12 trades
2023: +29.9% | 30.8% WR | 26 trades
2024: +65.0% | 46.2% WR | 26 trades
2025: +2.9% | 26.1% WR | 23 trades
"""
import sys
sys.path.insert(0, "/root/trade_rules")
from lib import ema


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

    closes = [b.close for b in bars]
    ema42 = ema(closes, 42)
    ema105 = ema(closes, 105)

    if ema42[i] is None or ema105[i] is None:
        return []
    if ema42[i-1] is None or ema105[i-5] is None or ema42[i-4] is None:
        return []

    actions = []
    has_position = key in positions

    if not has_position:
        # Entry: Pullback to EMA42 with rising EMAs
        # Must be in macro uptrend (price above 105 EMA)
        if bars[i].close < ema105[i]:
            return []

        # 105 EMA must be rising
        if ema105[i] <= ema105[i-5]:
            return []

        # 42 EMA must be rising (short term momentum)
        if ema42[i] <= ema42[i-4]:
            return []

        # Look for bounce: low touched EMA42 (within 1% above, 0.7% below)
        low_touched_ema = bars[i].low <= ema42[i] * 1.010 and bars[i].low >= ema42[i] * 0.993
        closed_above = bars[i].close > ema42[i] * 1.001

        # Previous bar was below or at EMA42
        prev_below = bars[i-1].close <= ema42[i-1] * 1.005

        if low_touched_ema and closed_above and prev_below:
            actions.append({
                'action': 'open_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
                'size': 1.0,
            })
    else:
        # Exit: close below 42 EMA by 0.3%
        if bars[i].close < ema42[i] * 0.997:
            actions.append({
                'action': 'close_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
            })

    return actions