Skip to main content

volatility regime detection - when to switch strategies

the market doesn’t care what strategy you’re running.

it runs whatever regime it wants.

your job is to detect the regime and adapt.

why regime matters
#

every strategy has conditions where it crushes and conditions where it bleeds.

the pattern:

  • momentum works in trends
  • mean reversion works in ranges
  • vol selling prints in low vol
  • all three can blow up if you’re in the wrong regime

ran my momentum algo through a high vol spike last august. lost 3 weeks of gains in 2 days.

lesson learned.

the detection framework
#

built a simple regime classifier using VIX.

yeah i know. “VIX is backward looking blah blah.”

it works. that’s all i care about.

VIX Regime Detection

vix regimes over last 7 months. blue shading = low vol (good for momentum). red shading = high vol (switch to mean reversion).

my thresholds:

  • VIX < 16 = low vol regime
  • VIX 16-24 = normal
  • VIX > 24 = high vol regime

simple. not trying to predict moves. just classifying current state.

the switching logic
#

from enum import Enum
from dataclasses import dataclass
from typing import Dict, Optional, Callable
import numpy as np

class VolRegime(Enum):
    LOW = "low_volatility"
    NORMAL = "normal"
    HIGH = "high_volatility"
    TRANSITIONING = "regime_change"

@dataclass
class RegimeConfig:
    low_vol_threshold: float = 16.0
    high_vol_threshold: float = 24.0
    lookback_days: int = 5
    min_days_for_switch: int = 2
    transition_buffer: float = 1.5  # buffer zone

class RegimeDetector:
    def __init__(self, config: RegimeConfig):
        self.config = config
        self.vix_history = []
        self.current_regime = VolRegime.NORMAL
        self.days_in_regime = 0
        self.strategy_allocations = {
            VolRegime.LOW: {"momentum": 0.50, "mean_rev": 0.15, "vol_sell": 0.35},
            VolRegime.NORMAL: {"momentum": 0.35, "mean_rev": 0.35, "vol_sell": 0.30},
            VolRegime.HIGH: {"momentum": 0.15, "mean_rev": 0.55, "vol_sell": 0.10},
            VolRegime.TRANSITIONING: {"momentum": 0.25, "mean_rev": 0.25, "vol_sell": 0.20}
        }

    def update(self, vix_value: float) -> VolRegime:
        """Update regime based on new VIX reading"""
        self.vix_history.append(vix_value)
        if len(self.vix_history) > self.config.lookback_days:
            self.vix_history = self.vix_history[-self.config.lookback_days:]

        new_regime = self._classify_regime(vix_value)

        if new_regime != self.current_regime:
            self.days_in_regime = 1
            # Don't switch immediately - wait for confirmation
            if self._confirm_regime_change(new_regime):
                self.current_regime = new_regime
        else:
            self.days_in_regime += 1

        return self.current_regime

    def _classify_regime(self, vix: float) -> VolRegime:
        """Raw classification without transition logic"""
        if vix < self.config.low_vol_threshold:
            return VolRegime.LOW
        elif vix > self.config.high_vol_threshold:
            return VolRegime.HIGH
        else:
            return VolRegime.NORMAL

    def _confirm_regime_change(self, proposed: VolRegime) -> bool:
        """Require multiple days before switching"""
        if len(self.vix_history) < self.config.min_days_for_switch:
            return False

        recent = self.vix_history[-self.config.min_days_for_switch:]
        all_agree = all(
            self._classify_regime(v) == proposed for v in recent
        )
        return all_agree

    def get_allocations(self) -> Dict[str, float]:
        """Get strategy allocations for current regime"""
        return self.strategy_allocations.get(
            self.current_regime,
            self.strategy_allocations[VolRegime.NORMAL]
        )

    def get_regime_stats(self) -> dict:
        """Return current regime analysis"""
        avg_vix = np.mean(self.vix_history) if self.vix_history else 0
        return {
            "current_regime": self.current_regime.value,
            "days_in_regime": self.days_in_regime,
            "avg_vix_5d": round(avg_vix, 2),
            "allocations": self.get_allocations()
        }

the key is not jumping too fast.

false signals = whipsaws = losses.

require 2 days of confirmation before switching.

strategy performance by regime
#

backtested 2 years of data.

Strategy by Regime

avg monthly return by strategy and vol regime. momentum crushes in low vol. mean reversion crushes in high vol. vol selling gets murdered when vol spikes.

what the data shows:

  • momentum: +2.1% low vol, -0.8% high vol
  • mean reversion: +0.6% low vol, +2.4% high vol
  • vol selling: +1.8% low vol, -2.1% high vol
  • all weather: consistent 0.7-1.2% across all regimes

the “all weather” approach is boring but survives everything.

current regime
#

as of today (jan 16), VIX sitting around 17.

regime: normal (slight low-vol bias)

running 40% momentum, 30% mean reversion, 30% vol selling.

if VIX drops below 16 for 2+ days, shifting to 50/15/35.

if VIX spikes above 24, switching to 15/55/10.

implementation notes
#

this isn’t rocket science.

the edge is consistency:

  • check VIX daily at market close
  • run classifier
  • adjust allocations if regime changes
  • don’t overthink it

been discussing regime-based allocation with other algo traders on NexusFi - the consensus is that simple regime models outperform complex ones. over-engineering leads to overfitting.

what i’m watching
#

next week CPI data (jan 17 or 18, forget exact date).

inflation surprises = vol spikes = regime change potential.

algo is ready either way.


2:31am thursday. volatility regime detection is the simplest alpha i’ve found. VIX under 16 = run momentum hard. VIX over 24 = switch to mean reversion. two years of backtesting confirms it. currently in normal regime. watching CPI next week.

-AK

Related

vix term structure algo - contango/backwardation trading
been researching VIX term structure trades. contango vs backwardation. predictable patterns. finally got an algo working. the concept # contango: front month VIX < back month VIX
earnings volatility filter - implementation and early results
been running the earnings volatility filter for a week now. early results are promising. the problem # earnings = binary events. stock moves 5-10% or nothing.
building volatility regime detection
need to stop trading when volatility spikes. building detection system. the problem # this week VIX spiked 18% in 2 days. my strategies got stopped out twice.
first week 2026 - january momentum algo kicking off
new year. new momentum. first real trading week of 2026 in the books. january effect algo activated. the january effect # some people think it’s BS.
year-end portfolio rebalancing algo - detecting institutional flows
december means institutional rebalancing. pension funds, endowments, mutual funds all adjusting. built an algo to detect and trade the flows. the concept # year-end rebalancing patterns:
sector rotation algo - implementation with relative strength scoring
been working on a sector rotation algo. concept: own the strongest sectors, short the weakest. simple in theory. complex in implementation. the core idea # sectors rotate in predictable cycles.