Skip to main content

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.

premium selling works in stable/elevated vol. doesn’t work in spiking vol.

need system to detect vol regime and pause trading when conditions are bad.

volatility regimes
#

1. low vol (VIX < 15)

  • calm market
  • tight ranges
  • premium is cheap
  • don’t trade (premium not worth risk)

2. normal vol (VIX 15-25)

  • typical conditions
  • decent premium
  • strategies work
  • trade normally

3. elevated vol (VIX 25-35)

  • higher premium
  • wider ranges
  • strategies work great
  • trade aggressively

4. spiking vol (VIX > 35 OR 1-day change > 15%)

  • panic/crash conditions
  • erratic movement
  • strategies fail
  • stop trading immediately

detection code
#

import pandas as pd
import numpy as np
from datetime import datetime, timedelta

class VolatilityRegime:
    def __init__(self, vix_data):
        self.vix = vix_data
        self.current_regime = None

    def calculate_regime(self):
        """Determine current volatility regime"""
        current_vix = self.vix.iloc[-1]['close']
        prev_vix = self.vix.iloc[-2]['close']

        # Calculate 1-day and 5-day change
        vix_1d_change = (current_vix - prev_vix) / prev_vix
        vix_5d = self.vix.iloc[-5:]['close']
        vix_5d_change = (current_vix - vix_5d.iloc[0]) / vix_5d.iloc[0]

        # Determine regime
        if current_vix > 35 or vix_1d_change > 0.15:
            regime = 'spike'
            confidence = 0.95
        elif current_vix > 25:
            if vix_1d_change > 0.10:
                regime = 'spike'
                confidence = 0.85
            else:
                regime = 'elevated'
                confidence = 0.90
        elif current_vix > 15:
            regime = 'normal'
            confidence = 0.85
        else:
            regime = 'low'
            confidence = 0.90

        return {
            'regime': regime,
            'vix': current_vix,
            'vix_1d_change': vix_1d_change,
            'vix_5d_change': vix_5d_change,
            'confidence': confidence,
            'timestamp': datetime.now()
        }

    def should_trade(self):
        """Check if current regime allows trading"""
        regime_info = self.calculate_regime()

        trading_allowed = {
            'low': False,      # Premium too cheap
            'normal': True,    # Trade normally
            'elevated': True,  # Trade aggressively
            'spike': False     # Stop all trading
        }

        return trading_allowed[regime_info['regime']], regime_info

testing on historical data
#

# Load VIX data
vix_data = load_vix_history('2023-01-01', '2023-05-26')

# Test regime detection
detector = VolatilityRegime(vix_data)

# Backtest: what would have happened if I followed regime signals
trades = load_my_trades()
filtered_trades = []

for trade in trades:
    trade_date = trade['entry_date']
    vix_at_entry = vix_data[vix_data['date'] == trade_date]

    detector_at_entry = VolatilityRegime(vix_data[:trade_date])
    should_trade, regime_info = detector_at_entry.should_trade()

    if should_trade:
        filtered_trades.append(trade)
    else:
        print(f"Would have skipped: {trade_date} (regime: {regime_info['regime']})")

# Compare performance
original_pnl = sum(t['pnl'] for t in trades)
filtered_pnl = sum(t['pnl'] for t in filtered_trades)

print(f"Original P&L: ${original_pnl:.0f}")
print(f"Filtered P&L: ${filtered_pnl:.0f}")
print(f"Improvement: ${filtered_pnl - original_pnl:.0f}")

results:

  • original P&L (all trades): +$450
  • filtered P&L (regime-aware): +$2,180
  • improvement: +$1,730

would’ve avoided 7 losing trades in spike conditions.

real-time monitoring
#

import asyncio
import aiohttp

