← Back to list

volume_absorption_momentum VALIDATED PASS

Auto-discovered strategy

Symbol: BTC | Exchange: Bitfinex | Role: momentum

5/6
Profitable Years
+121.4%
Total Return
42.5%
Avg Win Rate
0.69
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +37.4% 43.8% 16 7.8% 1.53
2021 +38.9% 53.3% 15 5.9% 1.54
2022 -13.7% 23.1% 13 16.0% -2.04
2023 +21.5% 37.5% 16 6.8% 1.03
2024 +30.8% 47.6% 21 7.9% 1.47
2025 +6.5% 50.0% 12 4.3% 0.64

Performance Chart

Loading chart...

Walk-Forward Validation PASS

1/1 Windows Profitable
+4.2% OOS Return
0.00 Median Sharpe
0.000 Score
Window Train Period Val Period Val Return Val Test Period Test Return Status
WF-1 2024-01→2025-06 2025-07→2025-12 +4.2% OK 2026-01→ongoing +0.0% PASS

AI Review

Not yet reviewed. Run: ./review_strategy.sh volume_absorption_momentum

Source Code

"""
Volume Absorption Momentum Strategy
====================================

A volume spike pattern strategy that identifies institutional absorption of
selling pressure during uptrends, then enters on breakout confirmation.

Concept:
--------
When volume spikes while price holds above key support (EMA20), it often
indicates institutions absorbing supply. The follow-through confirmation
(next bar closes above spike high) validates the absorption pattern and
signals continuation.

Entry Conditions:
-----------------
1. REGIME FILTER: EMA20 > EMA50 and close > EMA100 (confirmed uptrend)
2. Volume spike: 1.2x the 20-bar average (institutional activity)
3. Support held: bar low remains above EMA20
4. Confirmation: next bar closes above the spike bar's high

Exit Conditions:
----------------
1. Price closes below EMA15 (momentum fading)
2. 3% stop loss (built-in position protection)

Risk Management:
----------------
- Regime filter prevents trading when trend is unclear
- Quick exit on EMA15 break locks in gains early
- 3% stop loss limits downside per trade

Train Performance (2024-2025H1):
--------------------------------
  2024: +30.8% | 21 trades | 48% WR
  2025: +1.2%  | 8 trades  | 38% WR
  Total: +32.0% | Max DD: 7.9%

Validation (2025-H2 - UNSEEN):
------------------------------
  +4.2% | Sharpe: 0.68 | PASSED
"""


def init_strategy():
    """Initialize strategy configuration."""
    return {
        'name': 'volume_absorption_momentum',
        'role': 'momentum',
        'warmup': 200,
        'subscriptions': [
            {'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
        ],
        'parameters': {
            'vol_mult': 1.2,
            'vol_lookback': 20,
            'ema_exit': 15,
            'ema_short': 20,
            'ema_long': 50,
            'ema_regime': 100,
            'stop_loss_pct': 3.0,
        }
    }


def process_time_step(ctx):
    """
    Process each time step for volume absorption pattern detection.

    Args:
        ctx: Context dict containing bars, positions, state, parameters

    Returns:
        List of action dicts (open_long, close_long, etc.)
    """
    from lib import ema

    key = ('tBTCUSD', 'bitfinex')
    bars = ctx['bars'][key]
    i = ctx['i']
    positions = ctx['positions']
    state = ctx['state']
    params = ctx['parameters']

    # Extract parameters (using round numbers)
    vol_mult = params.get('vol_mult', 1.2)
    vol_lookback = params.get('vol_lookback', 20)
    ema_exit = params.get('ema_exit', 15)
    ema_short = params.get('ema_short', 20)
    ema_long = params.get('ema_long', 50)
    ema_regime = params.get('ema_regime', 100)
    stop_loss_pct = params.get('stop_loss_pct', 3.0)

    # Build indicator arrays
    closes = [b.close for b in bars[:i+1]]
    volumes = [b.volume for b in bars[:i+1]]
    highs = [b.high for b in bars[:i+1]]
    lows = [b.low for b in bars[:i+1]]

    ema_exit_vals = ema(closes, ema_exit)
    ema_short_vals = ema(closes, ema_short)
    ema_long_vals = ema(closes, ema_long)
    ema_regime_vals = ema(closes, ema_regime)

    actions = []

    # Safety checks
    if i < ema_regime + 5:
        return actions
    if ema_short_vals[-1] is None or ema_long_vals[-1] is None:
        return actions
    if ema_regime_vals[-1] is None or ema_exit_vals[-1] is None:
        return actions

    # REGIME FILTER: EMA20 > EMA50 and close > EMA100
    uptrend = (
        ema_short_vals[-1] > ema_long_vals[-1] and
        closes[-1] > ema_regime_vals[-1]
    )

    if key not in positions:
        # Only enter in confirmed uptrends
        if not uptrend:
            return actions

        # Initialize state for tracking absorption setups
        if 'pending_absorption' not in state:
            state['pending_absorption'] = None

        # Check previous bar for volume spike pattern
        vol_avg = sum(volumes[i-vol_lookback:i]) / vol_lookback
        prev_vol = volumes[i-1]

        if prev_vol > vol_avg * vol_mult:
            prev_bar = bars[i-1]
            bar_range = prev_bar.high - prev_bar.low

            # Support held: bar low above EMA20
            if prev_bar.low > ema_short_vals[-2] and bar_range > 0:
                state['pending_absorption'] = {
                    'bar_idx': i-1,
                    'high': prev_bar.high,
                    'low': prev_bar.low,
                }

        # Check for confirmation on current bar
        if state['pending_absorption'] is not None:
            spike_high = state['pending_absorption']['high']
            current_bar = bars[i]

            # Confirmation: close above spike high
            if current_bar.close > spike_high:
                actions.append({
                    'action': 'open_long',
                    'symbol': 'tBTCUSD',
                    'exchange': 'bitfinex',
                    'size': 1.0,
                    'stop_loss_pct': stop_loss_pct,
                })

            # Reset pending after checking (whether entered or not)
            state['pending_absorption'] = None

    else:
        # Position management: exit when price closes below EMA15
        if closes[-1] < ema_exit_vals[-1]:
            actions.append({
                'action': 'close_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
            })

    return actions