Documentation -- download OHLCV Parquet via Python & API

Quick Start

MarketParquet serves daily-partitioned OHLCV data as Apache Parquet files. Each file contains all symbols for a single trading day.

  1. No account? Grab the newest daily file (curl -LO https://marketparquet.com/api/data/download/stock_daily/latest.parquet) or the intraday sample pack (zip)
  2. Sign up for a free account
  3. Browse available data by asset type and timeframe
  4. Download the parquet files you need (web or API)
  5. Load into pandas, polars, DuckDB, or any tool that reads Parquet

File Format

Files are named by trading date and stored under by_date/{asset}_{timeframe}/YYYY/YYYY-MM-DD.parquet.

Intraday schema (1min, 5min, 30min, 1hour)

  timestamp    TIMESTAMP[us]   -- bar open time (US/Eastern)
  symbol       STRING          -- ticker symbol
  asset_type   STRING          -- "Stock", "ETF", or contract code
  open         FLOAT64
  high         FLOAT64
  low          FLOAT64
  close        FLOAT64
  volume       FLOAT64

Daily (EOD) schema

  date         DATE            -- trading date
  symbol       STRING          -- ticker symbol
  asset_type   STRING
  open         FLOAT64
  high         FLOAT64
  low          FLOAT64
  close        FLOAT64
  volume       FLOAT64

Note: daily files dated before 2026-03-27 carry the bar time in a timestamp TIMESTAMP[us] column instead of date. When reading a range that spans the boundary, use read_parquet(..., union_by_name=true) and COALESCE(date, CAST(timestamp AS DATE)) (DuckDB), or normalize per file in pandas/polars.

Stock and ETF prices are split-adjusted (dividends are not embedded in the price series -- see methodology). Futures use continuous ratio adjustment. Compression: Snappy.

Python

pandas

  import pandas as pd

  # Load one day of stock 1-min bars
  df = pd.read_parquet("stock_1min_2024-01-15.parquet")

  # Filter to a single ticker
  spy = df[df.symbol == "SPY"]

  # Compute VWAP
  spy["vwap"] = (spy.close * spy.volume).cumsum() / spy.volume.cumsum()

polars (faster, recommended)

  import polars as pl

  df = pl.read_parquet("stock_daily_2024-01-15.parquet")

  # Top 10 by volume
  top = df.sort("volume", descending=True).head(10)

  # Lazy load multiple files
  df = pl.scan_parquet("stock_daily_2024-*.parquet")
  monthly_avg = df.group_by("symbol").agg(pl.col("close").mean()).collect()

DuckDB (SQL on Parquet)

  import duckdb

  con = duckdb.connect()

  # Query parquet files directly with SQL
  result = con.execute("""
      SELECT symbol, AVG(close) AS avg_close, SUM(volume) AS total_vol
      FROM 'stock_daily_*.parquet'
      WHERE symbol IN ('AAPL', 'MSFT', 'GOOGL')
      GROUP BY symbol
  """).fetchdf()

REST API

The API is available on the free sample -- recent daily data (the anonymous intraday sample pack is a plain zip, no key needed). A one-time Complete Archive purchase unlocks full history, including intraday. Generate an API key from your account page, then authenticate with the Authorization header.

Rate limits: 600 requests/min per IP at the edge and per API key on paid plans; 60 requests/min per key on the free sample. Over the limit you get 429 with a Retry-After header (seconds). A manifest call counts as one request however many files it returns, so bulk downloads should use the manifest, not one /download call per file.

Machine-readable: GET /api/v1/ (no key needed) returns a JSON map of every endpoint, the auth header and the asset-type names, and /api/v1/openapi.json is the OpenAPI 3 document -- point your coding agent at either one instead of letting it guess URLs. The header is exactly Authorization: Bearer, one space, then the key; a 401 means it was pasted wrong.

List available assets

  GET https://marketparquet.com/api/v1/assets

  curl -H "Authorization: Bearer bt_YOUR_KEY" \
    https://marketparquet.com/api/v1/assets

  # Returns:
  {
    "plan": "full",
    "assets": [
      {
        "asset_type": "stock_1min",
        "asset": "stock",
        "timeframe": "1min",
        "file_count": 6690,
        "earliest_date": "2000-01-03",
        "latest_date": "2026-04-06",
        "access": "full"
      },
      ...
    ]
  }

List dates for an asset

  GET /api/v1/dates/{asset_type}?page=1

  curl -H "Authorization: Bearer bt_YOUR_KEY" \
    https://marketparquet.com/api/v1/dates/stock_daily

  # Returns up to 100 dates per page (newest first)
  {
    "asset_type": "stock_daily",
    "plan": "full",
    "page": 1,
    "count": 100,
    "dates": [
      {
        "date": "2026-04-06",
        "file_size_bytes": 232488,
        "download_url": "/api/v1/download/stock_daily/2026-04-06"
      },
      ...
    ]
  }

Download a file

  GET /api/v1/download/{asset_type}/{date}

  curl -H "Authorization: Bearer bt_YOUR_KEY" \
    https://marketparquet.com/api/v1/download/stock_daily/2026-04-06

  # Returns a presigned R2 URL (valid 60 seconds)
  {
    "asset_type": "stock_daily",
    "date": "2026-04-06",
    "file_size_bytes": 232488,
    "download_url": "https://...r2.cloudflarestorage.com/...?X-Amz-Signature=...",
    "expires_in": 60
  }

Bulk: manifest for a date range

  GET /api/v1/manifest/{asset_type}?start=YYYY-MM-DD&end=YYYY-MM-DD

  curl -H "Authorization: Bearer bt_YOUR_KEY" \
    "https://marketparquet.com/api/v1/manifest/stock_daily?start=2024-01-01&end=2024-12-31"

  # One call per date range instead of one per file. URLs valid 60 minutes.
  # Up to 400 files per call (a calendar year of any timeframe fits) -- walk
  # longer ranges year by year. Each file is logged as a download.
  {
    "asset_type": "stock_daily",
    "start": "2024-01-01",
    "end": "2024-12-31",
    "file_count": 252,
    "total_bytes": 58712345,
    "date_range": ["2024-01-02", "2024-12-31"],
    "expires_in": 3600,
    "files": [
      {
        "date": "2024-01-02",
        "filename": "stock_daily_2024-01-02.parquet",
        "file_size_bytes": 232488,
        "download_url": "https://...r2.cloudflarestorage.com/...?X-Amz-Signature=..."
      },
      ...
    ]
  }

Asset types

  stock_1min     stock_5min     stock_30min     stock_1hour     stock_daily
  etf_1min       etf_5min       etf_30min       etf_1hour       etf_daily
  futures_1min   futures_5min   futures_30min   futures_1hour   futures_daily

Examples

Python: download a file via the API

  import requests
  import pandas as pd

  API_KEY = "bt_YOUR_KEY"
  BASE = "https://marketparquet.com/api/v1"
  headers = {"Authorization": f"Bearer {API_KEY}"}

  # 1. Get a presigned URL
  resp = requests.get(
      f"{BASE}/download/stock_daily/2026-04-06",
      headers=headers,
  ).json()

  # 2. Download the parquet file
  parquet_bytes = requests.get(resp["download_url"]).content

  # 3. Load with pandas
  import io
  df = pd.read_parquet(io.BytesIO(parquet_bytes))
  print(df.head())

Python: download a whole year via the manifest

  import pathlib
  import requests

  H = {"Authorization": "Bearer bt_YOUR_KEY"}
  m = requests.get("https://marketparquet.com/api/v1/manifest/stock_daily",
                   params={"start": "2024-01-01", "end": "2024-12-31"}, headers=H).json()
  for f in m["files"]:
      pathlib.Path(f["filename"]).write_bytes(requests.get(f["download_url"]).content)
  # Full history: repeat per year (2000..today). Wrap the loop in a
  # ThreadPoolExecutor to fetch several files at once.

Bash: download every daily stock file (needs curl + jq)

  for y in $(seq 2000 2026); do
    curl -s -H "Authorization: Bearer bt_YOUR_KEY" "https://marketparquet.com/api/v1/manifest/stock_daily?start=$y-01-01&end=$y-12-31" \
    | jq -r '(.files // [])[] | "\(.download_url) -o \(.filename)"' | xargs -P8 -n3 curl -s --retry 3
  done

  # Swap stock_daily for any asset type; 8 parallel downloads is a good default.

Python: backtest a simple strategy

  import pandas as pd
  import glob

  # Load all daily files for 2024
  files = sorted(glob.glob("stock_daily_2024-*.parquet"))
  df = pd.concat([pd.read_parquet(f) for f in files])

  # SPY-only momentum strategy
  spy = df[df.symbol == "SPY"].sort_values("date").reset_index(drop=True)
  spy["return"] = spy.close.pct_change()
  spy["sma20"] = spy.close.rolling(20).mean()
  spy["signal"] = (spy.close > spy.sma20).astype(int)
  spy["strategy_return"] = spy.signal.shift(1) * spy["return"]

  cumret = (1 + spy.strategy_return).cumprod()
  print(f"Final value: {cumret.iloc[-1]:.2f}")

Need more help?

Check the FAQ for data quality details, or browse the interactive API docs at /docs.