VIX Regime Detector
Classify the VIX level into one of four regimes — Normal / Sweet Spot / Danger / Panic — and report which strategy types are favored, blocked, or signaled by each regime.
Thresholds are not arbitrary: they come from backtests on S&P 500 mean-reversion entries where the 25–30 VIX band has the worst hit rate (~19.6%) and the 20–25 band the best.
Note: Not financial advice. Backtest results are historical and do not guarantee future returns.
The four regimes
| Regime | VIX range | Mean-reversion (RSI-buy-the-dip) | Momentum (breakout) | Special signal |
|---|---|---|---|---|
| Normal | VIX < 20 | ✅ Allowed | ✅ Allowed | — |
| Sweet Spot | 20 ≤ VIX < 25 | ⭐ Best historical hit rate | ✅ Allowed | — |
| Danger | 25 ≤ VIX < 30 | ❌ Block (worst historical hit rate, ~19.6%) | ❌ Block | — |
| Panic | VIX ≥ 30 | ✅ Allowed (post-capitulation reversal) | ❌ Block (still volatile) | 🔔 Panic-buy signal for index ETFs (SPY/QQQ) |
Why these thresholds
- 20 — separates calm markets from elevated-fear markets.
- 25 — historically associated with sharp drawdowns where reversion fails.
- 30 — capitulation level. Index ETFs entering here have shown high mean-reversion success in backtest (e.g. SPY: 96.4% win rate, average +11.5% over ~88 days, Sharpe ~1.21). Exit at VIX < 20 to capture the fear-normalization arc.
These numbers reflect specific backtests on S&P 500 mean-reversion strategies between 2015–2024. Different universes (small caps, single stocks, options) may behave differently.
Step 1: Ensure yfinance is available
import subprocess, sys
try:
import yfinance # noqa: F401
except ImportError:
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", "yfinance"])
Step 2: Use the helper
from vix_regime import get_current_vix, classify_vix
vix = get_current_vix() # → 17.2
regime = classify_vix(vix)
# {
# "regime": "NORMAL",
# "vix": 17.2,
# "allow_mean_reversion": True,
# "allow_momentum": True,
# "panic_signal": False,
# "description": "VIX < 20 — calm market. Mean-reversion and momentum both allowed."
# }
classify_vix accepts a number; get_current_vix fetches today's close from yfinance (^VIX).
Step 3: Use the regime in a pipeline
regime = classify_vix(get_current_vix())
if not regime["allow_mean_reversion"]:
skip_strategy_a()
if regime["panic_signal"]:
enter_index_etf_position("SPY", reason="VIX panic buy")
Step 4: Respond to the user
When the user asks about the VIX, present the four-regime table briefly, then state the current regime and its implication:
Current VIX is 17.2 → NORMAL regime. Calm market environment. Both mean-reversion and momentum entries are allowed. No panic-buy signal active.
When the VIX is in the Danger zone:
Current VIX is 27.4 → DANGER regime. Historically the worst zone for both mean-reversion and momentum entries (S&P 500 mean-reversion hit rate ~19.6% in this band). Recommendation: hold off on new long entries until VIX exits 25–30 in either direction.
Caveats to mention
- Backtest is sample-specific (S&P 500, 2015–2024). Outside that, thresholds may need adjustment.
- VIX is forward-looking volatility, not certainty. A single VIX reading is a snapshot.
- This is a macro filter, not a stand-alone trade signal.
Real-world example
This skill was extracted from the quant-scanner project where it acts as the second layer of a three-layer macro regime filter (alongside QQQ-vs-MA200 trend and HYG credit health).