numeraire-dataset 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- numeraire_dataset-0.1.0/.github/workflows/ci.yml +44 -0
- numeraire_dataset-0.1.0/.github/workflows/publish.yml +42 -0
- numeraire_dataset-0.1.0/.gitignore +26 -0
- numeraire_dataset-0.1.0/CHANGELOG.md +41 -0
- numeraire_dataset-0.1.0/LICENSE +28 -0
- numeraire_dataset-0.1.0/PKG-INFO +124 -0
- numeraire_dataset-0.1.0/README.md +83 -0
- numeraire_dataset-0.1.0/docs/data-zones-design.md +104 -0
- numeraire_dataset-0.1.0/pyproject.toml +83 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/__init__.py +28 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/_samples/fredmd_representative.csv.gz +0 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/builders/__init__.py +5 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/builders/fredmd.py +401 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/paths.py +47 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/py.typed +0 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/sources.py +99 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/zones/__init__.py +46 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/zones/clean.py +227 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/zones/lock.py +104 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/zones/raw.py +55 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/zones/steps.py +134 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/zones/view.py +42 -0
- numeraire_dataset-0.1.0/src/numeraire_dataset/zones/wrds.py +264 -0
- numeraire_dataset-0.1.0/tests/test_fredmd.py +70 -0
- numeraire_dataset-0.1.0/tests/test_fredmd_build.py +57 -0
- numeraire_dataset-0.1.0/tests/test_fredmd_cache.py +49 -0
- numeraire_dataset-0.1.0/tests/test_fredmd_download.py +16 -0
- numeraire_dataset-0.1.0/tests/test_fredmd_zip.py +47 -0
- numeraire_dataset-0.1.0/tests/test_sources.py +88 -0
- numeraire_dataset-0.1.0/tests/test_zones.py +330 -0
- numeraire_dataset-0.1.0/tests/test_zones_view.py +48 -0
- numeraire_dataset-0.1.0/tests/test_zones_wrds.py +216 -0
- numeraire_dataset-0.1.0/uv.lock +2525 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
# The single required check. The WRDS + network tests are deselected by default (pyproject
|
|
10
|
+
# addopts = "-m 'not network and not wrds'"), so CI never touches live credentials or the network.
|
|
11
|
+
check:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
# numeraire is not on PyPI yet; the local uv source ([tool.uv.sources]) pins the sibling
|
|
15
|
+
# checkout at ../numeraire, so provide both repos side by side exactly as they sit locally.
|
|
16
|
+
- name: Check out numeraire-dataset
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
path: numeraire-dataset
|
|
20
|
+
fetch-depth: 0 # full history so hatch-vcs derives the version
|
|
21
|
+
- name: Check out numeraire (dependency)
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
with:
|
|
24
|
+
repository: py-numeraire/numeraire
|
|
25
|
+
ref: main
|
|
26
|
+
path: numeraire
|
|
27
|
+
- name: Install uv
|
|
28
|
+
uses: astral-sh/setup-uv@v6
|
|
29
|
+
with: { python-version: "3.12" }
|
|
30
|
+
- name: Sync
|
|
31
|
+
working-directory: numeraire-dataset
|
|
32
|
+
run: uv sync --extra dev
|
|
33
|
+
- name: Lint & format
|
|
34
|
+
working-directory: numeraire-dataset
|
|
35
|
+
run: uv run ruff check . && uv run ruff format --check .
|
|
36
|
+
- name: Type check
|
|
37
|
+
working-directory: numeraire-dataset
|
|
38
|
+
run: uv run basedpyright src
|
|
39
|
+
- name: Tests
|
|
40
|
+
working-directory: numeraire-dataset
|
|
41
|
+
run: uv run pytest
|
|
42
|
+
- name: Build
|
|
43
|
+
working-directory: numeraire-dataset
|
|
44
|
+
run: uv build
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Publish to PyPI via Trusted Publishing (OIDC, no stored secrets/tokens).
|
|
2
|
+
#
|
|
3
|
+
# Inert until a PyPI Trusted Publisher is configured for py-numeraire/numeraire-dataset AND a
|
|
4
|
+
# GitHub Release is cut — an ordinary push never publishes. Publishing is decoupled from mainline:
|
|
5
|
+
#
|
|
6
|
+
# push to main -> CI only (ci.yml); NOT published
|
|
7
|
+
# push a tag (e.g. v*) -> nothing; a bare tag does not publish
|
|
8
|
+
# create a Release -> THIS workflow publishes that tag's version
|
|
9
|
+
#
|
|
10
|
+
# The release's tag drives the version (hatch-vcs), so the published version is exactly the tag
|
|
11
|
+
# with no manual version bookkeeping. A Release marked "pre-release" on an rc/beta/alpha tag (e.g.
|
|
12
|
+
# v0.1.0rc1) publishes a PyPI pre-release, which pip installs only with --pre.
|
|
13
|
+
#
|
|
14
|
+
# Release procedure:
|
|
15
|
+
# 1. Move CHANGELOG.md [Unreleased] entries under a new [X.Y.Z] heading (first release: 0.1.0).
|
|
16
|
+
# 2. gh release create vX.Y.Z --generate-notes (or --notes from CHANGELOG)
|
|
17
|
+
# 3. This workflow builds + publishes; verify `pip install numeraire-dataset==X.Y.Z`.
|
|
18
|
+
#
|
|
19
|
+
# workflow_dispatch is kept only as a manual fallback (e.g. re-run a failed publish); dispatch it
|
|
20
|
+
# against the release tag ref so the version resolves.
|
|
21
|
+
name: Publish
|
|
22
|
+
on:
|
|
23
|
+
release:
|
|
24
|
+
types: [published]
|
|
25
|
+
workflow_dispatch: {}
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
publish:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
environment: pypi
|
|
31
|
+
permissions:
|
|
32
|
+
id-token: write # required for PyPI Trusted Publishing (OIDC); no secrets
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
with: { fetch-depth: 0 } # full history so hatch-vcs derives the version from the tag
|
|
36
|
+
- uses: astral-sh/setup-uv@v6
|
|
37
|
+
with: { python-version: "3.12" }
|
|
38
|
+
- name: Build
|
|
39
|
+
run: uv build
|
|
40
|
+
- name: Check distributions
|
|
41
|
+
run: uvx twine check dist/* # ephemeral twine; the publish job needs no dev toolchain
|
|
42
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.venv/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.coverage
|
|
9
|
+
coverage.xml
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
|
|
13
|
+
# OS
|
|
14
|
+
.DS_Store
|
|
15
|
+
|
|
16
|
+
# Secrets / credentials (WRDS etc.)
|
|
17
|
+
.env
|
|
18
|
+
.env.*
|
|
19
|
+
*.pgpass
|
|
20
|
+
|
|
21
|
+
# Built data & downloads — never commit bulk outputs; only tiny curated slices are added explicitly.
|
|
22
|
+
*.parquet
|
|
23
|
+
data/
|
|
24
|
+
cache/
|
|
25
|
+
downloads/
|
|
26
|
+
~/.numeraire_data/
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
Versions are tag-driven (`hatch-vcs`); the first tagged release will be `0.1.0`.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
Everything below ships in the current tree and is slated for the first tagged release (`0.1.0`).
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`sources` — tidyfinance-backed loaders.** Thin loaders over
|
|
16
|
+
[tidyfinance](https://github.com/tidy-finance/py-tidyfinance) for standard public sources
|
|
17
|
+
(Fama-French factors, Goyal-Welch predictors, and the FRED / JKP / q-factor / OSAP surface it
|
|
18
|
+
covers). Frame loaders (`load_ff_factors`, `load_goyal_welch`) return tidy frames and stay
|
|
19
|
+
`numeraire`-free; the `*_view` helpers (`load_gw_view`) add an optional `numeraire` bridge and a
|
|
20
|
+
`data_vintage` provenance stamp. This frame-vs-view split is intentional — see the README note.
|
|
21
|
+
- **Raw → clean → view data zones.** A step registry with recipe-hash chaining
|
|
22
|
+
(`register_step` / `run_step` / `recipe_hash`), deterministic clean builders for the standard
|
|
23
|
+
WRDS-schema panels (`crsp_monthly_clean`, `compustat_annual_clean`,
|
|
24
|
+
`compustat_quarterly_clean`, `ccm_links_clean`), a `DataLock` from which a numeraire result's
|
|
25
|
+
`data_vintage` is derived, and a `zones.view` bridge (`to_cross_section_view`) that lifts a
|
|
26
|
+
clean panel into a numeraire `CrossSectionView`. The clean builders are pure transforms and run
|
|
27
|
+
without credentials; only `zones.wrds` performs live, credentialed pulls.
|
|
28
|
+
- **`builders/fredmd` — vintage-aware FRED-MD.** Self-built ETL for what tidyfinance does not
|
|
29
|
+
cover: FRED-MD monthly vintages (reference period x vintage x series) with McCracken-Ng tcodes
|
|
30
|
+
applied at build time (`transform=True`), plus vintage archive download/cache helpers.
|
|
31
|
+
- **WRDS zone (`zones.wrds`), optional `[wrds]` extra.** Live CRSP / Compustat pulls into the raw
|
|
32
|
+
zone from environment-only credentials (nothing written to disk), each cached as parquet with a
|
|
33
|
+
`_meta.json` query-hash sidecar in the outside-repo data home — licensed bytes never enter git.
|
|
34
|
+
Deselected in CI (network + wrds markers); the module imports fine without the extra.
|
|
35
|
+
- **Packaging** — BSD-3 license, PEP 561 `py.typed`, per-version classifiers, and CI (lint,
|
|
36
|
+
format, basedpyright, pytest, build) plus an inert Trusted-Publishing release workflow.
|
|
37
|
+
|
|
38
|
+
### Notes
|
|
39
|
+
|
|
40
|
+
- Ships **code, not data.** No licensed or redistributable data is bundled; build outputs land in
|
|
41
|
+
a local, git-ignored cache. Public/synthetic fixtures and mocked pulls only in tests.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Yuheng Wu
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: numeraire-dataset
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Open data builders for numeraire: fetch + clean public and WRDS-sourced data into tidy, point-in-time tables.
|
|
5
|
+
Project-URL: Homepage, https://github.com/py-numeraire/numeraire-dataset
|
|
6
|
+
Project-URL: Repository, https://github.com/py-numeraire/numeraire-dataset
|
|
7
|
+
Project-URL: Issues, https://github.com/py-numeraire/numeraire-dataset/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/py-numeraire/numeraire-dataset/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Yuheng Wu <wuyuheng20074@live.com>
|
|
10
|
+
License-Expression: BSD-3-Clause
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: asset-pricing,fred-md,macro,point-in-time,real-time-data,vintage
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: numpy>=1.26
|
|
25
|
+
Requires-Dist: pandas>=2.0
|
|
26
|
+
Requires-Dist: platformdirs>=4.0
|
|
27
|
+
Requires-Dist: pyarrow>=15
|
|
28
|
+
Requires-Dist: tidyfinance>=0.3
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: basedpyright>=1.18; extra == 'dev'
|
|
31
|
+
Requires-Dist: numeraire<0.3,>=0.2; extra == 'dev'
|
|
32
|
+
Requires-Dist: pandas-stubs>=2.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
36
|
+
Provides-Extra: numeraire
|
|
37
|
+
Requires-Dist: numeraire<0.3,>=0.2; extra == 'numeraire'
|
|
38
|
+
Provides-Extra: wrds
|
|
39
|
+
Requires-Dist: wrds>=3.2; extra == 'wrds'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# numeraire-dataset
|
|
43
|
+
|
|
44
|
+
Open, reproducible **data loaders + builders** for
|
|
45
|
+
[`numeraire`](https://github.com/py-numeraire/numeraire). This package ships **code, not data**: it
|
|
46
|
+
fetches public (and, with your own WRDS credentials, licensed) sources and cleans them into tidy,
|
|
47
|
+
**point-in-time** tables that `numeraire` consumes.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install numeraire-dataset # frame loaders + builders (tidyfinance backend)
|
|
53
|
+
pip install "numeraire-dataset[numeraire]" # + the point-in-time view helpers (numeraire bridge)
|
|
54
|
+
pip install "numeraire-dataset[wrds]" # + live CRSP/Compustat pulls (your own WRDS account)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Two layers
|
|
58
|
+
|
|
59
|
+
- **`sources`** — [**tidyfinance**](https://github.com/tidy-finance/py-tidyfinance) (MIT) is the
|
|
60
|
+
primary backend for standard sources (Fama-French, Goyal-Welch, FRED, JKP, q-factors, OSAP).
|
|
61
|
+
Thin loaders return tidy frames (+ an optional point-in-time view helper) with `data_vintage`
|
|
62
|
+
provenance — no loader hand-rolling.
|
|
63
|
+
- **`builders`** — self-built ETL for what tidyfinance does *not* cover: **vintage-aware FRED-MD**
|
|
64
|
+
(reference period × vintage × series, tcodes applied at build time) and, with your own
|
|
65
|
+
credentials, WRDS panels. (The FRED-MD vintage/tcode design is a candidate to upstream into
|
|
66
|
+
tidyfinance — once merged, its `sources` path could replace the local builder.)
|
|
67
|
+
|
|
68
|
+
## Why a separate project
|
|
69
|
+
|
|
70
|
+
`numeraire` (the framework) bundles only tiny public example slices. Anything that needs to be
|
|
71
|
+
**downloaded, merged across releases, or built from a subscription source** lives here, as
|
|
72
|
+
transparent open-source ETL — so the cleaning is auditable and re-runnable, and no licensed data is
|
|
73
|
+
ever redistributed inside a wheel. Build outputs land in a local cache (git-ignored), never the repo.
|
|
74
|
+
|
|
75
|
+
## Design: build once, read clean
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
raw vendor files ──build (this package)──▶ tidy point-in-time table ──read──▶ numeraire
|
|
79
|
+
(download) clean / tcode [reference period × vintage × series]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- **The vintage axis is a column.** Each value carries the *reference period* it describes and the
|
|
83
|
+
*vintage* (data release) it came from, so revisions are first-class and `asof` is leak-safe.
|
|
84
|
+
- **Stationarity transforms (FRED-MD tcodes) are applied at build time, per vintage** — never inside
|
|
85
|
+
numeraire's main pipeline. `transform=False` keeps raw levels.
|
|
86
|
+
- **Availability lag (`L`) is NOT baked in.** It stays a read-time parameter in numeraire, so you can
|
|
87
|
+
sweep it for robustness. The table stores only `(reference period, vintage, series…)`.
|
|
88
|
+
|
|
89
|
+
## Builders
|
|
90
|
+
|
|
91
|
+
| Builder | Source | License of output | Status |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| `fredmd` | FRED-MD monthly vintages (St. Louis Fed, McCracken-Ng) | public (cite McCracken-Ng 2016) | in progress |
|
|
94
|
+
| `famafrench` | Ken French Data Library | public (cite, carry copyright notice) | planned |
|
|
95
|
+
| `zones.wrds` | CRSP / Compustat panels (your WRDS account) | **not redistributable** — local only | implemented |
|
|
96
|
+
|
|
97
|
+
## Usage
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# Standard sources via tidyfinance (frames; + a view helper with the `numeraire` extra):
|
|
101
|
+
from numeraire_dataset import load_ff_factors, load_gw_view
|
|
102
|
+
|
|
103
|
+
ff = load_ff_factors() # date, mkt_excess, smb, hml, risk_free (decimals)
|
|
104
|
+
view, vintage = load_gw_view(start_date="1926-07-01", end_date="2020-12-31")
|
|
105
|
+
# → feed `view` straight into numeraire's backtest_forecast; `vintage` is the provenance stamp.
|
|
106
|
+
|
|
107
|
+
# Vintage-aware FRED-MD (what tidyfinance lacks) via the local builder:
|
|
108
|
+
from numeraire_dataset.builders import fredmd
|
|
109
|
+
|
|
110
|
+
paths = fredmd.download(vintages=["2025-01", "2025-02", "2025-03"], dest="~/.numeraire_data")
|
|
111
|
+
table = fredmd.build_table(paths, transform=True) # tidy [reference, vintage, series…]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Frame loaders vs. `*_view` helpers
|
|
115
|
+
|
|
116
|
+
The split is intentional, not an oversight. **Frame loaders** (`load_ff_factors`,
|
|
117
|
+
`load_goyal_welch`) return plain tidy `pandas` frames and carry **no `numeraire` dependency**, so
|
|
118
|
+
they are usable on their own. The **`*_view` helpers** (`load_gw_view`, `zones.view`) add the
|
|
119
|
+
optional bridge into a `numeraire` view plus a `data_vintage` stamp, and only import `numeraire`
|
|
120
|
+
lazily (install the `[numeraire]` extra). A frame loader is therefore a strict subset of the
|
|
121
|
+
work a view helper does — the names differ because their return contracts and dependency footprints
|
|
122
|
+
differ, and both are kept rather than collapsed into one signature.
|
|
123
|
+
|
|
124
|
+
License: **BSD-3-Clause** (code). Source datasets keep their own terms — see each builder's docstring.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# numeraire-dataset
|
|
2
|
+
|
|
3
|
+
Open, reproducible **data loaders + builders** for
|
|
4
|
+
[`numeraire`](https://github.com/py-numeraire/numeraire). This package ships **code, not data**: it
|
|
5
|
+
fetches public (and, with your own WRDS credentials, licensed) sources and cleans them into tidy,
|
|
6
|
+
**point-in-time** tables that `numeraire` consumes.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install numeraire-dataset # frame loaders + builders (tidyfinance backend)
|
|
12
|
+
pip install "numeraire-dataset[numeraire]" # + the point-in-time view helpers (numeraire bridge)
|
|
13
|
+
pip install "numeraire-dataset[wrds]" # + live CRSP/Compustat pulls (your own WRDS account)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Two layers
|
|
17
|
+
|
|
18
|
+
- **`sources`** — [**tidyfinance**](https://github.com/tidy-finance/py-tidyfinance) (MIT) is the
|
|
19
|
+
primary backend for standard sources (Fama-French, Goyal-Welch, FRED, JKP, q-factors, OSAP).
|
|
20
|
+
Thin loaders return tidy frames (+ an optional point-in-time view helper) with `data_vintage`
|
|
21
|
+
provenance — no loader hand-rolling.
|
|
22
|
+
- **`builders`** — self-built ETL for what tidyfinance does *not* cover: **vintage-aware FRED-MD**
|
|
23
|
+
(reference period × vintage × series, tcodes applied at build time) and, with your own
|
|
24
|
+
credentials, WRDS panels. (The FRED-MD vintage/tcode design is a candidate to upstream into
|
|
25
|
+
tidyfinance — once merged, its `sources` path could replace the local builder.)
|
|
26
|
+
|
|
27
|
+
## Why a separate project
|
|
28
|
+
|
|
29
|
+
`numeraire` (the framework) bundles only tiny public example slices. Anything that needs to be
|
|
30
|
+
**downloaded, merged across releases, or built from a subscription source** lives here, as
|
|
31
|
+
transparent open-source ETL — so the cleaning is auditable and re-runnable, and no licensed data is
|
|
32
|
+
ever redistributed inside a wheel. Build outputs land in a local cache (git-ignored), never the repo.
|
|
33
|
+
|
|
34
|
+
## Design: build once, read clean
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
raw vendor files ──build (this package)──▶ tidy point-in-time table ──read──▶ numeraire
|
|
38
|
+
(download) clean / tcode [reference period × vintage × series]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- **The vintage axis is a column.** Each value carries the *reference period* it describes and the
|
|
42
|
+
*vintage* (data release) it came from, so revisions are first-class and `asof` is leak-safe.
|
|
43
|
+
- **Stationarity transforms (FRED-MD tcodes) are applied at build time, per vintage** — never inside
|
|
44
|
+
numeraire's main pipeline. `transform=False` keeps raw levels.
|
|
45
|
+
- **Availability lag (`L`) is NOT baked in.** It stays a read-time parameter in numeraire, so you can
|
|
46
|
+
sweep it for robustness. The table stores only `(reference period, vintage, series…)`.
|
|
47
|
+
|
|
48
|
+
## Builders
|
|
49
|
+
|
|
50
|
+
| Builder | Source | License of output | Status |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| `fredmd` | FRED-MD monthly vintages (St. Louis Fed, McCracken-Ng) | public (cite McCracken-Ng 2016) | in progress |
|
|
53
|
+
| `famafrench` | Ken French Data Library | public (cite, carry copyright notice) | planned |
|
|
54
|
+
| `zones.wrds` | CRSP / Compustat panels (your WRDS account) | **not redistributable** — local only | implemented |
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# Standard sources via tidyfinance (frames; + a view helper with the `numeraire` extra):
|
|
60
|
+
from numeraire_dataset import load_ff_factors, load_gw_view
|
|
61
|
+
|
|
62
|
+
ff = load_ff_factors() # date, mkt_excess, smb, hml, risk_free (decimals)
|
|
63
|
+
view, vintage = load_gw_view(start_date="1926-07-01", end_date="2020-12-31")
|
|
64
|
+
# → feed `view` straight into numeraire's backtest_forecast; `vintage` is the provenance stamp.
|
|
65
|
+
|
|
66
|
+
# Vintage-aware FRED-MD (what tidyfinance lacks) via the local builder:
|
|
67
|
+
from numeraire_dataset.builders import fredmd
|
|
68
|
+
|
|
69
|
+
paths = fredmd.download(vintages=["2025-01", "2025-02", "2025-03"], dest="~/.numeraire_data")
|
|
70
|
+
table = fredmd.build_table(paths, transform=True) # tidy [reference, vintage, series…]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Frame loaders vs. `*_view` helpers
|
|
74
|
+
|
|
75
|
+
The split is intentional, not an oversight. **Frame loaders** (`load_ff_factors`,
|
|
76
|
+
`load_goyal_welch`) return plain tidy `pandas` frames and carry **no `numeraire` dependency**, so
|
|
77
|
+
they are usable on their own. The **`*_view` helpers** (`load_gw_view`, `zones.view`) add the
|
|
78
|
+
optional bridge into a `numeraire` view plus a `data_vintage` stamp, and only import `numeraire`
|
|
79
|
+
lazily (install the `[numeraire]` extra). A frame loader is therefore a strict subset of the
|
|
80
|
+
work a view helper does — the names differ because their return contracts and dependency footprints
|
|
81
|
+
differ, and both are kept rather than collapsed into one signature.
|
|
82
|
+
|
|
83
|
+
License: **BSD-3-Clause** (code). Source datasets keep their own terms — see each builder's docstring.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Data zones: raw → clean → view (design)
|
|
2
|
+
|
|
3
|
+
> Design note for the WRDS-scale data lifecycle in `numeraire-dataset`. Written before the
|
|
4
|
+
> implementation (W3-1). The three-zone split makes preprocessing **reproducible and pinnable**:
|
|
5
|
+
> a numeraire result's `data_vintage` string must trace back, unambiguously, to exactly the bytes
|
|
6
|
+
> and the exact transform recipe that produced its inputs.
|
|
7
|
+
|
|
8
|
+
## Why three zones
|
|
9
|
+
|
|
10
|
+
Preprocessing is part of the method. A Sharpe or an alpha is only reproducible if the *cleaning* —
|
|
11
|
+
delisting adjustments, share-code filters, CCM link windows, the accounting-to-return lag — is
|
|
12
|
+
pinned as tightly as the model. So the pipeline is split into three zones with hard boundaries:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
raw/ immutable download cache, keyed by (source, vintage) — bytes as pulled, never edited
|
|
16
|
+
clean/ deterministic transforms of raw → tidy PIT tables — a recipe hash pins the transform
|
|
17
|
+
view/ numeraire TimeSeriesView / CrossSectionView builders — lazy numeraire import, no new state
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Arrows point one way: `view` reads `clean`, `clean` reads `raw`, `raw` reads the outside world.
|
|
21
|
+
Nothing downstream mutates an upstream zone.
|
|
22
|
+
|
|
23
|
+
### raw — immutable, pull-stamped
|
|
24
|
+
|
|
25
|
+
- One directory per `(source, vintage)`, e.g. `raw/crsp_msf/2024-12/`. `vintage` is the pull's
|
|
26
|
+
identity: a WRDS query date, a FRED-MD month, a Ken French release. Content is the bytes exactly
|
|
27
|
+
as pulled (parquet/csv), **never edited in place** — a re-pull with different content is a new
|
|
28
|
+
vintage directory, not an overwrite.
|
|
29
|
+
- Each pull writes a sibling `_meta.json`: `{source, vintage, pulled_at, query_hash, pit_status,
|
|
30
|
+
row_count, content_digest}`. `pit_status` records whether the pull is itself point-in-time
|
|
31
|
+
(a dated vintage) or a latest-snapshot convenience pull (revised, not PIT) — so a downstream
|
|
32
|
+
golden can refuse a non-PIT source.
|
|
33
|
+
- **The cache directory is user-configurable and defaults OUTSIDE the repo** (`$NUMERAIRE_DATA` →
|
|
34
|
+
platform cache dir, per `paths.data_home`). Licensed data (CRSP/Compustat) lives only here, on
|
|
35
|
+
the user's machine behind their own WRDS credentials — **never in any git history**.
|
|
36
|
+
|
|
37
|
+
### clean — deterministic, recipe-hashed
|
|
38
|
+
|
|
39
|
+
- A `clean` table is produced by one or more **steps**: pure `frame(s) → frame` functions with
|
|
40
|
+
**all parameters explicitly declared** (no hidden globals, no ambient config). A step is
|
|
41
|
+
deterministic: same inputs + same params → same output, bit-for-bit.
|
|
42
|
+
- Every step's identity is `(name, version, params)`. Its **recipe hash** is the SHA-256 of the
|
|
43
|
+
canonical JSON of `{name, version, params, inputs: [<recipe hash of each input>]}`. The hash
|
|
44
|
+
therefore **chains**: a clean table's recipe hash transitively pins every upstream transform and
|
|
45
|
+
every raw vintage that fed it. Bump `version` when a step's logic changes in a way that should
|
|
46
|
+
invalidate downstream caches.
|
|
47
|
+
- Steps register in an **open registry** (the same pattern as numeraire's evaluator registry —
|
|
48
|
+
`register_step` / `get_step` / `available_steps`, a module-global dict, `KeyError` on a duplicate
|
|
49
|
+
name unless `overwrite`). No second registration style is invented. A `@step(...)` decorator is
|
|
50
|
+
sugar over `register_step`.
|
|
51
|
+
|
|
52
|
+
### view — numeraire builders, lazy
|
|
53
|
+
|
|
54
|
+
- `view` builders turn a `clean` table into a numeraire `TimeSeriesView` / `CrossSectionView`.
|
|
55
|
+
They import `numeraire` **lazily** (as `sources.to_timeseries_view` already does), so the clean +
|
|
56
|
+
raw zones stay numeraire-free and installable without it. `view` adds no persisted state.
|
|
57
|
+
|
|
58
|
+
## `data.lock.json`
|
|
59
|
+
|
|
60
|
+
A single lock file records, for each built `clean` (and pinned `raw`) artifact, exactly what it is:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"version": 1,
|
|
65
|
+
"artifacts": {
|
|
66
|
+
"crsp_monthly_clean": {
|
|
67
|
+
"kind": "clean",
|
|
68
|
+
"step": "crsp_monthly_clean",
|
|
69
|
+
"step_version": 1,
|
|
70
|
+
"recipe_hash": "sha256:9f3c…",
|
|
71
|
+
"content_digest": "sha256:1a2b…",
|
|
72
|
+
"inputs": ["crsp_msf@2024-12", "crsp_msenames@2024-12"],
|
|
73
|
+
"rows": 4193021,
|
|
74
|
+
"built_at": "2026-07-05T…"
|
|
75
|
+
},
|
|
76
|
+
"crsp_msf@2024-12": {
|
|
77
|
+
"kind": "raw", "source": "crsp_msf", "vintage": "2024-12",
|
|
78
|
+
"content_digest": "sha256:…", "pit_status": "vintage"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- `recipe_hash` pins the transform; `content_digest` pins the actual output bytes (so a corrupted
|
|
85
|
+
or partial build is detectable independently of the recipe).
|
|
86
|
+
- **`data_vintage` derivation (the contract with numeraire).** The `data_vintage` string a numeraire
|
|
87
|
+
result carries for a table `T` is derived *directly* from `T`'s lock entry:
|
|
88
|
+
`data_vintage = f"{name}@{recipe_hash[:12]}"` (e.g. `crsp_monthly_clean@9f3c1d2e4a5b`). Given a
|
|
89
|
+
result's `data_vintage`, the lock entry — and thus the full recipe + raw vintages — is recoverable
|
|
90
|
+
by name and hash prefix. No provenance is stored in two places that could disagree.
|
|
91
|
+
|
|
92
|
+
## WRDS goldens ride the `WRDS-CRED` tier
|
|
93
|
+
|
|
94
|
+
The reproductions that need CRSP/Compustat (W3-2 GKX-lite, W3-3 ALZ empirical, W3-4 HXZ) register
|
|
95
|
+
their targets as `numeraire.golden.GoldenCase(tier=WRDS_CRED, available=<creds+cache present>)`.
|
|
96
|
+
That mechanism (built in F3) makes them **skip in CI** and run verbatim where the user's WRDS
|
|
97
|
+
credentials and raw cache are present — one code path, no forked assertions, no licensed bytes in
|
|
98
|
+
the repo. Live WRDS tests carry `@pytest.mark.wrds` and are deselected by default.
|
|
99
|
+
|
|
100
|
+
## Boundaries restated (red lines)
|
|
101
|
+
|
|
102
|
+
- WRDS credentials go through env / the tidyfinance WRDS config — never committed, never in code.
|
|
103
|
+
- The raw cache is user-configurable and outside the repo by default.
|
|
104
|
+
- No licensed data (CRSP/Compustat/JKP returns) in any tracked file or git history — ever.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "numeraire-dataset"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Open data builders for numeraire: fetch + clean public and WRDS-sourced data into tidy, point-in-time tables."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "BSD-3-Clause"
|
|
12
|
+
authors = [{ name = "Yuheng Wu", email = "wuyuheng20074@live.com" }]
|
|
13
|
+
keywords = ["asset-pricing", "macro", "real-time-data", "fred-md", "point-in-time", "vintage"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"License :: OSI Approved :: BSD License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Scientific/Engineering",
|
|
23
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"numpy>=1.26",
|
|
28
|
+
"pandas>=2.0",
|
|
29
|
+
"pyarrow>=15",
|
|
30
|
+
"platformdirs>=4.0",
|
|
31
|
+
# tidyfinance is the primary backend for standard sources (Fama-French, Goyal-Welch, FRED, JKP,
|
|
32
|
+
# q-factors, OSAP). The local builders cover what it lacks: vintage FRED-MD (until upstreamed) and
|
|
33
|
+
# licensed WRDS panels.
|
|
34
|
+
"tidyfinance>=0.3",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
# WRDS-sourced builders (factor panels) bring their own heavy deps; public builders need none.
|
|
39
|
+
wrds = ["wrds>=3.2"]
|
|
40
|
+
# The tidyfinance loaders return tidy frames with no numeraire dependency; install this extra to also
|
|
41
|
+
# get the point-in-time TimeSeriesView helper (numeraire is not on PyPI yet — see [tool.uv.sources]).
|
|
42
|
+
numeraire = ["numeraire>=0.2,<0.3"]
|
|
43
|
+
dev = [
|
|
44
|
+
"pytest>=8.0", "pytest-cov>=5.0",
|
|
45
|
+
"ruff>=0.6", "basedpyright>=1.18", "pandas-stubs>=2.0",
|
|
46
|
+
"numeraire>=0.2,<0.3", # exercise the view helper in tests
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# numeraire is not on PyPI yet — resolve it from the sibling checkout for local dev.
|
|
50
|
+
[tool.uv.sources]
|
|
51
|
+
numeraire = { path = "../numeraire", editable = true }
|
|
52
|
+
|
|
53
|
+
[project.urls]
|
|
54
|
+
Homepage = "https://github.com/py-numeraire/numeraire-dataset"
|
|
55
|
+
Repository = "https://github.com/py-numeraire/numeraire-dataset"
|
|
56
|
+
Issues = "https://github.com/py-numeraire/numeraire-dataset/issues"
|
|
57
|
+
Changelog = "https://github.com/py-numeraire/numeraire-dataset/blob/main/CHANGELOG.md"
|
|
58
|
+
|
|
59
|
+
[tool.hatch.version]
|
|
60
|
+
source = "vcs"
|
|
61
|
+
|
|
62
|
+
[tool.hatch.build.targets.wheel]
|
|
63
|
+
packages = ["src/numeraire_dataset"]
|
|
64
|
+
|
|
65
|
+
[tool.ruff]
|
|
66
|
+
line-length = 100
|
|
67
|
+
src = ["src", "tests"]
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "NPY", "PD", "RUF"]
|
|
71
|
+
|
|
72
|
+
[tool.basedpyright]
|
|
73
|
+
# Standard (not strict): this is a data-prep tool, not numeraire's core spine.
|
|
74
|
+
typeCheckingMode = "standard"
|
|
75
|
+
include = ["src/numeraire_dataset"]
|
|
76
|
+
|
|
77
|
+
[tool.pytest.ini_options]
|
|
78
|
+
testpaths = ["tests"]
|
|
79
|
+
addopts = "-m 'not network and not wrds'"
|
|
80
|
+
markers = [
|
|
81
|
+
"network: hits a live data source (deselected by default; run with -m network)",
|
|
82
|
+
"wrds: hits live WRDS behind the user's own credentials (deselected; run with -m wrds)",
|
|
83
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""numeraire-dataset: open, reproducible builders + loaders that fetch and clean data for numeraire.
|
|
2
|
+
|
|
3
|
+
Ships code, not data. Two layers:
|
|
4
|
+
|
|
5
|
+
- :mod:`numeraire_dataset.sources` — tidyfinance-backed loaders for standard sources (Fama-French,
|
|
6
|
+
Goyal-Welch, …), returning tidy frames (+ an optional point-in-time view helper).
|
|
7
|
+
- :mod:`numeraire_dataset.builders` — self-built ETL for what tidyfinance lacks: vintage-aware
|
|
8
|
+
FRED-MD (reference period x vintage x series, tcodes at build time) and, with your own
|
|
9
|
+
credentials, WRDS panels.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from numeraire_dataset.sources import (
|
|
15
|
+
data_vintage,
|
|
16
|
+
load_ff_factors,
|
|
17
|
+
load_goyal_welch,
|
|
18
|
+
load_gw_view,
|
|
19
|
+
to_timeseries_view,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"data_vintage",
|
|
24
|
+
"load_ff_factors",
|
|
25
|
+
"load_goyal_welch",
|
|
26
|
+
"load_gw_view",
|
|
27
|
+
"to_timeseries_view",
|
|
28
|
+
]
|
|
Binary file
|