← Back to list

volume_spike_regime_btc VALIDATED FAIL

Auto-discovered strategy

Symbol: BTC | Exchange: Binance | Role: momentum

5/6
Profitable Years
+103.9%
Total Return
43.4%
Avg Win Rate
0.95
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +22.9% 37.0% 27 11.4% 1.05
2021 +21.9% 60.0% 10 4.3% 1.64
2022 -4.4% 28.6% 7 8.5% -0.46
2023 +14.8% 33.3% 24 12.2% 0.73
2024 +33.4% 57.7% 26 14.7% 1.40
2025 +15.4% 43.8% 16 3.9% 1.33

Performance Chart

Loading chart...

Walk-Forward Validation FAIL

0/1 Windows Profitable
-1.4% 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 -1.4% FAIL 2026-01→ongoing +0.0% FAIL

AI Review

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

Source Code

"""
Volume Spike Regime Breakout Strategy (BTCUSDT)
================================================

A momentum strategy that trades volume spike breakouts in established uptrends.
Uses strict regime filter to stay flat during bear markets.

Entry Conditions (ALL required):
1. Regime: EMA50 > EMA200 (bullish macro trend)
2. Volume spike: Current volume > 2x the 20-bar SMA
3. Bullish bar: Close > Open
4. Breakout: Current bar high exceeds 10-bar high
5. Price support: Close above EMA10

Exit Conditions (ANY triggers exit):
1. Close below EMA10 (momentum fade)
2. 4% trailing stop from 10-bar high (profit protection)
3. Regime change: EMA50 crosses below EMA200

Risk Management:
- 4% hard stop loss per trade
- Regime filter ensures no trades in bear markets
- Multiple exit triggers prevent holding losers

Strategy Role: MOMENTUM
- Designed to capture bull market breakouts
- Allowed to lose up to -15% in validation (bear market)
- Expected to stay flat when EMA50 < EMA200

Performance (TRAIN: 2024-2025H1):
- 2024: +33.4% | 26 trades | 1.40 Sharpe | 14.7% max DD
- 2025H1: +9.3% | 8 trades | 1.04 Sharpe | 3.9% max DD
- Total: +42.6% | Both train periods profitable
"""

import sys
sys.path.insert(0, '/root/trade_rules')
from lib import ema, sma


def init_strategy():
    """Initialize strategy configuration."""
    return {
        'name': 'volume_spike_regime_btc',
        'role': 'momentum',
        'warmup': 200,
        'subscriptions': [
            {'symbol': 'BTCUSDT', 'exchange': 'binance', 'timeframe': '4h'},
        ],
        'parameters': {
            'vol_mult': 2.0,       # Volume spike threshold (2x average)
            'breakout_bars': 10,   # Bars for breakout high
            'exit_ema': 10,        # EMA period for exit
            'trail_pct': 4.0,      # Trailing stop percentage
            'stop_pct': 4.0,       # Hard stop loss percentage
        }
    }


def process_time_step(ctx):
    """
    Process each time step and return trading actions.

    Entry: Volume spike + breakout + uptrend regime
    Exit: EMA cross or trailing stop or regime change
    """
    key = ('BTCUSDT', 'binance')
    bars = ctx['bars'][key]
    i = ctx['i']
    positions = ctx['positions']
    params = ctx['parameters']

    # Extract parameters
    vol_mult = params['vol_mult']
    breakout_bars = params['breakout_bars']
    exit_ema_period = params['exit_ema']
    trail_pct = params['trail_pct']
    stop_pct = params['stop_pct']

    # Build price arrays up to current bar
    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]]

    # Calculate indicators
    vol_sma20 = sma(volumes, 20)
    ema_exit = ema(closes, exit_ema_period)
    ema50 = ema(closes, 50)
    ema200 = ema(closes, 200)

    actions = []

    if key not in positions:
        # ========== ENTRY LOGIC ==========

        # Need valid indicators
        if (vol_sma20[i] is None or ema_exit[i] is None or
            ema50[i] is None or ema200[i] is None):
            return []
        if vol_sma20[i] == 0:
            return []

        bar = bars[i]

        # 1. REGIME FILTER: EMA50 > EMA200 (uptrend only)
        if ema50[i] <= ema200[i]:
            return []

        # 2. VOLUME SPIKE: > 2x the 20-bar average
        vol_ratio = volumes[i] / vol_sma20[i]
        if vol_ratio < vol_mult:
            return []

        # 3. BULLISH BAR: close > open
        if bar.close <= bar.open:
            return []

        # 4. BREAKOUT: high > 10-bar high
        if i < breakout_bars:
            return []
        high_n = max(highs[i-breakout_bars:i])
        if bar.high <= high_n:
            return []

        # 5. PRICE SUPPORT: close above EMA
        if bar.close < ema_exit[i]:
            return []

        # All conditions met - enter long
        actions.append({
            'action': 'open_long',
            'symbol': 'BTCUSDT',
            'exchange': 'binance',
            'size': 1.0,
            'stop_loss_pct': stop_pct,
        })

    else:
        # ========== EXIT LOGIC ==========

        pos = positions[key]
        bars_held = i - pos.entry_bar

        # Hold for at least 1 bar
        if bars_held < 1:
            return []

        bar = bars[i]

        # Exit 1: Close below exit EMA
        if ema_exit[i] and bar.close < ema_exit[i]:
            actions.append({
                'action': 'close_long',
                'symbol': 'BTCUSDT',
                'exchange': 'binance',
            })
            return actions

        # Exit 2: Trailing stop below recent high
        if i >= breakout_bars:
            recent_high = max(highs[i-breakout_bars:i+1])
            trail_stop = recent_high * (1 - trail_pct/100)
            if bar.close < trail_stop:
                actions.append({
                    'action': 'close_long',
                    'symbol': 'BTCUSDT',
                    'exchange': 'binance',
                })
                return actions

        # Exit 3: Regime change
        if ema50[i] and ema200[i] and ema50[i] < ema200[i]:
            actions.append({
                'action': 'close_long',
                'symbol': 'BTCUSDT',
                'exchange': 'binance',
            })

    return actions


# For standalone testing
if __name__ == '__main__':
    from strategy import backtest_strategy
    results, profitable, _ = backtest_strategy(init_strategy, process_time_step, verbose=True)