The Backtesting Data Paywall That Doesn't Exist

Most builders assume that multi-year Binance historical data — the kind you need to backtest a strategy or train a model — sits behind an expensive data vendor. It doesn't. Binance publishes its entire futures price and funding history as free, checksummed ZIP files on a public CDN, with no API key and no rate limits.

This is the exact dataset we use to train our HMM market-regime model: USD⃋-M perpetual futures, 1-hour klines plus funding rates, going back to January 2020. Roughly 56,000 hourly candles per long-lived symbol.

This post shows you precisely where that data lives, the URL patterns to fetch it, the symbol list we train on, the five gotchas that will trip you up, and a copy-paste Python downloader. Then: when to use these raw dumps versus our live API.

Where Binance Historical Data Actually Lives

The archive is data.binance.vision — Binance's own public S3 mirror. It serves one ZIP per symbol per month, for spot and futures, across every interval. For regime work you want USD⃋-M futures (futures/um), 1-hour klines, plus the matching funding-rate series.

The two URL patterns you need:

# 1h OHLCV klines (note the /1h/ interval subpath)
https://data.binance.vision/data/futures/um/monthly/klines/{SYMBOL}/1h/{SYMBOL}-1h-{YYYY-MM}.zip

# 8h funding rate (no interval subpath)
https://data.binance.vision/data/futures/um/monthly/fundingRate/{SYMBOL}/{SYMBOL}-fundingRate-{YYYY-MM}.zip

# Append .CHECKSUM to any URL for its SHA256 (optional integrity check)
https://data.binance.vision/data/futures/um/monthly/klines/BTCUSDT/1h/BTCUSDT-1h-2020-01.zip.CHECKSUM

A quick smoke test with curl to confirm a file exists before you script the whole loop:

curl -sI "https://data.binance.vision/data/futures/um/monthly/klines/BTCUSDT/1h/BTCUSDT-1h-2020-01.zip" \
  | head -n 1
# HTTP/1.1 200 OK

Each kline ZIP unzips to a CSV with the familiar columns: open_time, open, high, low, close, volume, close_time, quote_volume, .... Funding ZIPs carry a calc_time (or fundingTime) column and the rate in the last column.

The 30 Symbols We Train On

Our HMM uses two layers. BTCUSDT and ETHUSDT drive the market-regime model (the broad risk backdrop). All 30 symbols feed a pooled per-coin model so it learns altcoin and meme-coin dynamics, not just majors. Each trains from its own listing date onward.

The exact Binance tickers to download:

You don't need our list to use the archive — every Binance perp is in there. But this is the universe behind the regime model, so it's a sensible default if you want parity with our published signals.

Five Gotchas That Will Bite You

The archive is clean, but the edges are sharp. These five cost us time so they don't have to cost you any:

One modelling note: funding events are 8-hourly. We forward-fill them to the hourly grid and annualize (rate × periods_per_year) before feeding the model. Match that if you want feature parity with our regime output.

A Copy-Paste Python Downloader

This pulls every complete month of 1h klines for a symbol, handles 404s and microsecond timestamps, and returns (open_time_ms, close) rows. Funding follows the same shape against the fundingRate path.

import io, zipfile, csv
from datetime import datetime, timezone
import httpx

DUMP = "https://data.binance.vision/data/futures/um/monthly"

def months(start="2020-01"):
    y, m = map(int, start.split("-"))
    now = datetime.now(timezone.utc)
    while (y, m) < (now.year, now.month):   # stop before the incomplete current month
        yield f"{y:04d}-{m:02d}"
        y, m = (y + 1, 1) if m == 12 else (y, m + 1)

def klines(client, symbol):
    rows = []
    for ym in months():
        url = f"{DUMP}/klines/{symbol}/1h/{symbol}-1h-{ym}.zip"
        r = client.get(url, timeout=120)
        if r.status_code == 404:           # month predates listing -- normal
            continue
        r.raise_for_status()
        with zipfile.ZipFile(io.BytesIO(r.content)) as zf:
            with zf.open(zf.namelist()[0]) as f:
                for row in csv.reader(io.TextIOWrapper(f, "utf-8")):
                    if not row or row[0] == "open_time":
                        continue           # skip header row
                    t = int(row[0])
                    if t > 1e13:           # microsecond dumps
                        t //= 1000
                    rows.append((t, float(row[4])))   # (open_time_ms, close)
    return rows

with httpx.Client() as c:
    btc = klines(c, "BTCUSDT")
    print(len(btc), "hourly bars")   # ~56,000 from 2020-01 to now

No key, no auth, no quota. The whole 30-symbol universe downloads in a few minutes on a home connection.

Binance Dumps vs Our API: Which Should You Use?

The free dumps and cryptodataapi.com solve different problems. Raw Binance data is unbeatable for bulk price history; it has nothing to say about derived signals. Here's the split:

NeedBinance dumpsCryptoDataAPI
Multi-year OHLCV (2020+)Yes — free, 1h/1mLast ~30 days (1m) live
Funding & OI historyFunding yes; OI no/backtesting/funding
Regime labels & HMM probabilitiesNo — you'd retrain/api/v1/quant
Point-in-time signal snapshotsNo/backtesting/daily-snapshots
Health scores, sentiment, macroNo/api/v1/daily

Rule of thumb: pull raw price and funding history from the free Binance archive, and use the API for the things you can't reconstruct from candles — our regime labels, health scores, and the point-in-time snapshot archive that captures what every signal read on a given day.

How to Use This for Backtesting

A practical workflow that combines both sources:

If you want to reproduce our regime model end-to-end, the acquisition logic above is the whole input pipeline — everything downstream is feature engineering and the HMM fit. If you just want the output, skip the training entirely and read /api/v1/quant and the 14-basket regime framework directly.

Either way, the data that used to feel like the expensive part is free. The value is in what you build on top of it.

Or skip the pipeline: the hosted archive

Everything this guide builds by hand — deduped 1-minute klines for 450+ Binance markets, volume-verified against the exchange and updated daily — is also available as a hosted archive on the backtest data page. One call to /api/v1/backtesting/archives/index lists every dataset, symbol and date range; /api/v1/backtesting/archives/download returns pre-signed Parquet links you can load straight into pandas. It also covers what the public dumps don't: Hyperliquid perps, hourly funding back to May 2023, liquidations and daily full-market snapshots.