← Back to list

vol_compress_accel

Volatility Compression Acceleration Breakout - ETHUSDT A momentum breakout strategy that identifies volatility compression followed by explosive moves. Core Concept:

Symbol: ETH | Exchange: Binance

5/6
Profitable Years
+119.5%
Total Return
48.6%
Avg Win Rate
0.90
Avg Sharpe

Year-by-Year Results

Year Return Win Rate Trades Max DD Sharpe
2020 +52.4% 52.8% 36 5.5% 2.13
2021 +20.1% 60.0% 30 8.2% 1.12
2022 +11.6% 55.6% 27 18.6% 0.62
2023 +8.6% 50.0% 22 6.1% 0.63
2024 -0.7% 33.3% 27 9.5% -0.05
2025 +27.5% 40.0% 25 10.6% 0.98

Entry Logic

See strategy file

Exit Logic

See strategy file

Source Code

"""
Strategy: vol_compress_accel
============================
Volatility Compression Acceleration Breakout - ETHUSDT

A momentum breakout strategy that identifies volatility compression followed by explosive moves.

Core Concept:
- Detects periods of low volatility (ATR compression in bottom 35%)
- Waits for acceleration breakout with volume confirmation
- Combines price acceleration, velocity, and volume percentiles

Entry Conditions:
1. ATR was compressed (< 35th percentile) within last 12 bars
2. Price acceleration > 1.5% (momentum of momentum)
3. Velocity > 3% (7-bar rate of change)
4. Volume above 70th percentile (past 60 bars)
5. Breaking above 25-bar high (within 1%)

Exit Conditions:
- Maximum hold: 18 bars (72 hours / 3 days on 4h timeframe)
- Velocity turns negative (< -0.5%)
- Acceleration reverses strongly (< -2.0%)

Performance: 5/6 years profitable | Total: +46.8%
2020: +28.2% | 70% WR | 27 trades
2021: +14.6% | 67% WR | 27 trades
2022: +4.2% | 52% WR | 27 trades
2023: -10.9% | 44% WR | 27 trades
2024: +0.6% | 37% WR | 27 trades
2025: +10.1% | 41% WR | 27 trades
"""
import sys
sys.path.insert(0, "/root/trade_rules")


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


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 < 65:
        return []

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

    # Calculate ATR using EMA smoothing
    atr_vals = [0.0] * len(bars)
    for j in range(1, i + 1):
        tr = max(
            highs[j] - lows[j],
            abs(highs[j] - closes[j-1]),
            abs(lows[j] - closes[j-1])
        )
        if j < 14:
            atr_vals[j] = tr
        else:
            atr_vals[j] = (atr_vals[j-1] * 13 + tr) / 14

    # Calculate velocity: 7-bar rate of change (%)
    velocity = [0.0] * len(bars)
    for j in range(7, i + 1):
        velocity[j] = (closes[j] - closes[j-7]) / closes[j-7] * 100

    # Calculate acceleration: change in velocity over 4 bars
    acceleration = [0.0] * len(bars)
    for j in range(11, i + 1):
        acceleration[j] = velocity[j] - velocity[j-4]

    # ATR as % of price (normalized volatility)
    atr_pct = [0.0] * len(bars)
    for j in range(i + 1):
        if closes[j] > 0:
            atr_pct[j] = (atr_vals[j] / closes[j]) * 100

    # ATR percentile: where current ATR ranks in last 60 bars
    atr_percentile = [0.0] * len(bars)
    for j in range(60, i + 1):
        window = atr_pct[j-60:j]
        atr_percentile[j] = sum(1 for x in window if x < atr_pct[j]) / len(window) * 100

    # Volume 70th percentile over 60 bars
    vol_percentile_70 = [0.0] * len(bars)
    for j in range(60, i + 1):
        vol_window = sorted(volumes[j-60:j])
        vol_percentile_70[j] = vol_window[int(len(vol_window) * 0.7)]

    # 25-bar high for breakout detection
    high_25 = [0.0] * len(bars)
    for j in range(25, i + 1):
        high_25[j] = max(highs[j-25:j])

    actions = []
    has_position = key in positions

    if not has_position:
        # Entry: volatility compression + acceleration breakout
        # 1. ATR was compressed (< 35th percentile) within last 12 bars
        compression_found = False
        for lookback in range(1, 13):
            if i - lookback >= 60 and atr_percentile[i - lookback] < 35:
                compression_found = True
                break

        if not compression_found:
            return []

        # 2. Price acceleration > 1.5%
        if acceleration[i] < 1.5:
            return []

        # 3. Positive velocity > 3%
        if velocity[i] < 3.0:
            return []

        # 4. Volume above 70th percentile
        if volumes[i] < vol_percentile_70[i]:
            return []

        # 5. Breaking above recent highs (within 1%)
        if closes[i] < high_25[i] * 0.99:
            return []

        actions.append({
            'action': 'open_long',
            'symbol': 'ETHUSDT',
            'exchange': 'binance',
            'size': 1.0,
        })
    else:
        # Exit conditions
        pos = positions[key]
        bars_held = i - pos.entry_bar

        # 1. Max holding period: 18 bars
        if bars_held >= 18:
            actions.append({
                'action': 'close_long',
                'symbol': 'ETHUSDT',
                'exchange': 'binance',
            })
        # 2. Velocity turns negative (momentum reversal)
        elif velocity[i] < -0.5:
            actions.append({
                'action': 'close_long',
                'symbol': 'ETHUSDT',
                'exchange': 'binance',
            })
        # 3. Acceleration reverses strongly
        elif acceleration[i] < -2.0:
            actions.append({
                'action': 'close_long',
                'symbol': 'ETHUSDT',
                'exchange': 'binance',
            })

    return actions