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
# 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
- 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.
- switching_variance=True hardcoded (mean-switching-only model cannot be selected)
- order=1 hardcoded
- Single EM fit (no multi-start / Bayesian)
- Pure noise data → degenerate regime (σ~1e-5 + σ~real)
- No trend/drift preprocessing
- Fallback threshold vol_ratio > 1.3 hardcoded
- 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 OKregime_switching— half low-vol + half high-vol → current=1, probs [0.04, 0.96]trending_up— positive drift → primary path, near-uniform probsdeterminism— 5 reruns consistentresult_structure_primary_path— 9 fixed keysprobabilities_sum_to_one— valid probability distributioncurrent_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)