class RealtimeVolMonitor:
    def __init__(self, polygon_key):
        self.polygon_key = polygon_key
        self.current_regime = None
        self.last_check = None

    async def monitor_loop(self):
        """Check VIX every 5 minutes during market hours"""
        while True:
            if self.is_market_hours():
                await self.check_regime()
                await asyncio.sleep(300)  # 5 minutes
            else:
                await asyncio.sleep(3600)  # 1 hour when market closed

    async def check_regime(self):
        """Fetch current VIX and update regime"""
        vix_data = await self.fetch_vix()
        detector = VolatilityRegime(vix_data)

        should_trade, regime_info = detector.should_trade()

        # If regime changed, take action
        if regime_info['regime'] != self.current_regime:
            await self.handle_regime_change(regime_info)

        self.current_regime = regime_info['regime']
        self.last_check = datetime.now()

    async def handle_regime_change(self, regime_info):
        """Take action when regime changes"""
        print(f"Regime changed to: {regime_info['regime']}")

        if regime_info['regime'] == 'spike':
            # Stop all new trades
            await self.pause_strategy()
            await self.send_alert(f"VOL SPIKE: VIX={regime_info['vix']:.1f}, pausing trading")

        elif regime_info['regime'] == 'low':
            # Reduce trading
            await self.reduce_position_sizes()
            await self.send_alert(f"LOW VOL: VIX={regime_info['vix']:.1f}, reducing size")

        elif self.current_regime in ['spike', 'low'] and regime_info['regime'] in ['normal', 'elevated']:
            # Resume trading
            await self.resume_strategy()
            await self.send_alert(f"VOL NORMALIZED: VIX={regime_info['vix']:.1f}, resuming trading")

    async def fetch_vix(self):
        """Get latest VIX data from Polygon"""
        url = "https://api.polygon.io/v2/aggs/ticker/I:VIX/range/1/day/{start}/{end}"
        # ... fetch logic
        pass

    async def send_alert(self, message):
        """Send pushover notification"""
        # ... notification logic
        pass

integration with strategy
#

class PremiumSellingStrategy:
    def __init__(self):
        self.vol_monitor = RealtimeVolMonitor(POLYGON_KEY)
        self.trading_paused = False

    async def run(self):
        # Start vol monitoring in background
        asyncio.create_task(self.vol_monitor.monitor_loop())

        while True:
            should_trade, regime_info = await self.vol_monitor.should_trade()

            if should_trade and not self.trading_paused:
                signals = self.generate_signals()
                await self.execute_signals(signals)
            else:
                print(f"Trading paused (regime: {regime_info['regime']})")

            await asyncio.sleep(300)  # Check every 5 minutes

this week’s performance with detection
#

without regime detection:

  • monday: -$315 (would’ve been flagged as spike, skipped)
  • tuesday: -$280 (would’ve been flagged as spike, skipped)
  • total: -$595

with regime detection:

  • monday: $0 (skipped due to spike)
  • tuesday: $0 (skipped due to spike)
  • total: $0

saved $595 this week alone.

going forward
#

deploying vol regime detection monday.

will monitor for 1 week in paper mode to verify it works.

if results good, going live with it week after.

expected improvement: ~$1,700/year based on backtest.

lessons
#

1. know when NOT to trade

  • most important skill in trading
  • missing good opportunities < avoiding bad losses

2. vol regime matters

  • same strategy, different results in different regimes
  • must adapt to market conditions

3. simple beats complex

  • VIX + 1-day change = 90% of what you need
  • fancier indicators don’t help much

4:18am. vol detection coded and tested. ready to deploy. should’ve built this months ago.

-AK

Related

checking my backtests for overfitting
worried my strategies are overfit to historical data. spent today testing for it. been reading NexusFi backtesting threads about this exact problem. the problem # my backtests look great:
upgraded IV rank filtering
the IV rank problem # my original algo only sells premium when IV rank > 40 IV rank = where current IV sits relative to its 52-week range
down week but recovering
rough couple days. gave back some of last week’s gains. quick update # monday/tuesday market was choppy AF. stopped out twice.
using python async for real-time market data
rewrote my market data pipeline to use async. 3x faster, way cleaner code. the problem # old synchronous code:
how i organize my trading code on github
got asked on r/algotrading how i organize my trading repos. here’s my setup after 4 months of refactoring. repo structure # i have 4 main repos:
correlation risk - learned the hard way
lost $1,400 yesterday because i didn’t track correlation between my positions. dumb mistake. what happened # may 10, 2pm: