TL;DR
- What it is: a Python library for unsupervised Hidden Markov Models with a scikit-learn-style API (
fit,predict,predict_proba,score,sample,aic,bic).1 - Verdict: Selective fit. It's a good, tested engine for an HMM regime baseline in research (the SOE backtester and MCI benchmarking). It's not something to wire into live bot gating as-is.
- Two things to know first: the README says it is in limited-maintenance mode,2 and
predict/predict_probarun over the whole sequence you pass in. On a full history, that leaks future bars into past labels.1
Evidence
Pinned to default branch main at commit e01a10e (committed 2024-10-31). Evidence was collected anonymously on 2026-10-16.3
| Signal | Value |
|---|---|
| Stars / forks | 3,438 / 7554 |
| Open issues + PRs | 814 |
| Contributors (approx.) | 404 |
Commits on main in the last 90 days |
04 |
| Last release | 0.3.3 on PyPI, 2024-10-31 ("wheels compatible with numpy 2"); no GitHub releases56 |
| License | BSD-3-Clause4 |
| Languages | Python, plus a C++ core built with pybind113 |
| Tests | 14 files under src/hmmlearn/tests, run in CI with pytest7 |
| CI | GitHub Actions + cibuildwheel on Linux, macOS and Windows, CPython 3.8–3.137 |
Architecture
- Models:
GaussianHMM,GMMHMM,CategoricalHMM,MultinomialHMMandPoissonHMM(EM, inhmm.py). There are alsoVariationalGaussianHMMandVariationalCategoricalHMM(vhmm.py); the changelog calls variational inference provisional.6 - Core:
base.pyholds the EM loop, aConvergenceMonitor, decodersviterbiandmap, and two numerics modes (scaling,log).1 The forward, backward and Viterbi recursions run in C++ (ext/_hmmc.cpp). - No online API: there's no
partial_fitor step-by-step filtering call. Each prediction re-runs over the array you pass.
Claims checked against the pinned source
- "predict_proba returns smoothed posteriors."
predict_probacallsscore_samples(L378-L396), which runs a forward pass and a backward pass over the whole sub-sequence (L253-L271). The probability at bar t therefore uses bars after t.1 - "predict is Viterbi over the full sequence."
predictreturnsdecode(X)(L358-L376). Decoders areviterbiandmap(L17-L18), and both use the whole sequence.1 - "Limited maintenance." Stated in the README (L29), and consistent with zero commits in the last 90 days.24
- "Tested across platforms." The CI matrix covers 3 OSes × 6 CPython versions, and pytest runs inside cibuildwheel (L17-L36).7
Verdict
Selective fit. Use it for research and benchmarking. Don't make it a production dependency of MCI or live gating without a wrapper and a maintenance plan.
Roadmap
- Now: use
GaussianHMMin SOE research notebooks as an HMM baseline. Always compute causal probabilities (expanding window, keep the last row, or a small forward-filter helper). - Next: benchmark MCI conditions against HMM states on the same 4H history (see the experiment below).
- Later: if HMM gates prove useful, write a small in-house forward filter (a few dozen lines on top of the fitted
startprob_,transmat_and emission parameters). Live gating then doesn't depend on a library in limited maintenance. - Borrow without adopting: the scikit-learn-style model interface, the
aic/bicmodel-selection helpers, and the multi-sequencelengthsconvention for fitting across many symbols.
Bounded experiment
Do MCI conditions separate forward returns and volatility better than a causal 3-state Gaussian HMM?
- Data
- BTC and ETH 4H OHLC, same history for both labelers
- Method
- walk-forward; refit the HMM on each training window; filtered (causal) HMM states vs MCI conditions; 1-bar lag
- Metric
- difference in forward 20-bar return and volatility distributions between labels (effect size), out-of-sample only
- Success
- MCI shows clearly larger out-of-sample separation than the HMM on both symbols
- Owner
- SOE research
Stop if:
- HMM fails to converge on more than a small share of windows
- causal HMM states flip so often they are unusable as a gate
- time box of one working day is used up
Machine-readable YAML
experiment:
id: hmm-vs-mci-4h-separation
question: Do MCI conditions separate forward returns and volatility better than a causal 3-state Gaussian HMM?
data: BTC and ETH 4H OHLC, same history for both labelers
method: walk-forward; refit the HMM on each training window; filtered (causal) HMM states vs MCI conditions; 1-bar lag
metric: difference in forward 20-bar return and volatility distributions between labels (effect size), out-of-sample only
success: MCI shows clearly larger out-of-sample separation than the HMM on both symbols
stop_if:
- HMM fails to converge on more than a small share of windows
- causal HMM states flip so often they are unusable as a gate
- time box of one working day is used up
owner: SOE research
Strategy hooks
Gate any bot with the filtered probability of the high-volatility HMM state.
- Markets
- crypto majors, index ETFs
- Data
- OHLC returns
- Gate
- run while P(high-vol | data up to t) is below a threshold
- Test
- walk-forward with costs; compare to an ungated bot
- Success
- lower drawdown at similar return after costs
Let BIC pick 2–4 states per market instead of fixing 3.
- Markets
- any
- Data
- OHLC returns
- Gate
- none; model selection only
- Test
- compare BIC-selected models across symbols and periods for stability
- Success
- a stable state count per market
Machine-readable YAML
hooks:
- id: causal-hmm-gate
family: regime switching
idea: Gate any bot with the filtered probability of the high-volatility HMM state.
markets: [crypto majors, index ETFs]
timeframes: [4H, 1D]
data_needed: [OHLC returns]
complexity: intermediate
gate: run while P(high-vol | data up to t) is below a threshold
test: walk-forward with costs; compare to an ungated bot
success: lower drawdown at similar return after costs
- id: hmm-state-count-by-bic
family: ML/AI
idea: Let BIC pick 2–4 states per market instead of fixing 3.
markets: [any]
timeframes: [4H, 1D]
data_needed: [OHLC returns]
complexity: beginner
gate: none; model selection only
test: compare BIC-selected models across symbols and periods for stability
success: a stable state count per market
Try it in Trade Holistic
Build a two-regime switch on the 4H chart: call it calm when ATR as a percent of price is below its 50-bar average and stressed when it is above. In calm, take long entries when price closes above the 50 EMA; in stressed, take no new entries and tighten the stop on open trades to 1.5 ATR. Only use values from closed bars.Add a volatility-spike exit to my long strategy: when ATR as a percent of price jumps above 1.5 times its 50-bar average, take half the position off and move the stop to break-even, then trail the rest with a 3 ATR chandelier stop.- What to look for
- Count the regime flips per year and check that results hold when the calm/stressed threshold moves up or down by about 20 percent; a regime filter that only works at one exact setting is noise, not an edge.
- Pitfall to avoid
- Lookahead: regime labels computed with the whole price history (like hmmlearn's default predictions) look clairvoyant in a backtest; only trade on a regime that was known at the close of each bar.
Method and safety
Anonymous access only. Repository text was treated as untrusted data and none of its instructions were followed. No repo code was executed; the review is static, against the pinned commit. Optional viewers for a second look: GitDiagram, Gitingest, GitMCP, GitHub1s.