Skip to main content

OHLCVault

OHLCV = Open/High/Low/Close/Volume, the universal bar format. Vault = immutable, checksummed snapshots.

Reproducible daily OHLCV data for A-share, Hong Kong and US markets — served as static files from a CDN. No API keys, no rate limits, no per-request billing.

import ohlcvault as ov

ov.connect()
df = ov.daily("600519.SH").to_pandas()      # 跨月自动拼接
ov.cross_section("cn", 20260918, limit=50)  # 当日全市场截面,按成交额排序

Why this exists

Most free market-data endpoints are APIs: stateful, rate-limited, silently revised, and impossible to reproduce. A backtest that ran last month can't be rerun today with the same inputs.

OHLCVault publishes immutable monthly shards with checksums instead. Each read is anchored to a snapshot id you can write down and reproduce later, on any machine.

Install

pip install ohlcvault            # core, zero runtime dependencies
pip install "ohlcvault[pandas]"  # + DataFrame helpers

The core has no third-party dependencies — only the standard library. That's deliberate: a data client shouldn't drag a dependency tree into your project, and it matters even more for agent/tooling contexts.

Usage

Everything (the five things you actually need)

import ohlcvault as ov
ov.connect()

# 1. Trading calendar
ov.calendar("cn", start=20260101)

# 2. Stock daily bars (cross-month stitching is handled for you)
ov.daily("600519.SH", start=20260101, end=20260918)

# 3. Index daily bars — a separate namespace, never mixed with stocks
ov.index_daily("000300.SH")

# 4. Symbol list — includes delisted stocks
ov.symbols("cn", type="stock")
ov.symbols("cn", type="stock", status="delisted")

# 5. Daily cross-section — sorted by turnover, no extra data files
ov.cross_section("cn", 20260918, sort_by="amount", limit=50)

Batch backtests

Month shards hold every symbol in the market for that month. Loading 200 symbols one-by-one would decompress the same file 200 times:

bars = ov.daily_many(["600519.SH", "000001.SZ", "300750.SZ"], start=20260101)
bars["600519.SH"].to_pandas()

Adjustment is a view, not a stored field

The dataset stores unadjusted prices only, plus the official cumulative back-adjustment factor. Forward/backward adjusted prices are computed client-side:

b = ov.daily("600519.SH")
ov.adjust(b, to="hfq")   # 后复权
ov.adjust(b, to="qfq")   # 前复权

This is not a limitation — it's the reason historical files never change. If forward-adjusted prices were stored, every dividend would rewrite all of history, and immutable caching would be impossible.

Reproducibility

st = ov.connect()
sid = st.snapshot                       # e.g. "6b197df3723871c5"
ov.connect(snapshot=sid)                # later, anywhere: exact same inputs

Offline / self-hosted mirrors

A mirror can be an HTTP(S) URL or a local directory:

ov.connect(mirrors=["/path/to/data"])

Mirrors are tried in order; whichever one succeeds is promoted to first place. Every file is checked against the sha256 in the snapshot manifest, and anything that fails is discarded and the next mirror is tried — bad bytes are never handed to the caller.

Data integrity

Guarantee How
No silently-corrupted data Every file verified against the snapshot's sha256
No silently-changed history Sealed months are never rewritten
No unverifiable numbers Missing adjustment factors raise, instead of returning raw prices
No hidden survivorship bias Delisted stocks are kept in the universe and in the data (cn; see coverage table for hk/us limits)
Byte-for-byte reproduction Fixed-point integers, gzip with MTIME=0, no wall-clock timestamps

Delisted stocks matter. If your backtest universe only contains companies that are still listed today, your historical returns are systematically overstated. ov.symbols("cn", status="delisted") returns them, and their daily bars are complete over ipo … out.

Coverage and known gaps

Coverage is declared explicitly in meta/symbols/{market}.json under coverage, and ov.connect() prints it on startup. Current state:

