ohlcvault 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.
@@ -0,0 +1,24 @@
1
+ # 构建产物
2
+ build/
3
+ dist/
4
+ *.egg-info/
5
+ __pycache__/
6
+ *.py[cod]
7
+
8
+ # 虚拟环境
9
+ .venv/
10
+ venv/
11
+
12
+ # 测试与缓存
13
+ .pytest_cache/
14
+ .ruff_cache/
15
+ .coverage
16
+ htmlcov/
17
+
18
+ # 编辑器 / 系统
19
+ .DS_Store
20
+ .idea/
21
+ .vscode/
22
+
23
+ # 契约镜像是**生成物**,由 ohlcvault_server/tools/sync_contract.py 写入,
24
+ # 但**必须提交**(消费方要能独立校验契约),所以这里不忽略。
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HelpQuant
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.5
2
+ Name: ohlcvault
3
+ Version: 0.1.0
4
+ Summary: Reproducible, checksummed daily OHLCV data for A-share, HK and US markets — static files, content-addressed snapshots, zero API keys.
5
+ Project-URL: Homepage, https://helpquant.com
6
+ Project-URL: Documentation, https://github.com/helpquant/ohlcvault#readme
7
+ Project-URL: Issues, https://github.com/helpquant/ohlcvault/issues
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: a-share,backtest,hk-stocks,market-data,ohlcv,quant,reproducible,us-stocks
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
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
+ Requires-Python: >=3.10
22
+ Provides-Extra: dev
23
+ Requires-Dist: pandas>=2.0; extra == 'dev'
24
+ Requires-Dist: pytest>=8.0; extra == 'dev'
25
+ Requires-Dist: ruff>=0.6; extra == 'dev'
26
+ Provides-Extra: pandas
27
+ Requires-Dist: pandas>=2.0; extra == 'pandas'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # OHLCVault
31
+
32
+ > OHLCV = Open/High/Low/Close/Volume, the universal bar format.
33
+ > Vault = immutable, checksummed snapshots.
34
+
35
+ Reproducible daily OHLCV data for **A-share, Hong Kong and US markets** — served as
36
+ static files from a CDN. No API keys, no rate limits, no per-request billing.
37
+
38
+ ```python
39
+ import ohlcvault as ov
40
+
41
+ ov.connect()
42
+ df = ov.daily("600519.SH").to_pandas() # 跨月自动拼接
43
+ ov.cross_section("cn", 20260918, limit=50) # 当日全市场截面,按成交额排序
44
+ ```
45
+
46
+ ## Why this exists
47
+
48
+ Most free market-data endpoints are **APIs**: stateful, rate-limited, silently
49
+ revised, and impossible to reproduce. A backtest that ran last month can't be rerun
50
+ today with the same inputs.
51
+
52
+ OHLCVault publishes **immutable monthly shards with checksums** instead. Each read is
53
+ anchored to a snapshot id you can write down and reproduce later, on any machine.
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ pip install ohlcvault # core, zero runtime dependencies
59
+ pip install "ohlcvault[pandas]" # + DataFrame helpers
60
+ ```
61
+
62
+ The core has **no third-party dependencies** — only the standard library. That's
63
+ deliberate: a data client shouldn't drag a dependency tree into your project, and it
64
+ matters even more for agent/tooling contexts.
65
+
66
+ ## Usage
67
+
68
+ ### Everything (the five things you actually need)
69
+
70
+ ```python
71
+ import ohlcvault as ov
72
+ ov.connect()
73
+
74
+ # 1. Trading calendar
75
+ ov.calendar("cn", start=20260101)
76
+
77
+ # 2. Stock daily bars (cross-month stitching is handled for you)
78
+ ov.daily("600519.SH", start=20260101, end=20260918)
79
+
80
+ # 3. Index daily bars — a separate namespace, never mixed with stocks
81
+ ov.index_daily("000300.SH")
82
+
83
+ # 4. Symbol list — includes delisted stocks
84
+ ov.symbols("cn", type="stock")
85
+ ov.symbols("cn", type="stock", status="delisted")
86
+
87
+ # 5. Daily cross-section — sorted by turnover, no extra data files
88
+ ov.cross_section("cn", 20260918, sort_by="amount", limit=50)
89
+ ```
90
+
91
+ ### Batch backtests
92
+
93
+ Month shards hold **every symbol in the market** for that month. Loading 200 symbols
94
+ one-by-one would decompress the same file 200 times:
95
+
96
+ ```python
97
+ bars = ov.daily_many(["600519.SH", "000001.SZ", "300750.SZ"], start=20260101)
98
+ bars["600519.SH"].to_pandas()
99
+ ```
100
+
101
+ ### Adjustment is a view, not a stored field
102
+
103
+ The dataset stores **unadjusted prices only**, plus the official cumulative
104
+ back-adjustment factor. Forward/backward adjusted prices are computed client-side:
105
+
106
+ ```python
107
+ b = ov.daily("600519.SH")
108
+ ov.adjust(b, to="hfq") # 后复权
109
+ ov.adjust(b, to="qfq") # 前复权
110
+ ```
111
+
112
+ This is not a limitation — it's the reason historical files never change. If
113
+ forward-adjusted prices were stored, every dividend would rewrite all of history, and
114
+ `immutable` caching would be impossible.
115
+
116
+ ### Reproducibility
117
+
118
+ ```python
119
+ st = ov.connect()
120
+ sid = st.snapshot # e.g. "6b197df3723871c5"
121
+ ov.connect(snapshot=sid) # later, anywhere: exact same inputs
122
+ ```
123
+
124
+ ### Offline / self-hosted mirrors
125
+
126
+ A mirror can be an HTTP(S) URL **or a local directory**:
127
+
128
+ ```python
129
+ ov.connect(mirrors=["/path/to/data"])
130
+ ```
131
+
132
+ Mirrors are tried in order; whichever one succeeds is promoted to first place. Every
133
+ file is checked against the `sha256` in the snapshot manifest, and anything that
134
+ fails is **discarded and the next mirror is tried** — bad bytes are never handed to
135
+ the caller.
136
+
137
+ ## Data integrity
138
+
139
+ | Guarantee | How |
140
+ |---|---|
141
+ | No silently-corrupted data | Every file verified against the snapshot's `sha256` |
142
+ | No silently-changed history | Sealed months are never rewritten |
143
+ | No unverifiable numbers | Missing adjustment factors raise, instead of returning raw prices |
144
+ | No hidden survivorship bias | Delisted stocks are kept in the universe and in the data |
145
+ | Byte-for-byte reproduction | Fixed-point integers, `gzip` with `MTIME=0`, no wall-clock timestamps |
146
+
147
+ **Delisted stocks matter.** If your backtest universe only contains companies that
148
+ are still listed today, your historical returns are systematically overstated.
149
+ `ov.symbols("cn", status="delisted")` returns them, and their daily bars are complete
150
+ over `ipo … out`.
151
+
152
+ ## Coverage and known gaps
153
+
154
+ Coverage is declared explicitly in `meta/symbols/{market}.json` under `coverage`, and
155
+ `ov.connect()` prints it on startup. Current state:
156
+
157
+ | Market | Status | Gaps |
158
+ |---|---|---|
159
+ | `cn` | Daily bars + indices, 2000→present | **No Beijing Stock Exchange** (upstream source doesn't provide it) |
160
+ | `hk` | Symbol list only | No daily bars yet; **list is current listings only** |
161
+ | `us` | Symbol list only | No daily bars yet; **list is current listings only** |
162
+
163
+ `hk` / `us` symbol lists carry **survivorship bias** (the upstream source only returns
164
+ currently-listed securities) and have no IPO dates. Their daily bars are pending.
165
+
166
+ An honest data project states its gaps. A dataset that quietly omits them is worse
167
+ than one that is merely incomplete.
168
+
169
+ ## API
170
+
171
+ | Function | Purpose |
172
+ |---|---|
173
+ | `connect(mirrors=, cache_dir=, snapshot=)` | Build the default client |
174
+ | `symbols(market, type=, status=, board=)` | Symbol list |
175
+ | `symbol(code)` | Single symbol entry |
176
+ | `calendar(market, start=, end=)` | Trading calendar |
177
+ | `daily(code, start=, end=)` | Stock daily bars, cross-month stitching |
178
+ | `index_daily(code, start=, end=)` | Index daily bars |
179
+ | `daily_many(codes, start=, end=)` | Batch read (preferred for backtests) |
180
+ | `cross_section(market, date, sort_by=, limit=)` | Daily cross-section |
181
+ | `adjust(bars_or_df, to="qfq"\|"hfq"\|"none")` | Adjustment view |
182
+ | `snapshot()` | Current snapshot id |
183
+
184
+ Date parameters (`start` / `end` / `date`) accept an `int` `YYYYMMDD` (preferred)
185
+ or common string forms — `"2026-09-18"`, `"20260918"`, `"2026/09/18"`. Anything
186
+ unparseable raises `DateError` instead of failing deep inside the library.
187
+
188
+ The frozen data contract lives in [`SPEC.md`](SPEC.md) — the client and the pipeline
189
+ share nothing but this document and the files it describes. `schema/example-*.json`
190
+ are machine-generated from real data, so the examples cannot drift from the contract.
191
+
192
+ ## License
193
+
194
+ MIT. See [LICENSE](LICENSE).
195
+
196
+ Data is gathered from public sources. Verify before relying on it for anything
197
+ consequential.
@@ -0,0 +1,168 @@
1
+ # OHLCVault
2
+
3
+ > OHLCV = Open/High/Low/Close/Volume, the universal bar format.
4
+ > Vault = immutable, checksummed snapshots.
5
+
6
+ Reproducible daily OHLCV data for **A-share, Hong Kong and US markets** — served as
7
+ static files from a CDN. No API keys, no rate limits, no per-request billing.
8
+
9
+ ```python
10
+ import ohlcvault as ov
11
+
12
+ ov.connect()
13
+ df = ov.daily("600519.SH").to_pandas() # 跨月自动拼接
14
+ ov.cross_section("cn", 20260918, limit=50) # 当日全市场截面,按成交额排序
15
+ ```
16
+
17
+ ## Why this exists
18
+
19
+ Most free market-data endpoints are **APIs**: stateful, rate-limited, silently
20
+ revised, and impossible to reproduce. A backtest that ran last month can't be rerun
21
+ today with the same inputs.
22
+
23
+ OHLCVault publishes **immutable monthly shards with checksums** instead. Each read is
24
+ anchored to a snapshot id you can write down and reproduce later, on any machine.
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pip install ohlcvault # core, zero runtime dependencies
30
+ pip install "ohlcvault[pandas]" # + DataFrame helpers
31
+ ```
32
+
33
+ The core has **no third-party dependencies** — only the standard library. That's
34
+ deliberate: a data client shouldn't drag a dependency tree into your project, and it
35
+ matters even more for agent/tooling contexts.
36
+
37
+ ## Usage
38
+
39
+ ### Everything (the five things you actually need)
40
+
41
+ ```python
42
+ import ohlcvault as ov
43
+ ov.connect()
44
+
45
+ # 1. Trading calendar
46
+ ov.calendar("cn", start=20260101)
47
+
48
+ # 2. Stock daily bars (cross-month stitching is handled for you)
49
+ ov.daily("600519.SH", start=20260101, end=20260918)
50
+
51
+ # 3. Index daily bars — a separate namespace, never mixed with stocks
52
+ ov.index_daily("000300.SH")
53
+
54
+ # 4. Symbol list — includes delisted stocks
55
+ ov.symbols("cn", type="stock")
56
+ ov.symbols("cn", type="stock", status="delisted")
57
+
58
+ # 5. Daily cross-section — sorted by turnover, no extra data files
59
+ ov.cross_section("cn", 20260918, sort_by="amount", limit=50)
60
+ ```
61
+
62
+ ### Batch backtests
63
+
64
+ Month shards hold **every symbol in the market** for that month. Loading 200 symbols
65
+ one-by-one would decompress the same file 200 times:
66
+
67
+ ```python
68
+ bars = ov.daily_many(["600519.SH", "000001.SZ", "300750.SZ"], start=20260101)
69
+ bars["600519.SH"].to_pandas()
70
+ ```
71
+
72
+ ### Adjustment is a view, not a stored field
73
+
74
+ The dataset stores **unadjusted prices only**, plus the official cumulative
75
+ back-adjustment factor. Forward/backward adjusted prices are computed client-side:
76
+
77
+ ```python
78
+ b = ov.daily("600519.SH")
79
+ ov.adjust(b, to="hfq") # 后复权
80
+ ov.adjust(b, to="qfq") # 前复权
81
+ ```
82
+
83
+ This is not a limitation — it's the reason historical files never change. If
84
+ forward-adjusted prices were stored, every dividend would rewrite all of history, and
85
+ `immutable` caching would be impossible.
86
+
87
+ ### Reproducibility
88
+
89
+ ```python
90
+ st = ov.connect()
91
+ sid = st.snapshot # e.g. "6b197df3723871c5"
92
+ ov.connect(snapshot=sid) # later, anywhere: exact same inputs
93
+ ```
94
+
95
+ ### Offline / self-hosted mirrors
96
+
97
+ A mirror can be an HTTP(S) URL **or a local directory**:
98
+
99
+ ```python
100
+ ov.connect(mirrors=["/path/to/data"])
101
+ ```
102
+
103
+ Mirrors are tried in order; whichever one succeeds is promoted to first place. Every
104
+ file is checked against the `sha256` in the snapshot manifest, and anything that
105
+ fails is **discarded and the next mirror is tried** — bad bytes are never handed to
106
+ the caller.
107
+
108
+ ## Data integrity
109
+
110
+ | Guarantee | How |
111
+ |---|---|
112
+ | No silently-corrupted data | Every file verified against the snapshot's `sha256` |
113
+ | No silently-changed history | Sealed months are never rewritten |
114
+ | No unverifiable numbers | Missing adjustment factors raise, instead of returning raw prices |
115
+ | No hidden survivorship bias | Delisted stocks are kept in the universe and in the data |
116
+ | Byte-for-byte reproduction | Fixed-point integers, `gzip` with `MTIME=0`, no wall-clock timestamps |
117
+
118
+ **Delisted stocks matter.** If your backtest universe only contains companies that
119
+ are still listed today, your historical returns are systematically overstated.
120
+ `ov.symbols("cn", status="delisted")` returns them, and their daily bars are complete
121
+ over `ipo … out`.
122
+
123
+ ## Coverage and known gaps
124
+
125
+ Coverage is declared explicitly in `meta/symbols/{market}.json` under `coverage`, and
126
+ `ov.connect()` prints it on startup. Current state:
127
+
128
+ | Market | Status | Gaps |
129
+ |---|---|---|
130
+ | `cn` | Daily bars + indices, 2000→present | **No Beijing Stock Exchange** (upstream source doesn't provide it) |
131
+ | `hk` | Symbol list only | No daily bars yet; **list is current listings only** |
132
+ | `us` | Symbol list only | No daily bars yet; **list is current listings only** |
133
+
134
+ `hk` / `us` symbol lists carry **survivorship bias** (the upstream source only returns
135
+ currently-listed securities) and have no IPO dates. Their daily bars are pending.
136
+
137
+ An honest data project states its gaps. A dataset that quietly omits them is worse
138
+ than one that is merely incomplete.
139
+
140
+ ## API
141
+
142
+ | Function | Purpose |
143
+ |---|---|
144
+ | `connect(mirrors=, cache_dir=, snapshot=)` | Build the default client |
145
+ | `symbols(market, type=, status=, board=)` | Symbol list |
146
+ | `symbol(code)` | Single symbol entry |
147
+ | `calendar(market, start=, end=)` | Trading calendar |
148
+ | `daily(code, start=, end=)` | Stock daily bars, cross-month stitching |
149
+ | `index_daily(code, start=, end=)` | Index daily bars |
150
+ | `daily_many(codes, start=, end=)` | Batch read (preferred for backtests) |
151
+ | `cross_section(market, date, sort_by=, limit=)` | Daily cross-section |
152
+ | `adjust(bars_or_df, to="qfq"\|"hfq"\|"none")` | Adjustment view |
153
+ | `snapshot()` | Current snapshot id |
154
+
155
+ Date parameters (`start` / `end` / `date`) accept an `int` `YYYYMMDD` (preferred)
156
+ or common string forms — `"2026-09-18"`, `"20260918"`, `"2026/09/18"`. Anything
157
+ unparseable raises `DateError` instead of failing deep inside the library.
158
+
159
+ The frozen data contract lives in [`SPEC.md`](SPEC.md) — the client and the pipeline
160
+ share nothing but this document and the files it describes. `schema/example-*.json`
161
+ are machine-generated from real data, so the examples cannot drift from the contract.
162
+
163
+ ## License
164
+
165
+ MIT. See [LICENSE](LICENSE).
166
+
167
+ Data is gathered from public sources. Verify before relying on it for anything
168
+ consequential.