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.
Files changed (33) hide show
  1. numeraire_dataset-0.1.0/.github/workflows/ci.yml +44 -0
  2. numeraire_dataset-0.1.0/.github/workflows/publish.yml +42 -0
  3. numeraire_dataset-0.1.0/.gitignore +26 -0
  4. numeraire_dataset-0.1.0/CHANGELOG.md +41 -0
  5. numeraire_dataset-0.1.0/LICENSE +28 -0
  6. numeraire_dataset-0.1.0/PKG-INFO +124 -0
  7. numeraire_dataset-0.1.0/README.md +83 -0
  8. numeraire_dataset-0.1.0/docs/data-zones-design.md +104 -0
  9. numeraire_dataset-0.1.0/pyproject.toml +83 -0
  10. numeraire_dataset-0.1.0/src/numeraire_dataset/__init__.py +28 -0
  11. numeraire_dataset-0.1.0/src/numeraire_dataset/_samples/fredmd_representative.csv.gz +0 -0
  12. numeraire_dataset-0.1.0/src/numeraire_dataset/builders/__init__.py +5 -0
  13. numeraire_dataset-0.1.0/src/numeraire_dataset/builders/fredmd.py +401 -0
  14. numeraire_dataset-0.1.0/src/numeraire_dataset/paths.py +47 -0
  15. numeraire_dataset-0.1.0/src/numeraire_dataset/py.typed +0 -0
  16. numeraire_dataset-0.1.0/src/numeraire_dataset/sources.py +99 -0
  17. numeraire_dataset-0.1.0/src/numeraire_dataset/zones/__init__.py +46 -0
  18. numeraire_dataset-0.1.0/src/numeraire_dataset/zones/clean.py +227 -0
  19. numeraire_dataset-0.1.0/src/numeraire_dataset/zones/lock.py +104 -0
  20. numeraire_dataset-0.1.0/src/numeraire_dataset/zones/raw.py +55 -0
  21. numeraire_dataset-0.1.0/src/numeraire_dataset/zones/steps.py +134 -0
  22. numeraire_dataset-0.1.0/src/numeraire_dataset/zones/view.py +42 -0
  23. numeraire_dataset-0.1.0/src/numeraire_dataset/zones/wrds.py +264 -0
  24. numeraire_dataset-0.1.0/tests/test_fredmd.py +70 -0
  25. numeraire_dataset-0.1.0/tests/test_fredmd_build.py +57 -0
  26. numeraire_dataset-0.1.0/tests/test_fredmd_cache.py +49 -0
  27. numeraire_dataset-0.1.0/tests/test_fredmd_download.py +16 -0
  28. numeraire_dataset-0.1.0/tests/test_fredmd_zip.py +47 -0
  29. numeraire_dataset-0.1.0/tests/test_sources.py +88 -0
  30. numeraire_dataset-0.1.0/tests/test_zones.py +330 -0
  31. numeraire_dataset-0.1.0/tests/test_zones_view.py +48 -0
  32. numeraire_dataset-0.1.0/tests/test_zones_wrds.py +216 -0
  33. 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
+ ]
@@ -0,0 +1,5 @@
1
+ """Data builders. Each module fetches one source and emits a tidy point-in-time table."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __all__: list[str] = []