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 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.
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