← Back to list

doge_vol_expansion VALIDATED PASS

Auto-discovered strategy

Symbol: DOGE | Exchange: Binance | Role: momentum

4/6
Profitable Years
+116.1%
Total Return
34.1%
Avg Win Rate
0.54
Avg Sharpe

Year-by-Year Results

Click a year to view chart

Year Return Win Rate Trades Max DD Sharpe
2020 -5.9% 20.0% 25 22.2% -0.22
2021 +8.2% 30.0% 40 26.0% 0.20
2022 +22.0% 41.7% 12 14.5% 0.91
2023 -9.0% 31.8% 22 22.9% -0.36
2024 +87.9% 45.7% 35 16.7% 2.23
2025 +12.9% 35.7% 14 16.7% 0.51

Performance Chart

Loading chart...

Walk-Forward Validation PASS

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

AI Review

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

Source Code

"""
DOGE Volatility Expansion Strategy
===================================

Concept: Enter DOGEUSDT when volatility expands from compression during bullish trends.

Market Principle:
- Volatility is mean-reverting: periods of low volatility (compression) are followed
  by periods of high volatility (expansion)
- The direction of expansion often follows the prevailing trend
- By filtering for bullish regime (EMA50 > EMA200), we trade expansion breakouts
  in the direction of the dominant trend

Entry Conditions (ALL must be true):
1. Regime filter: EMA50 > EMA200 (bullish macro trend)
2. Recent compression: Bollinger Band width percentile < 20 in last 10 bars
3. Current expansion: BB width percentile increasing (expanding volatility)
4. Price momentum: Close > EMA20 (short-term bullish)
5. Volume confirmation: Volume >= 20-bar average

Exit Conditions:
1. Take profit: 10% gain (captures expansion moves)
2. Stop loss: 5% loss (limits downside)
3. Signal exit: Close < EMA50 (trend weakening)

Risk Management:
- Strict regime filter prevents trading in bear markets
- 5% stop loss caps individual trade risk
- No trades during volatility compression (waits for breakout)

Train Performance (2024-01 to 2025-06):
- 2024: +87.9% | 35 trades | 46% WR
- 2025 (H1): +5.0% | 6 trades | 33% WR
- Total: +93% | Max DD: 16.7% | Sharpe: 2.17
"""

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

# Global indicator cache
_indicator_cache = {}


def init_strategy():
    return {
        'name': 'doge_vol_expansion',
        'role': 'momentum',  # Volatility breakout is momentum strategy
        'warmup': 200,  # Need 200 bars for EMA200
        'subscriptions': [
            {'symbol': 'DOGEUSDT', 'exchange': 'binance', 'timeframe': '4h'},
        ],
        'parameters': {
            # EMA periods for trend detection
            'ema_fast': 20,
            'ema_mid': 50,
            'ema_slow': 200,
            # Bollinger Band settings
            'bb_period': 20,
            # Compression detection
            'compression_lookback': 10,
            'compression_threshold': 20,  # percentile
            # Risk management
            'take_profit_pct': 10,
            'stop_loss_pct': 5,
        }
    }


def compute_all_indicators(bars):
    """Compute all indicators once per bar sequence."""
    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]

    # EMAs for trend
    ema20 = ema(closes, 20)
    ema50 = ema(closes, 50)
    ema200 = ema(closes, 200)

    # Bollinger Bands for volatility
    middle, upper, lower = bollinger_bands(closes, 20, 2.0)

    # Volume average
    vol_sma = sma(volumes, 20)

    # BB width as % of price (volatility measure)
    bb_width = [
        (upper[i] - lower[i]) / middle[i] * 100 if middle[i] else None
        for i in range(len(closes))
    ]

    # Rolling percentile of BB width (compression detection)
    bb_percentile = [None] * len(closes)
    for i in range(50, len(closes)):
        window = [bb_width[j] for j in range(i-50, i) if bb_width[j] is not None]
        if window and bb_width[i]:
            sorted_w = sorted(window)
            percentile = sum(1 for w in sorted_w if w <= bb_width[i]) / len(sorted_w) * 100
            bb_percentile[i] = percentile

    return {
        'closes': closes,
        'volumes': volumes,
        'ema20': ema20,
        'ema50': ema50,
        'ema200': ema200,
        'bb_percentile': bb_percentile,
        'vol_sma': vol_sma,
    }


def process_time_step(ctx):
    key = ('DOGEUSDT', 'binance')
    bars = ctx['bars'][key]
    i = ctx['i']
    positions = ctx['positions']
    params = ctx['parameters']

    # Compute indicators (cached)
    cache_key = id(bars)
    if cache_key not in _indicator_cache:
        _indicator_cache[cache_key] = compute_all_indicators(bars)
    ind = _indicator_cache[cache_key]

    actions = []
    has_position = key in positions

    if not has_position:
        # === ENTRY LOGIC ===

        # Safety check - ensure all indicators exist
        if not all([
            ind['ema50'][i], ind['ema200'][i], ind['ema20'][i],
            ind['bb_percentile'][i], ind['vol_sma'][i]
        ]):
            return []

        # 1. REGIME FILTER: Bullish trend only
        # EMA50 > EMA200 indicates bullish macro environment
        if ind['ema50'][i] <= ind['ema200'][i]:
            return []

        # 2. RECENT COMPRESSION: BB percentile < 20 in last 10 bars
        # Volatility was squeezed recently
        was_compressed = False
        for j in range(max(50, i-10), i-2):
            if ind['bb_percentile'][j] and ind['bb_percentile'][j] < 20:
                was_compressed = True
                break
        if not was_compressed:
            return []

        # 3. CURRENT EXPANSION: BB percentile increasing
        # Volatility is now expanding (breakout potential)
        if ind['bb_percentile'][i-3] and ind['bb_percentile'][i] < ind['bb_percentile'][i-3]:
            return []

        # 4. PRICE MOMENTUM: Close > EMA20
        # Price showing short-term strength
        if ind['closes'][i] <= ind['ema20'][i]:
            return []

        # 5. VOLUME CONFIRMATION: Volume >= average
        # Institutional participation
        if ind['volumes'][i] < ind['vol_sma'][i]:
            return []

        # All conditions met - enter long
        actions.append({
            'action': 'open_long',
            'symbol': 'DOGEUSDT',
            'exchange': 'binance',
            'size': 1.0,
            'take_profit_pct': params['take_profit_pct'],
            'stop_loss_pct': params['stop_loss_pct'],
        })

    else:
        # === EXIT LOGIC ===
        # (TP/SL handled by framework)

        # Signal exit: Close below EMA50 = trend weakening
        if ind['ema50'][i] and ind['closes'][i] < ind['ema50'][i]:
            actions.append({
                'action': 'close_long',
                'symbol': 'DOGEUSDT',
                '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)