Market Status Gaps
cn Daily bars 1990-12→present (A-shares incl. 1,187 delisted) + 461 indices No Beijing Stock Exchange (upstream source doesn't provide it); trade calendar starts 2000-01 (upstream boundary — earlier trading days must be inferred from bar dates); ETF/bond lists included, bars not collected
hk Daily bars 2,806 stocks + HSI index (index history from 2013-08) List is current listings only (survivorship bias, no IPO dates); calendar & index history start 2013-08 (free index source boundary)
us Daily bars 7,513 stocks + SPX index (index history from 2004-01) List is current listings only (survivorship bias, no IPO dates); 5,733 ETFs listed but bars not collected; calendar & index history start 2004-01 (free index source boundary)

hk / us symbol lists carry survivorship bias (the upstream source only returns currently-listed securities) and have no IPO dates. cn is unaffected: 1,187 delisted stocks are in the list and their full history (e.g. 600001.SH from 1998) is in the data.

An honest data project states its gaps. A dataset that quietly omits them is worse than one that is merely incomplete.

Performance

The store layout (one file per market per month, all symbols inside) is optimized for cross-section reads (one file = one whole trading day for the market) and for one-download-many-symbols workflows. Three layers make repeated reads fast:

  1. HTTP keep-alive — shard fetches reuse TLS connections instead of one handshake per request (cold-cache full-history pulls drop from minutes of handshake overhead).
  2. Shard LRU — connect(shard_cache=N) keeps N parsed monthly shards in memory (~20–25 MB each for recent full-market months; default 24 ≈ 500 MB).
  3. Per-symbol materialization (default on) — the first full-history read of a symbol writes a compact local copy under {cache}/symbols/{snapshot_id}/; any later read of the same symbol under the same snapshot is served in milliseconds and never touches a shard. Files are keyed by snapshot id, so a new snapshot invalidates them naturally. Disable with connect(materialize=False).

For SQL workflows, optionally materialize a snapshot into a local DuckDB file (pip install "ohlcvault[duckdb]" — an optional extra; the core stays zero-dependency):

p = ov.to_duckdb(store, markets=["cn"])   # writes {cache}/duckdb/{snapshot_id}.duckdb
con = ov.connect_duckdb(p)                # a local file, not a server
con.sql("SELECT d, close/1000.0 AS close FROM bars WHERE symbol='600519.SH' ORDER BY d")

to_duckdb() is idempotent per snapshot: an existing file for the same snapshot id is reused unless refresh=True. The DuckDB file is a derived local artifact — it is not part of the checksum chain and can be deleted/rebuilt at any time.

API

Function Purpose
connect(mirrors=, cache_dir=, snapshot=, shard_cache=, materialize=) Build the default client
symbols(market, type=, status=, board=) Symbol list
symbol(code) Single symbol entry
calendar(market, start=, end=) Trading calendar
daily(code, start=, end=) Stock daily bars, cross-month stitching
index_daily(code, start=, end=) Index daily bars
daily_many(codes, start=, end=) Batch read (preferred for backtests)
cross_section(market, date, sort_by=, limit=) Daily cross-section
adjust(bars_or_df, to="qfq"|"hfq"|"none") Adjustment view
to_duckdb(store, markets=, periods=) Materialize snapshot into a local DuckDB file (optional extra)
connect_duckdb(path) Open a materialized DuckDB file for SQL
snapshot() Current snapshot id

Date parameters (start / end / date) accept an int YYYYMMDD (preferred) or common string forms — "2026-09-18", "20260918", "2026/09/18". Anything unparseable raises DateError instead of failing deep inside the library.

The frozen data contract lives in SPEC.md — the client and the pipeline share nothing but this document and the files it describes. schema/example-*.json are machine-generated from real data, so the examples cannot drift from the contract.

License

MIT. See LICENSE.

Data is gathered from public sources. Verify before relying on it for anything consequential.

Release files for ohlcvault 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ohlcvault 0.2.1
File Size Uploaded
ohlcvault-0.2.1.tar.gz 57.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ohlcvault 0.2.1
File Interpreter ABI Platform
ohlcvault-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 94.8 kB

Release files / ohlcvault-0.2.1.tar.gz

Download URL ohlcvault-0.2.1.tar.gz
Size 57.1 kB
Tags Source
SHA-256 checksum
How to use checksums
8893d0ed49b36177c27c7751125a79343f76bde3f58a44b627fbe8351e027f47
BLAKE2b-256 checksum
How to use checksums
dc42ce060ccdb6321371122d5a1e52dadb07f191156da60f1ee5c4c8773fd7ce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.12

Release files / ohlcvault-0.2.1-py3-none-any.whl

Download URL ohlcvault-0.2.1-py3-none-any.whl
Size 37.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
416d8c103139d554ac2fda225ab74a739affe3e333c27bc9bd5c1aae909a642d
BLAKE2b-256 checksum
How to use checksums
20eff49079b7613edd6e9ae59b41a474ab4c31154653a87848937cc2bebf8c51
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.12

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page