Skip to content

What (One-liner)

2-state Markov-switching autoregression (MS-AR(1), switching variance) performs regime identification on return series. statsmodels primary path + rolling-vol fallback. Returns current regime, probs, per-regime mean/σ, and label_map (low_volatility / high_volatility).

How to use

Drag markov_regime_v1 onto Canvas, upstream price_factor close → frontend derives returns (or fill params.returns directly). Default n_regimes=2. Typical usage:

price_factor → markov_regime_v1 → institution_composite / casa_strategy (regime overlay)

Core formulas

python
# Primary path (statsmodels MarkovAutoregression):
mod = MarkovAutoregression(returns, k_regimes=2, order=1, switching_variance=True)
res = mod.fit(maxiter=200)

# 2026-04-20 shim (pandas or ndarray):
smoothed = res.smoothed_marginal_probabilities
last = smoothed.iloc[-1] if hasattr(smoothed, 'iloc') else smoothed[-1]
current_regime = argmax(last)

# Named param lookup supports both DataFrame-style and ndarray + mod.param_names
means  = [_param_by_name(f'const[{i}]')  for i in range(2)]
sigmas = [sqrt(_param_by_name(f'sigma2[{i}]')) for i in range(2)]

# Label map (n_regimes=2 only):
low_idx = argmin(sigmas)
label_map = {low_idx: 'low_volatility', 1-low_idx: 'high_volatility'}

# Fallback (on any statsmodels exception):
vol_ratio = std(second_half) / std(first_half)
label = 'high_volatility' if vol_ratio > 1.3 else 'low_volatility'

Assumptions & applicability

Assumptions: ≥30 obs + daily equal frequency + 2-regime + order=1 + switching_variance=True + single EM fit.

Applicable: Canvas single-asset returns regime identification, crisis/normal phases, reflexivity/contagion pre-processing.

Not applicable: intraday, 3+ regimes (label_map doesn't work), multi-start Bayesian, scenarios requiring transition matrix.

Input / Output contract

Primary: {returns: list, n_regimes?=2}{n_observations, n_regimes, current_regime, current_regime_label, regime_probabilities, regime_means, regime_sigmas, regime_labels, _method='MarkovAutoregression (statsmodels)'}

Fallback: {n_observations, n_regimes=2, current_regime, current_regime_label, regime_probabilities, vol_ratio, _method='rolling_volatility_fallback', _fallback_reason}

Error: {error: 'need_more_data', message}

Known limitations

  1. Primary path previously failed silently (before 2026-04-20 fix): statsmodels new vs old version DataFrame vs ndarray difference caused AttributeError → ALL fallback. Shim added.
  2. switching_variance=True hardcoded (mean-switching-only model cannot be selected)
  3. order=1 hardcoded
  4. Single EM fit (no multi-start / Bayesian)
  5. Pure noise data → degenerate regime (σ~1e-5 + σ~real)
  6. No trend/drift preprocessing
  7. Fallback threshold vol_ratio > 1.3 hardcoded
  8. n_regimes=3+ interface accepted but label_map not mapped (client needs to handle 'regime_X' string)

References

Hamilton (1989) Econometrica MS-AR + Kim-Nelson (1999) State-Space with Regime Switching + Ang-Timmermann (2012) Regime Changes in Financial Markets. See frontmatter for details.

Golden Test

tests/golden/fixtures/tier3/markov_regime/, statsmodels reference. 1e-10 tolerance. 9 tests:

  • too_short_returns — 10 obs → error='need_more_data'
  • low_vol_regime — stable σ=0.005 → primary path + degenerate regime OK
  • regime_switching — half low-vol + half high-vol → current=1, probs [0.04, 0.96]
  • trending_up — positive drift → primary path, near-uniform probs
  • determinism — 5 reruns consistent
  • result_structure_primary_path — 9 fixed keys
  • probabilities_sum_to_one — valid probability distribution
  • current_regime_in_range — ∈ [0, n_regimes)
  • label_map_low_high — 2-regime + sigmas →

Changelog

  • 1.0.1 (2026-04-20) — statsmodels primary-path fix + golden v1.0.1
  • 1.0.0 (2026-04-19) — Canvas shipped (Phase B advanced quant)

Verifiable intelligence for the decisions that demand scrutiny.