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:

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).

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:

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:

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:

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.