← Back to list

atr_expansion_momentum_breakout

ATR Expansion + Momentum Breakout Volatility expansion often signals the start of strong trending moves. Enters when ATR rises above average (expanding volatility) combined

Symbol: ETH | Exchange: Binance

6/6
Profitable Years
+195.0%
Total Return
53.8%
Avg Win Rate
1.08
Avg Sharpe

Year-by-Year Results

Year Return Win Rate Trades Max DD Sharpe
2020 +38.8% 37.5% 16 20.2% 0.72
2021 +10.9% 75.0% 4 11.5% 0.63
2022 +38.6% 57.1% 7 4.3% 1.31
2023 +10.7% 33.3% 12 11.1% 0.54
2024 +60.7% 80.0% 5 3.6% 2.23
2025 +35.3% 40.0% 10 7.8% 1.05

Entry Logic

See strategy file

Exit Logic

See strategy file

Source Code

"""
Strategy: atr_expansion_momentum_breakout
=========================================
ATR Expansion + Momentum Breakout

Volatility expansion often signals the start of strong trending moves.
Enters when ATR rises above average (expanding volatility) combined
with price breaking above recent highs and a strong bar range.

Performance: 6/6 years profitable | Total: +191.0%
2020: +39.8% | 38% WR | 16 trades
2021: +11.0% | 75% WR | 4 trades
2022: +34.6% | 50% WR | 8 trades
2023: +10.7% | 33% WR | 12 trades
2024: +60.6% | 80% WR | 5 trades
2025: +34.3% | 40% WR | 10 trades
"""
import sys
sys.path.insert(0, "/root/trade_rules")
from lib import atr


def init_strategy():
    return {
        'name': 'atr_expansion_momentum_breakout',
        'subscriptions': [
            {'symbol': 'ETHUSDT', 'exchange': 'binance', 'timeframe': '4h'},
        ],
        'parameters': {}
    }


def process_time_step(ctx):
    key = ('ETHUSDT', 'binance')
    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]
    highs = [b.high for b in bars]
    lows = [b.low for b in bars]

    # Calculate ATR
    atr_14 = atr(highs, lows, closes, period=14)

    # Calculate ATR moving average (30-period)
    if i < 43:
        return []
    valid_atr = [x for x in atr_14[i-29:i+1] if x is not None]
    if len(valid_atr) < 25:
        return []
    atr_sma_30 = sum(valid_atr) / len(valid_atr)

    actions = []
    has_position = key in positions

    if not has_position:
        # Entry logic
        if atr_14[i] is None:
            return []

        # 1. Volatility expansion: ATR above 1.3x its 30-bar average
        vol_expanding = atr_14[i] > atr_sma_30 * 1.3

        # 2. Price momentum: close above 15-bar high
        high_15 = max(highs[i-15:i])
        momentum = closes[i] > high_15

        # 3. Strong bar: current bar range > 1.2x average range
        bar_range = bars[i].high - bars[i].low
        avg_range = sum(highs[j] - lows[j] for j in range(i-5, i)) / 5
        strong_bar = bar_range > avg_range * 1.2

        if vol_expanding and momentum and strong_bar:
            actions.append({
                'action': 'open_long',
                'symbol': 'ETHUSDT',
                'exchange': 'binance',
                'size': 1.0,
            })
    else:
        # Exit logic: price breaks below 8-bar low
        low_8 = min(lows[i-8:i])
        if closes[i] < low_8:
            actions.append({
                'action': 'close_long',
                'symbol': 'ETHUSDT',
                'exchange': 'binance',
            })

    return actions