← Back to list

volatility_spike_hedge VALIDATED PASS

Auto-discovered strategy

Symbol: BTC | Exchange: Bitfinex | Role: hedge

4/6
Profitable Years
+29.6%
Total Return
53.9%
Avg Win Rate
0.00
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 +8.0% 100.0% 1 0.0% 0.00
2021 +16.0% 60.0% 5 4.0% 1.22
2022 -7.3% 28.6% 7 12.3% -0.67
2023 -10.1% 20.0% 5 9.8% -2.99
2024 +15.3% 75.0% 4 0.9% 1.83
2025 +7.6% 40.0% 5 4.3% 0.63

Performance Chart

Loading chart...

Walk-Forward Validation PASS

1/1 Windows Profitable
+3.6% 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 +3.6% OK 2026-01→ongoing +0.0% PASS

AI Review

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

Source Code

"""
Volatility Spike Hedge Strategy
===============================

A hedge strategy that goes SHORT when volatility spikes occur during
established bearish trends. Designed to profit from crash/panic events.

Role: hedge
Exchange: bitfinex
Symbol: tBTCUSD

Entry Conditions (all must be met):
1. Price > 5% below EMA200 (significant weakness)
2. EMA cascade bearish: EMA10 < EMA20 < EMA50 < EMA200
3. Volatility just expanded: ATR10 crosses above 1.2x ATR50
4. RSI(10) < 40 (bearish momentum)
5. Two consecutive red candles (selling pressure)

Exit Conditions (any triggers exit):
1. RSI(10) > 55 (momentum recovery)
2. EMA10 crosses above EMA20 (trend shift)
3. Time stop: 30 bars
4. Take profit: 8%
5. Stop loss: 4%

Train Performance (2024-01-01 to 2025-06-30):
- Total Return: +19.32%
- Sharpe Ratio: 1.60
- Win Rate: 66.7%
- Max Drawdown: 4.0%
- Trades: 6 (very selective)
"""

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


def init_strategy():
    return {
        'name': 'volatility_spike_hedge',
        'role': 'hedge',
        'warmup': 200,
        'subscriptions': [
            {'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
        ],
        'parameters': {
            # EMA periods (round numbers)
            'ema_fast': 10,
            'ema_mid': 20,
            'ema_slow': 50,
            'ema_trend': 200,
            # RSI settings
            'rsi_period': 10,
            'rsi_entry': 40,
            'rsi_exit': 55,
            # ATR volatility detection
            'atr_fast': 10,
            'atr_slow': 50,
            'vol_threshold': 1.2,  # ATR10 > 1.2x ATR50
            # Entry filter
            'weakness_threshold': -5,  # % below EMA200
            # Exit settings
            'take_profit': 8,
            'stop_loss': 4,
            'time_stop': 30,
        }
    }


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

    # Need enough data for EMA200 - now handled by framework via 'warmup' field
    # if i < 200:
    #     return []

    # Extract price data
    closes = [b.close for b in bars[:i+1]]
    highs = [b.high for b in bars[:i+1]]
    lows = [b.low for b in bars[:i+1]]
    opens = [b.open for b in bars[:i+1]]

    # Calculate indicators
    ema_10 = ema(closes, params['ema_fast'])
    ema_20 = ema(closes, params['ema_mid'])
    ema_50 = ema(closes, params['ema_slow'])
    ema_200 = ema(closes, params['ema_trend'])
    rsi_10 = rsi(closes, params['rsi_period'])
    atr_10 = atr(highs, lows, closes, params['atr_fast'])
    atr_50 = atr(highs, lows, closes, params['atr_slow'])

    idx = len(closes) - 1
    rsi_idx = len(rsi_10) - 1

    # Check indicator availability
    if ema_50[idx] is None or ema_200[idx] is None:
        return []
    if ema_10[idx] is None or ema_20[idx] is None:
        return []
    if rsi_10[rsi_idx] is None or atr_10[idx] is None or atr_50[idx] is None:
        return []

    # Entry conditions

    # 1. Significant weakness: price > 5% below EMA200
    below_ema200_pct = (closes[idx] - ema_200[idx]) / ema_200[idx] * 100
    significant_weakness = below_ema200_pct < params['weakness_threshold']

    # 2. EMA cascade bearish
    ema_cascade = (
        ema_10[idx] < ema_20[idx] < ema_50[idx] < ema_200[idx]
    )

    # 3. Volatility just expanded (ATR10 crossed above threshold)
    vol_threshold = params['vol_threshold']
    vol_expanding = atr_10[idx] > vol_threshold * atr_50[idx] if atr_50[idx] > 0 else False

    prev_atr10 = atr_10[idx-1] if idx > 0 else None
    prev_atr50 = atr_50[idx-1] if idx > 0 else None
    vol_was_low = (
        prev_atr10 < vol_threshold * prev_atr50
        if prev_atr10 and prev_atr50 and prev_atr50 > 0
        else True
    )
    vol_just_expanded = vol_expanding and vol_was_low

    # 4. RSI bearish
    rsi_bearish = rsi_10[rsi_idx] < params['rsi_entry']

    # 5. Consecutive red candles
    consecutive_red = (
        closes[idx] < opens[idx] and
        closes[idx-1] < opens[idx-1]
        if idx > 0 else False
    )

    actions = []

    if key not in positions:
        # Entry: all conditions must be met
        if (significant_weakness and ema_cascade and
            vol_just_expanded and rsi_bearish and consecutive_red):
            state['entry_high'] = closes[idx]
            actions.append({
                'action': 'open_short',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
                'size': 1.0,
                'take_profit_pct': params['take_profit'],
                'stop_loss_pct': params['stop_loss'],
            })
    else:
        pos = positions[key]
        bars_held = i - pos.entry_bar

        # Track high for potential trailing stop
        if 'entry_high' in state:
            state['entry_high'] = max(state['entry_high'], closes[idx])

        # Exit conditions

        # 1. RSI recovery
        rsi_recovery = rsi_10[rsi_idx] > params['rsi_exit']

        # 2. EMA10 crosses above EMA20 (momentum shift)
        prev_ema10 = ema_10[idx-1] if idx > 0 else None
        prev_ema20 = ema_20[idx-1] if idx > 0 else None
        ema_cross_up = (
            ema_10[idx] > ema_20[idx] and
            prev_ema10 <= prev_ema20
            if prev_ema10 and prev_ema20 else False
        )

        # 3. Time stop
        time_exit = bars_held >= params['time_stop']

        if rsi_recovery or ema_cross_up or time_exit:
            actions.append({
                'action': 'close_short',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
            })

    return actions


# Testing
if __name__ == '__main__':
    from strategy import backtest_strategy
    results, profitable, _ = backtest_strategy(init_strategy, process_time_step)