Skip to main content

chainmill

Zipped option chains in, queryable store out.

tests PyPI Python License: MIT

Parallel ingest of vendor option-chain archives into SQLite, with resumable builds and a coverage line you can quote. Map: one worker parses one archive. Reduce: the parent folds every frame into one store, on one connection.

pip install chainmill

Thirty seconds

from chainmill import build, ChainQuery

if __name__ == "__main__":                      # required on macOS and Windows
    report = build("raw_archives/", "chains.db", max_workers=8)
    print(report.summary())
60/60 archives ingested (0 skipped, 0 failed) - 120,000 rows, 60 sessions
2024-01-02 to 2024-03-01

Then query it:

with ChainQuery("chains.db") as q:
    chain = q.chain("2024-01-02", symbol="SPY", option_type="P")
    wide  = q.strikes("2024-01-02", 400, 460)
    busy  = q.liquid("2024-01-02", min_volume=500)
    panel = q.daily_summary()

Re-running skips archives already ingested, so an interrupted multi-year load resumes instead of duplicating rows.

When you outgrow SQLite, there is a door out:

with ChainQuery("chains.db") as q:
    q.to_parquet("chains.parquet")                        # one file, streamed
    q.to_parquet("chains/", partition_by_session=True)    # Hive layout, date=YYYY-MM-DD/

Streamed in batches rather than loaded whole, so a multi-year store does not have to fit in memory. Needs pip install chainmill[parquet].

What it handles

  • Vendor drift. Headers matched case-insensitively with aliases (iv → implied_volatility, volume → trade_volume, expiry → expiration). A file genuinely missing required columns is reported by name, never silently coerced.
  • Bad archives. A corrupt or dateless file is recorded as a failure in the report; it does not abort a 1,400-archive build.
  • Multi-member archives. All CSV members concatenated.
  • Session dates parsed from chain_2024-01-02.zip, SPY20240102.zip or x_2024_01_02.zip, or passed explicitly.
  • Coverage reporting. Rows in, archives ingested / skipped / failed, and the session range — the line a result should be quoted with.

Throughput on a four-core laptop: 60 archives / 120,000 rows in 2.0s including index build. Real archives are larger and compression-bound.

Where this fits

If you want Use
A market-data platform with feeds OpenBB
A general dataframe / warehouse layer DuckDB, Polars, Parquet
To turn a folder of vendor zips into something queryable, correctly chainmill

SQLite and pandas, nothing else — pyarrow only if you export. It is an ingest and lookup layer, not an analytics engine and not a distributed system. Beyond one machine, to_parquet() and point a real warehouse at it.

No market data is included — option-chain licences do not permit redistribution.

Why this exists

This replaces five hand-rolled extractors that accumulated in a private research program over two years. Before rewriting anything, each was tested against a synthetic archive:

extractor rows parsed rows persisted
optimized_extractor 0 0
extract_and_index 50 none — map only
robust_extractor 50 50
space_efficient_processor 50 0

Four defects, each producing a plausible-looking run:

The reader outlived its archive. pd.read_csv(handle, chunksize=...) returns a lazy reader. It was created inside with ZipFile(...) / with archive.open(...) and iterated after both closed — so every archive raised I/O operation on closed file, was swallowed by a bare except, and reported zero rows while printing a line that looked like progress. Here the member is read to completion inside the block, with a regression test that touches the frame after the archive has closed.

Workers were bound methods. Submitting self.extract_file to a ProcessPoolExecutor pickles the entire processor to every worker. Each mutated its own copy of the in-memory indexes; those mutations died with the process, and the parent then wrote the empty originals to disk. The map step here is a module-level function of a path, with nothing shared.

The reduce step was distributed. Workers each opened their own SQLite connection and inserted row-by-row in a Python loop — lock contention plus per-row overhead, and the reason one extractor persisted nothing. One writer, executemany, batched transactions, WAL.

Indexes were built before the load, so every insert paid for them. create_indexes() now runs once, after.

Tests

pip install -e ".[test]"
pytest -q

38 tests, no fixtures on disk — archives are synthesised in tmp_path. Includes an equivalence test asserting the parallel and in-process builds produce identical stores, which is what keeps the map step honest about being a pure function.

Licence

MIT. See CHANGELOG.md and CONTRIBUTING.md.

Metadata

Release files for chainmill 0.1.0

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

Source distribution (sdist)

Source distribution for chainmill 0.1.0
File Size Uploaded Provenance Actions
chainmill-0.1.0.tar.gz 15.8 kB With provenance Details for chainmill-0.1.0.tar.gz

Built distribution (wheel)

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

Total release size: 29.7 kB

Release files / chainmill-0.1.0.tar.gz

Download URL chainmill-0.1.0.tar.gz
Size 15.8 kB
Tags Source
SHA-256 checksum
How to use checksums
45639d1d00bd14052dce9905d4de53d37e68a57358246eac065545f24a408cb4
BLAKE2b-256 checksum
How to use checksums
32b8a3fa4527ab976f35460edd37eb65f733f35381836a8820638202c35c7d42
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 3, 2026.

Transparency log

Release files / chainmill-0.1.0-py3-none-any.whl

Download URL chainmill-0.1.0-py3-none-any.whl
Size 13.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6af9e47ae089bd72a90673bba67f04ba4d88ee2c0c73b65e73f3c8b9f9f96528
BLAKE2b-256 checksum
How to use checksums
74e3a94180e264a664c504f02f67789f983a7eefc049421ef131547d346f7122
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 3, 2026.

Transparency log

Security for chainmill 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for chainmill-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 3, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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