What is Regime Detection?
Identifying which of several distinct market states (bull, bear, volatile, sideways) the market or a stock currently occupies, to adapt strategy parameters accordingly.
In Plain English
Markets do not behave the same way all the time. A stock that predictably drifts upward during calm periods can become whipsaw and mean-reverting during volatile, fear-driven markets. A momentum strategy that generates strong returns in trending markets can produce devastating losses when trends reverse abruptly. Regime detection is the discipline of identifying which "mode" the market is currently in, so you can adapt accordingly.
The analogy is weather forecasting. You dress differently in winter than in summer even if the annual average is mild. A strategy should not behave the same way in a low-volatility bull market as in a high-volatility bear market, but that is what happens when you ignore regimes.
Most regime models divide market states into two to five categories. A simple two-regime model might distinguish "low volatility / trending up" from "high volatility / trending down." A more nuanced model might use four regimes: bull (uptrending, low volatility), bear (downtrending, elevated volatility), volatile (high volatility, no clear trend), and sideways (low volatility, no clear trend).
Research consistently shows that momentum strategies have positive expected returns in bull and sideways regimes, but suffer sharp reversals at the onset of volatile or bear regimes. Detecting regime shifts early and reducing momentum exposure before the worst drawdowns substantially improves the risk-adjusted returns of trend-following strategies.
Technical Definition
The Hidden Markov Model (HMM) is the classical approach to regime detection. The market regime S_t ∈ \{1, ..., K\} is unobserved (hidden), but the returns r_t are observed. The model specifies:
P(S\_t = j | S\_{t-1} = i) = A\_\{ij\} (transition matrix) r\_t | S\_t = k ~ N(μ\_k, σ\_k²) (regime-conditional return distribution)
The Viterbi algorithm decodes the most likely regime sequence given observed returns. The forward-backward algorithm computes P(S\_t = k | r\_\{1:T\}), the regime probability at time t given all returns.
Alternative approaches include:
- Markov-switching variants on the return series
- K-means / GMM clustering on features (volatility, trend, breadth)
- Structural break tests (Chow, CUSUM)
- ML classifiers (gradient boosting on technical features)
Transition probabilities should show high diagonal values (regimes last weeks to months, not days), as overfitting to noisy regime boundaries is a primary practical failure mode.
How VectorFin Uses This
Regime is not a separate endpoint or table, it rides inside the flat signals table as one of the five signal families, served at GET /v1/signals/{ticker}. Each row's components.regime object carries the classification:
current: one ofbull,bear,volatile,sideways(may benullwhen undetermined)confidence: probability from the underlying HMM (0.0-1.0)
Alongside components.regime, every row also carries the bitemporal stamps effective_ts (the business date) and knowledge_ts (when VectorFin computed this signal, for backtest safety).
The signals table is populated weekly via the signals_writer Cloud Run Job, which fits an HMM on the daily return series to label each ticker's market regime.
GET https://api.vectorfinancials.com/v1/signals/{ticker}
GET https://api.vectorfinancials.com/v1/signals/{ticker}?date_from=2024-01-01&date_to=2024-12-31&limit=365Read the regime from components.regime.current (may be null) and components.regime.confidence.
Strategy applications: use the regime signal to switch between factor tilts, adjust leverage, or activate/deactivate momentum overlays based on current market conditions.
Code Example
import requests
import pandas as pd
import matplotlib.pyplot as plt
API_BASE = "https://api.vectorfinancials.com"
API_KEY = "vf_your_api_key_here"
# Fetch full year of signals for SPY (market-level proxy); regime rides inside each row
resp = requests.get(
f"{API_BASE}/v1/signals/SPY",
params={"date_from": "2024-01-01", "date_to": "2024-12-31", "limit": 365},
headers={"X-API-Key": API_KEY},
)
rows = resp.json()
# Pull the regime family out of components.regime on each row
df = pd.DataFrame([
{
"date": r["date"],
"regime": (r["components"]["regime"] or {}).get("current"),
"confidence": (r["components"]["regime"] or {}).get("confidence"),
}
for r in rows
])
df["date"] = pd.to_datetime(df["date"])
# Regime distribution (drop rows where regime is null/undetermined)
print("Regime distribution (SPY 2024):")
print(df["regime"].dropna().value_counts(normalize=True).round(3))
# Days in high-confidence regime (confidence > 0.70)
high_confidence = df[df["confidence"] > 0.70]
print(f"\nHigh-confidence regime days: {len(high_confidence)} / {len(df)}")
print(high_confidence["regime"].value_counts())
# Strategy: only trade momentum when regime is bull or sideways
tradeable_dates = df[df["regime"].isin(["bull", "sideways"])]["date"].tolist()
print(f"\nDates where momentum strategy is active: {len(tradeable_dates)}")Put Regime Detection to work in your pipeline
Pull AI-ready embeddings and signals as Iceberg tables or over the REST API.
Get API Access