HistPrice

Historical market data built for backtesting. One-minute candles stored once as Parquet in Cloudflare R2, with every other timeframe aggregated on demand.

One resolution, stored once

Only 1-minute bars are persisted. 5m, 35m, 4h and 1d are computed at read time, so there is never a second copy of the same truth to drift.

Any timeframe

Standard intervals plus any whole number of minutes. Buckets are anchored to the Unix epoch, so the same window always returns the same bars.

Reference, not payload

Large backtests receive presigned Parquet object references and read straight from R2. Gigabytes never stream through an API process.

Markets kept separate

Binance BTCUSDT and Bybit BTCUSDT are different markets and are never merged. An ETF is never returned as the index it tracks.

Authentication

Every data endpoint requires a credential. The dataset is not anonymously downloadable.

curl https://histprice.com/v1/coverage/BTCUSDT \
  -H "Authorization: Bearer $HISTPRICE_API_KEY"

Cloudflare Access is also accepted, and is preferred for interactive users.

Endpoints

GET/v1/coverage/{symbol}

What history exists, per provider and venue.

{
  "symbol": "BTCUSDT",
  "providers": {
    "binance:spot":  { "from": "2017-08-17T04:00:00Z", "to": "...", "resolution": "1m" },
    "bybit:linear":  { "from": "2020-03-15T00:00:00Z", "to": "...", "resolution": "1m" }
  }
}

GET/v1/history/{symbol}

parametermeaning
providerbinance, bybit, ...
venuespot, linear, um, ...
from, toISO 8601, a date, or now-30d
timeframe1m, 35m, 4h, 1d, or any integer minutes
formatparquet (default), arrow, csv, json
curl "https://histprice.com/v1/history/BTCUSDT\
?provider=binance&from=2024-01-01&to=2025-01-01&timeframe=35m&format=parquet" \
  -H "Authorization: Bearer $HISTPRICE_API_KEY" -o btc-35m.parquet

POST/v1/history/query

Several instruments in one request, for cross-asset and portfolio work.

{
  "symbols": ["BTCUSDT", "ETHUSDT", "SOLUSDT"],
  "provider": "binance",
  "from": "2022-01-01",
  "to": "2026-01-01",
  "timeframe": "35m",
  "format": "arrow"
}

POST/v1/dataset

The right call for a large backtest. Returns presigned R2 object references so the client reads Parquet directly from Cloudflare.

{
  "dataset_id": "ds_a1b2c3...",
  "total_objects": 3,
  "total_rows": 15778800,
  "objects": [
    { "key": "histprice/candles/crypto/binance/spot/BTCUSDT/years=2017-2026/part-000.parquet",
      "bytes": 231000000, "rows": 4400000, "sha256": "...", "url": "https://...", }
  ],
  "timestamp_semantics": { "meaning": "candle OPEN time", "unit": "epoch ms, UTC" }
}
Timestamps. Every timestamp is the candle's open time in UTC epoch milliseconds. A bar stamped 10:35 covers [10:35, 10:35 + interval) and is only knowable at its close. Acting on its closing price at 10:35 is look-ahead bias.

Timeframe aggregation

Applied identically by the API, the SDK and any client following the published rule:

open   = first(open)   by timestamp
high   = max(high)
low    = min(low)
close  = last(close)   by timestamp
volume = sum(volume)

bucket = floor(timestamp / interval_ms) * interval_ms   # anchored to the Unix epoch

Epoch anchoring is what makes results reproducible: a 35-minute request starting in March and one starting in June produce identical bars for the days they share.

Python SDK

from histprice import HistPrice

hp = HistPrice()
df = hp.history("BTCUSDT", provider="binance",
                start="2022-01-01", end="2026-01-01", timeframe="35m")

df.to_pandas()   # or .to_arrow()

Returns a Polars DataFrame. The SDK reads Parquet from a local cache and never issues an HTTP request for data it already holds.

Coverage

asset classsourcestatus
CryptoBinance, Bybit1-minute spot and perpetual history
Equitieslicensed vendorrequires a configured provider
Indiceslicensed vendorrequires a configured provider
OptionsBinancesparse; availability varies by contract
Equities and indices. Binance and Bybit supply crypto only. Equity and index history requires a separately licensed market-data vendor. Until one is configured those instruments report PROVIDER_NOT_CONFIGURED and name the exact credential needed. No data is ever fabricated, and an ETF is never substituted for the index it tracks.

Response formats

formatuse
parquetdefault; smallest and fastest to re-read
arrowfastest machine-to-machine; zero-copy into Polars or pandas
csvcompatibility only; generated on demand, never stored
jsonsmall results only; refused above one million rows