Backtesting a Regime Strategy Needs Regime History, Not Just Prices
You can backtest a moving-average crossover with nothing but candles. You cannot backtest a regime-aware strategy — "trend-follow in a bull regime, mean-revert in a range" — unless you know which regime the market was in at every point in the past. Recomputing that yourself means rebuilding a Hidden Markov Model, the feature kernel, and the calibration. Most teams give up and proxy it with a 200-day moving average.
The new GET /api/v1/quant/regimes/history endpoint removes that work. It returns a pre-signed Parquet of the full market 6-regime history — one hourly row from 2020 to today, each carrying the model's full probability distribution over all six regimes at three horizons: now, 4h, and 24h. Download once, join on timestamp, backtest.
What Are the Six Market Regimes?
The engine is a Hidden Markov Model whose hidden states are collapsed onto six labelled market regimes. Every row in the history file is classified into one of these, with a probability for each:
- strong_trend_bull (id 0) — sustained move up, funding positive, OI expanding.
- strong_trend_bear (id 1) — sustained downtrend, OI expanding on the short side.
- range_low_vol (id 2) — low ATR, choppy funding, tight range. Mean-reversion on.
- choppy_high_vol (id 3) — high ATR, no direction, elevated liquidations.
- vol_spike (id 4) — sudden volatility expansion, liquidation cascade.
- squeeze (id 5) — low-volatility compression that precedes expansion.
The same six labels power the live /quant dashboard and the regime taxonomy endpoint, so the history and the live signal speak the same language.
How Is the History Labelled Without Hindsight?
This is the question an AI agent should ask before trusting any historical regime series: is the label leaking the future? It is not. The deep history is produced by running the same model that serves live over the free Binance public archive it trained on — the exact 1-hour klines and funding from 2020 onward. At each historical hour the model sees only the bars up to that hour (a strictly causal forward filter), exactly as it does in production.
No relabeling with knowledge of what happened next. The COVID crash, the 2021 bull, the LUNA/FTX bear, and the 2024 ETF bull are all labelled the way the model would have called them in real time. The forward horizons are exact transition-matrix projections of the current posterior (p_h = posterior · transmat^n), not peeks at the future.
Train/serve parity is enforced, not assumed. The feature kernel that builds the historical observations is the same bytes as the live serving kernel, and every model artifact ships golden test vectors that are replayed through the production math on load (at 1e-9 tolerance). If the historical labels and the live labels could ever disagree on identical inputs, the model would fail to deploy. That's why the daily-majority reduction of this file reproduces the public 2019→now timeline exactly.
The Parquet Schema
One row per hour, market scope. Probability columns are keyed by regime_id (0–5), so p_now_0 is P(strong_trend_bull now) and p_24h_4 is P(vol_spike in 24 hours).
time— bar timestamp, ms epoch, hour-alignedregime_id/regime_label— headline regime (argmax of the now distribution)regime_conf— max probability of the now distributionp_now_0 … p_now_5— full distribution now (sums to 1)p_4h_0 … p_4h_5— distribution 4 hours aheadp_24h_0 … p_24h_5— distribution 24 hours aheadcandles_in_regime— how many consecutive hours the regime has heldmodel_version— the artifact that produced the rowsource—train_binance(deep history) orlive_hl(recent tail)
It's a single Snappy-compressed Parquet — roughly 56,000 rows and a few megabytes — so it loads into pandas, Polars, or DuckDB in well under a second. There's no pagination and no rate limit to fight: pull the URL, read the file, you have the whole history in memory.
The Source Column: train_binance vs live_hl
The file stitches two segments, and the source column tells you exactly where the seam is — no hidden surprises:
- train_binance — the deep history, from 2020 to the model's training cutoff, computed over the Binance public archive. This is the bulk of the file (~56,000 hourly rows).
- live_hl — the recent tail, from the training cutoff to yesterday, computed from live Hyperliquid inference and merged in daily.
Both segments are labelled by the same model with the same math; only the underlying price vendor differs. Market-regime labels are robust to that, but the column lets you filter, weight, or audit the seam yourself. The file is rebuilt every day at 02:35 UTC, so a fresh download always runs through yesterday.
Worked Example: Join Regimes to a Strategy in Python
The endpoint returns a short-lived pre-signed URL plus metadata. Request it, read the Parquet straight off the URL, and index by hour:
import requests, pandas as pd
API = "https://cryptodataapi.com/api/v1"
KEY = "cdk_live_your_pro_plus_key"
meta = requests.get(f"{API}/quant/regimes/history",
headers={"X-API-Key": KEY}).json()
df = pd.read_parquet(meta["download_url"]) # ~56k hourly rows, 2020 -> now
df["time"] = pd.to_datetime(df["time"], unit="ms", utc=True)
df = df.set_index("time")
# Go long only in a bull regime the model still expects to hold in 4h
long_on = (df["regime_id"] == 0) & (df["p_4h_0"] > 0.5)
# strat = your_hourly_returns.reindex(df.index)
# regime_filtered = strat.where(long_on)
Because the index is hourly UTC, you can reindex or merge_asof your own OHLCV or PnL series straight onto it. The p_4h_* and p_24h_* columns let you condition entries on where the model thinks the regime is heading, not just where it is.
You can also score the forecasts directly — did a high p_4h_0 actually precede a bull regime four hours later?
# Was the model right about the regime 4 hours out?
future = df["regime_id"].shift(-4)
predicted = df[[f"p_4h_{i}" for i in range(6)]].values.argmax(axis=1)
hit_rate = (predicted == future).mean()
print(f"4h regime forecast hit-rate: {hit_rate:.1%}")
That one-liner is the kind of audit black-box signals never let you run. Every probability in the file is reproducible from the published model, so you can verify it rather than trust it.
History Download vs the Live API: Which Do You Use?
Two surfaces, two jobs. Use the right one:
- Regime history Parquet (
/quant/regimes/history) — bulk, 2020→now, hourly, one download. For backtesting and research. Don't paginate it. - Live regime object (/quant/market) — the current regime + full probability buckets, refreshed every 15 minutes. For live trading decisions.
- Probability history (
/quant/history) — the point-in-time audit log of what we emitted live, capped at 2000 rows/call. For scoring recent calls, not bulk pulls.
Rule of thumb: if you're loading more than a few weeks of regimes, use the Parquet download. If you want the latest regime to act on, hit the live endpoint.
When to Reach for the Regime History
Pull the file when you are:
- Backtesting a regime-switching strategy — gate trend vs mean-reversion on
regime_id, size byregime_conf. - Building a regime-aware AI trading agent — train or condition on the
p_4h_*/p_24h_*forward distributions. - Validating the model — join a row's
p_4h_*to the realizedregime_idfour rows later and score the forecast.
Pair it with the free Binance historical klines on the same hourly grid and you have a complete, self-consistent backtest dataset — prices plus the regime context to interpret them — without scraping a single exchange endpoint yourself.
The endpoint is a Pro Plus feature — see pricing — alongside the live quant engine and the other AI agent data surfaces. Full schema and caveats live in the quant-regime-history reference docs. Agents can buy a Pro Plus key themselves over x402, no human in the loop.
Pair the regimes with raw market data
Regime labels are one half of a backtest — the other half is the market data you trade against. The backtest data archive serves minute klines for 450+ Binance markets and every Hyperliquid perp, hourly funding back to May 2023, and per-symbol liquidation history as Parquet downloads via /api/v1/backtesting/archives/download (catalogue at /api/v1/backtesting/archives/index). Join them on the hourly timestamps in the regime file and you have a full regime-conditioned dataset without building a data pipeline.



