quantpad-data 0.4.2__tar.gz → 0.5.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.
- quantpad_data-0.5.0/.gitignore +34 -0
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/PKG-INFO +63 -3
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/README.md +61 -2
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/pyproject.toml +5 -2
- quantpad_data-0.5.0/quantpad_data/__init__.py +219 -0
- quantpad_data-0.5.0/quantpad_data/cache.py +440 -0
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/quantpad_data/client.py +11 -17
- quantpad_data-0.5.0/quantpad_data/rth.py +178 -0
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/quantpad_data/v2.py +208 -5
- quantpad_data-0.5.0/tests/conftest.py +26 -0
- quantpad_data-0.5.0/tests/test_packaging.py +184 -0
- quantpad_data-0.5.0/tests/test_rth.py +479 -0
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/tests/test_v2.py +23 -17
- quantpad_data-0.5.0/tests/test_v2_cache.py +675 -0
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/tests/test_v2_gate.py +85 -33
- quantpad_data-0.4.2/.gitignore +0 -21
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/quantpad_data/errors.py +0 -0
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/quantpad_data/lean.py +0 -0
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/quantpad_data/memory.py +0 -0
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/quantpad_data/py.typed +0 -0
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/tests/test_client.py +0 -0
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/tests/test_lean.py +0 -0
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/tests/test_message_encoding.py +0 -0
- {quantpad_data-0.4.2 → quantpad_data-0.5.0}/tests/test_version.py +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
.env
|
|
5
|
+
.env.local
|
|
6
|
+
.venv/
|
|
7
|
+
venv/
|
|
8
|
+
# Any suffixed venv too. .venv141 (fastapi 0.141, the version the image builds --
|
|
9
|
+
# see pinfastapi) was NOT matched by the pattern above and a `git add -A` swallowed
|
|
10
|
+
# all ~4,000 files of it into a commit.
|
|
11
|
+
.venv*/
|
|
12
|
+
*.egg-info/
|
|
13
|
+
.mypy_cache/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
|
|
17
|
+
# Throwaway investigation probes. Named so they sort together and are
|
|
18
|
+
# obviously not part of the service; there are usually a lot of them
|
|
19
|
+
# mid-investigation and none of them should ever be committed.
|
|
20
|
+
_qp_*
|
|
21
|
+
|
|
22
|
+
# Scratch: commit-message and one-off investigation files.
|
|
23
|
+
_*.txt
|
|
24
|
+
_*.md
|
|
25
|
+
_*.py
|
|
26
|
+
|
|
27
|
+
# A DUNDER FILE IS NOT A SCRATCH FILE. `_*.py` matches `__init__.py`, and while
|
|
28
|
+
# git keeps an already-tracked file regardless, HATCHLING APPLIES THESE PATTERNS
|
|
29
|
+
# WHEN SELECTING WHAT GOES IN THE WHEEL -- so from 2026-09-14 every published
|
|
30
|
+
# quantpad-data release shipped without its `__init__.py`, i.e. without Client,
|
|
31
|
+
# Historical, get_bars and every other documented name. `pip install
|
|
32
|
+
# quantpad-data` gave a namespace package that answered AttributeError to the
|
|
33
|
+
# README's first example. Verified against the artifact on PyPI, not inferred.
|
|
34
|
+
!__*.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: quantpad-data
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Official Python client for the QuantPad market-data API
|
|
5
5
|
Project-URL: Documentation, https://api.quantpad.ai/external/docs
|
|
6
6
|
Author: QuantPad
|
|
@@ -16,6 +16,7 @@ Provides-Extra: rth
|
|
|
16
16
|
Requires-Dist: exchange-calendars>=4.5; extra == 'rth'
|
|
17
17
|
Provides-Extra: test
|
|
18
18
|
Requires-Dist: build>=1.2; extra == 'test'
|
|
19
|
+
Requires-Dist: exchange-calendars>=4.5; extra == 'test'
|
|
19
20
|
Requires-Dist: pytest>=8; extra == 'test'
|
|
20
21
|
Description-Content-Type: text/markdown
|
|
21
22
|
|
|
@@ -75,8 +76,7 @@ client honors `Retry-After` and uses exponential backoff for transient failures.
|
|
|
75
76
|
QuantPad notebooks remain compatible through `QUANTPAD_NOTEBOOK_DATA_TOKEN`.
|
|
76
77
|
|
|
77
78
|
Bars preserve the notebook helper's OHLCV aliases and smart futures
|
|
78
|
-
back-adjustment default. Tick timestamps are epoch nanoseconds.
|
|
79
|
-
`quantpad-data[rth]` for XNYS regular-session helpers.
|
|
79
|
+
back-adjustment default. Tick timestamps are epoch nanoseconds.
|
|
80
80
|
|
|
81
81
|
`mbp-10` is true L2 depth with bid/ask price, size, and order-count fields at
|
|
82
82
|
levels 0-9. It is available for supported CME futures, individual CME
|
|
@@ -172,6 +172,66 @@ job = client.batch.submit(
|
|
|
172
172
|
client.batch.hydrate(job["job_id"])
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
+
### Regular trading hours
|
|
176
|
+
|
|
177
|
+
`get_range` returns the whole published session, which for US equities includes
|
|
178
|
+
pre- and post-market. That is what the upstream feed publishes, and it is more
|
|
179
|
+
data than QuantPad's older `/v1` tick endpoints returned: those enumerated
|
|
180
|
+
exchange sessions, and the XNYS calendar's open and close *are* regular hours,
|
|
181
|
+
so `/v1` excluded the extended session without saying so — around 7% of the
|
|
182
|
+
rows on a liquid name.
|
|
183
|
+
|
|
184
|
+
If you want that narrower scope, ask for it:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
df = client.timeseries.get_range_rth(
|
|
188
|
+
dataset="XNAS.ITCH", symbols="BA", schema="trades",
|
|
189
|
+
start="2026-09-16", end="2026-09-17",
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`iter_range_rth` is the streaming form. Both follow the exchange calendar, so
|
|
194
|
+
half-days close early and holidays are skipped, and both filter on `ts_event`
|
|
195
|
+
— the clock `/v1` presented as its `t` column — so the numbers are comparable
|
|
196
|
+
with a `/v1` result. Requires the calendar extra: `pip install
|
|
197
|
+
'quantpad-data[rth]'`.
|
|
198
|
+
|
|
199
|
+
The window sent to the service is still the whole range; the narrowing happens
|
|
200
|
+
here. So the size budget applies to the full session either way, and a window
|
|
201
|
+
too large unfiltered is too large for `get_range_rth` too — use
|
|
202
|
+
`iter_range_rth` for those.
|
|
203
|
+
|
|
204
|
+
### The disk cache
|
|
205
|
+
|
|
206
|
+
Settled `/v2` answers are cached on disk, so asking twice costs the network
|
|
207
|
+
once. Measured against production on a day of `BA` `mbp-10` (237,916 rows):
|
|
208
|
+
1.30s cold, 0.04s warm for `get_range`, and 1.01s to 0.14s for `iter_range`,
|
|
209
|
+
which caches each day's partition separately.
|
|
210
|
+
|
|
211
|
+
Only *final* answers are stored. A window reaching into the current day is
|
|
212
|
+
never cached, and neither is one the service clamped at the end, because both
|
|
213
|
+
legitimately return more data later. A cached answer carries the same
|
|
214
|
+
`.attrs['quantpad']` as the network one, so a hit and a miss are
|
|
215
|
+
indistinguishable to your code.
|
|
216
|
+
|
|
217
|
+
Defaults, all overridable:
|
|
218
|
+
|
|
219
|
+
| Variable | Default | Meaning |
|
|
220
|
+
| --- | --- | --- |
|
|
221
|
+
| `QPD_CACHE` | `1` | Set `0` to disable entirely |
|
|
222
|
+
| `QPD_CACHE_DIR` | platform user-cache dir | Where entries live |
|
|
223
|
+
| `QPD_CACHE_MAX_BYTES` | 1 GiB | Least-recently-used entries are evicted past this |
|
|
224
|
+
|
|
225
|
+
The cache is best-effort by design: a full disk, an unwritable directory or a
|
|
226
|
+
corrupt entry costs you the speedup and never the request.
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from quantpad_data import cache
|
|
230
|
+
|
|
231
|
+
cache.total_bytes() # how much is on disk
|
|
232
|
+
cache.clear() # throw it all away
|
|
233
|
+
```
|
|
234
|
+
|
|
175
235
|
### Other namespaces
|
|
176
236
|
|
|
177
237
|
`client.symbology.resolve(...)`, the `client.metadata.*` mirrors
|
|
@@ -54,8 +54,7 @@ client honors `Retry-After` and uses exponential backoff for transient failures.
|
|
|
54
54
|
QuantPad notebooks remain compatible through `QUANTPAD_NOTEBOOK_DATA_TOKEN`.
|
|
55
55
|
|
|
56
56
|
Bars preserve the notebook helper's OHLCV aliases and smart futures
|
|
57
|
-
back-adjustment default. Tick timestamps are epoch nanoseconds.
|
|
58
|
-
`quantpad-data[rth]` for XNYS regular-session helpers.
|
|
57
|
+
back-adjustment default. Tick timestamps are epoch nanoseconds.
|
|
59
58
|
|
|
60
59
|
`mbp-10` is true L2 depth with bid/ask price, size, and order-count fields at
|
|
61
60
|
levels 0-9. It is available for supported CME futures, individual CME
|
|
@@ -151,6 +150,66 @@ job = client.batch.submit(
|
|
|
151
150
|
client.batch.hydrate(job["job_id"])
|
|
152
151
|
```
|
|
153
152
|
|
|
153
|
+
### Regular trading hours
|
|
154
|
+
|
|
155
|
+
`get_range` returns the whole published session, which for US equities includes
|
|
156
|
+
pre- and post-market. That is what the upstream feed publishes, and it is more
|
|
157
|
+
data than QuantPad's older `/v1` tick endpoints returned: those enumerated
|
|
158
|
+
exchange sessions, and the XNYS calendar's open and close *are* regular hours,
|
|
159
|
+
so `/v1` excluded the extended session without saying so — around 7% of the
|
|
160
|
+
rows on a liquid name.
|
|
161
|
+
|
|
162
|
+
If you want that narrower scope, ask for it:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
df = client.timeseries.get_range_rth(
|
|
166
|
+
dataset="XNAS.ITCH", symbols="BA", schema="trades",
|
|
167
|
+
start="2026-09-16", end="2026-09-17",
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`iter_range_rth` is the streaming form. Both follow the exchange calendar, so
|
|
172
|
+
half-days close early and holidays are skipped, and both filter on `ts_event`
|
|
173
|
+
— the clock `/v1` presented as its `t` column — so the numbers are comparable
|
|
174
|
+
with a `/v1` result. Requires the calendar extra: `pip install
|
|
175
|
+
'quantpad-data[rth]'`.
|
|
176
|
+
|
|
177
|
+
The window sent to the service is still the whole range; the narrowing happens
|
|
178
|
+
here. So the size budget applies to the full session either way, and a window
|
|
179
|
+
too large unfiltered is too large for `get_range_rth` too — use
|
|
180
|
+
`iter_range_rth` for those.
|
|
181
|
+
|
|
182
|
+
### The disk cache
|
|
183
|
+
|
|
184
|
+
Settled `/v2` answers are cached on disk, so asking twice costs the network
|
|
185
|
+
once. Measured against production on a day of `BA` `mbp-10` (237,916 rows):
|
|
186
|
+
1.30s cold, 0.04s warm for `get_range`, and 1.01s to 0.14s for `iter_range`,
|
|
187
|
+
which caches each day's partition separately.
|
|
188
|
+
|
|
189
|
+
Only *final* answers are stored. A window reaching into the current day is
|
|
190
|
+
never cached, and neither is one the service clamped at the end, because both
|
|
191
|
+
legitimately return more data later. A cached answer carries the same
|
|
192
|
+
`.attrs['quantpad']` as the network one, so a hit and a miss are
|
|
193
|
+
indistinguishable to your code.
|
|
194
|
+
|
|
195
|
+
Defaults, all overridable:
|
|
196
|
+
|
|
197
|
+
| Variable | Default | Meaning |
|
|
198
|
+
| --- | --- | --- |
|
|
199
|
+
| `QPD_CACHE` | `1` | Set `0` to disable entirely |
|
|
200
|
+
| `QPD_CACHE_DIR` | platform user-cache dir | Where entries live |
|
|
201
|
+
| `QPD_CACHE_MAX_BYTES` | 1 GiB | Least-recently-used entries are evicted past this |
|
|
202
|
+
|
|
203
|
+
The cache is best-effort by design: a full disk, an unwritable directory or a
|
|
204
|
+
corrupt entry costs you the speedup and never the request.
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from quantpad_data import cache
|
|
208
|
+
|
|
209
|
+
cache.total_bytes() # how much is on disk
|
|
210
|
+
cache.clear() # throw it all away
|
|
211
|
+
```
|
|
212
|
+
|
|
154
213
|
### Other namespaces
|
|
155
214
|
|
|
156
215
|
`client.symbology.resolve(...)`, the `client.metadata.*` mirrors
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quantpad-data"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "Official Python client for the QuantPad market-data API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -23,7 +23,10 @@ classifiers = [
|
|
|
23
23
|
|
|
24
24
|
[project.optional-dependencies]
|
|
25
25
|
rth = ["exchange-calendars>=4.5"]
|
|
26
|
-
test
|
|
26
|
+
# The calendar is in `test` as well as `rth` on purpose: tests/test_rth.py
|
|
27
|
+
# importorskips it, so without it here CI would report green having run none of
|
|
28
|
+
# the regular-hours suite.
|
|
29
|
+
test = ["pytest>=8", "build>=1.2", "exchange-calendars>=4.5"]
|
|
27
30
|
|
|
28
31
|
[project.urls]
|
|
29
32
|
Documentation = "https://api.quantpad.ai/external/docs"
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""QuantPad market-data SDK and notebook-compatible function API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Iterator, Optional
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import pyarrow as pa
|
|
10
|
+
|
|
11
|
+
from .client import Client, ROLL_ADJUST_MODES, TICK_SCHEMAS, USER_AGENT, VERSION
|
|
12
|
+
from .errors import (
|
|
13
|
+
AuthenticationError,
|
|
14
|
+
NotFoundError,
|
|
15
|
+
PermissionDeniedError,
|
|
16
|
+
QuantPadDataError,
|
|
17
|
+
QuantPadDataNotReady,
|
|
18
|
+
QuotaExceededError,
|
|
19
|
+
RateLimitError,
|
|
20
|
+
ResponseTooLargeError,
|
|
21
|
+
UpstreamError,
|
|
22
|
+
V2NotAvailableError,
|
|
23
|
+
ValidationError,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__version__ = VERSION
|
|
27
|
+
QuantPadClient = Client
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _client(**kwargs) -> Client:
|
|
31
|
+
return Client(**kwargs)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_bars(symbol, timeframe, start_ms, end_ms, *, max_rows=None, as_df=True,
|
|
35
|
+
roll_adjust=None):
|
|
36
|
+
return _client().get_bars(
|
|
37
|
+
symbol, timeframe, start_ms, end_ms, max_rows=max_rows,
|
|
38
|
+
as_df=as_df, roll_adjust=roll_adjust,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_bars_with_retry(symbol, timeframe, start_ms, end_ms, *, max_rows=None,
|
|
43
|
+
retries=3, sleep_seconds=10, roll_adjust=None):
|
|
44
|
+
# Client retries use Retry-After and exponential jitter. ``sleep_seconds``
|
|
45
|
+
# remains accepted so existing notebooks do not break.
|
|
46
|
+
return Client(max_retries=max(0, int(retries) - 1)).get_bars(
|
|
47
|
+
symbol, timeframe, start_ms, end_ms, max_rows=max_rows,
|
|
48
|
+
roll_adjust=roll_adjust,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_ticks(symbol, schema, start_ms, end_ms, *, columns=None, chunksize=250_000):
|
|
53
|
+
return _client().get_ticks(
|
|
54
|
+
symbol, schema, start_ms, end_ms, columns=columns, chunksize=chunksize
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_trades(symbol, start_ms, end_ms, *, columns=None, chunk_rows=250_000):
|
|
59
|
+
return get_ticks(
|
|
60
|
+
symbol, "trades", start_ms, end_ms, columns=columns, chunksize=chunk_rows
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_mbp1(symbol, start_ms, end_ms, *, columns=None, chunk_rows=250_000):
|
|
65
|
+
return get_ticks(
|
|
66
|
+
symbol, "mbp-1", start_ms, end_ms, columns=columns, chunksize=chunk_rows
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_mbp10(symbol, start_ms, end_ms, *, columns=None, chunk_rows=250_000):
|
|
71
|
+
return get_ticks(
|
|
72
|
+
symbol, "mbp-10", start_ms, end_ms, columns=columns, chunksize=chunk_rows
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def symbology_resolve(dataset=None, symbols=None, stype_in=None, stype_out=None,
|
|
77
|
+
start_date=None, end_date=None):
|
|
78
|
+
return _client().symbology_resolve(
|
|
79
|
+
dataset, symbols, stype_in, stype_out, start_date, end_date
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_universe(query: str, *, limit: int = 20, asset_class: str | None = None):
|
|
84
|
+
return _client().get_universe(query, limit=limit, asset_class=asset_class)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_coverage(symbol: str):
|
|
88
|
+
return _client().get_coverage(symbol)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _iter_rth_windows_ms(start_ms: int, end_ms: int):
|
|
92
|
+
try:
|
|
93
|
+
import exchange_calendars as ec
|
|
94
|
+
except ImportError as exc:
|
|
95
|
+
raise RuntimeError(
|
|
96
|
+
"RTH helpers require: pip install 'quantpad-data[rth]'"
|
|
97
|
+
) from exc
|
|
98
|
+
cal = ec.get_calendar("XNYS")
|
|
99
|
+
start_day = pd.Timestamp(start_ms, unit="ms", tz="UTC").tz_convert(
|
|
100
|
+
"America/New_York"
|
|
101
|
+
).date()
|
|
102
|
+
end_day = pd.Timestamp(end_ms - 1, unit="ms", tz="UTC").tz_convert(
|
|
103
|
+
"America/New_York"
|
|
104
|
+
).date()
|
|
105
|
+
for session in cal.sessions_in_range(pd.Timestamp(start_day), pd.Timestamp(end_day)):
|
|
106
|
+
open_ms = int(cal.session_open(session).timestamp() * 1000)
|
|
107
|
+
close_ms = int(cal.session_close(session).timestamp() * 1000)
|
|
108
|
+
left, right = max(open_ms, int(start_ms)), min(close_ms, int(end_ms))
|
|
109
|
+
if left < right:
|
|
110
|
+
yield left, right
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _stream_ticks_rth(symbol, schema, start_ms, end_ms, *, columns, chunk_rows):
|
|
114
|
+
for open_ms, close_ms in _iter_rth_windows_ms(start_ms, end_ms):
|
|
115
|
+
for frame in get_ticks(
|
|
116
|
+
symbol, schema, open_ms, close_ms,
|
|
117
|
+
columns=columns, chunksize=chunk_rows,
|
|
118
|
+
):
|
|
119
|
+
if "t" in frame:
|
|
120
|
+
frame = frame.loc[
|
|
121
|
+
(frame["t"] >= open_ms * 1_000_000)
|
|
122
|
+
& (frame["t"] < close_ms * 1_000_000)
|
|
123
|
+
]
|
|
124
|
+
if not frame.empty:
|
|
125
|
+
yield frame
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_trades_rth(symbol, start_ms, end_ms, *, columns=None, chunk_rows=250_000):
|
|
129
|
+
return _stream_ticks_rth(
|
|
130
|
+
symbol, "trades", start_ms, end_ms, columns=columns, chunk_rows=chunk_rows
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_mbp1_rth(symbol, start_ms, end_ms, *, columns=None, chunk_rows=250_000):
|
|
135
|
+
return _stream_ticks_rth(
|
|
136
|
+
symbol, "mbp-1", start_ms, end_ms, columns=columns, chunk_rows=chunk_rows
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_mbp10_rth(symbol, start_ms, end_ms, *, columns=None, chunk_rows=250_000):
|
|
141
|
+
return _stream_ticks_rth(
|
|
142
|
+
symbol, "mbp-10", start_ms, end_ms, columns=columns, chunk_rows=chunk_rows
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_spread_1m_rth(symbol, start_ms, end_ms, *, chunk_rows=250_000):
|
|
147
|
+
pieces = []
|
|
148
|
+
for frame in get_mbp1_rth(
|
|
149
|
+
symbol, start_ms, end_ms,
|
|
150
|
+
columns=["t", "bid_px", "ask_px"], chunk_rows=chunk_rows,
|
|
151
|
+
):
|
|
152
|
+
spread = frame["ask_px"] - frame["bid_px"]
|
|
153
|
+
valid = spread.notna() & (spread >= 0)
|
|
154
|
+
if valid.any():
|
|
155
|
+
index = pd.to_datetime(frame.loc[valid, "t"], unit="ns", utc=True)
|
|
156
|
+
pieces.append(pd.Series(spread.loc[valid].to_numpy(), index=index))
|
|
157
|
+
if not pieces:
|
|
158
|
+
return pd.DataFrame(
|
|
159
|
+
{"spread": pd.Series(dtype="float64")},
|
|
160
|
+
index=pd.DatetimeIndex([], name="t", tz="UTC"),
|
|
161
|
+
)
|
|
162
|
+
result = pd.concat(pieces).resample("1min").mean().to_frame("spread")
|
|
163
|
+
result.index.name = "t"
|
|
164
|
+
return result
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def iter_trades(symbol, start_ms, end_ms, *, columns=None, chunksize=250_000):
|
|
168
|
+
return get_trades(
|
|
169
|
+
symbol, start_ms, end_ms, columns=columns, chunk_rows=chunksize
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def iter_mbp1(symbol, start_ms, end_ms, *, columns=None, chunksize=250_000):
|
|
174
|
+
return get_mbp1(symbol, start_ms, end_ms, columns=columns, chunk_rows=chunksize)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def iter_l1_df_chunks(symbol, schema="trades", start_ms=0, end_ms=0,
|
|
178
|
+
chunksize=250_000, usecols: Optional[list[str]] = None,
|
|
179
|
+
time_col="ts_event", set_datetime_index=True, channel=None,
|
|
180
|
+
chunk_size=None):
|
|
181
|
+
return get_ticks(
|
|
182
|
+
symbol, channel or schema, start_ms, end_ms, columns=usecols,
|
|
183
|
+
chunksize=chunk_size or chunksize,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def iter_l1_trades_df_chunks(symbol, start_ms, end_ms, chunksize=250_000,
|
|
188
|
+
usecols=None):
|
|
189
|
+
return iter_trades(
|
|
190
|
+
symbol, start_ms, end_ms, columns=usecols, chunksize=chunksize
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def iter_l1_mbp1_df_chunks(symbol, start_ms, end_ms, chunksize=250_000,
|
|
195
|
+
usecols=None):
|
|
196
|
+
return iter_mbp1(symbol, start_ms, end_ms, columns=usecols, chunksize=chunksize)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
from . import cache # noqa: E402 (submodule: on-disk cache for settled /v2)
|
|
200
|
+
from . import lean # noqa: E402 (submodule: LEAN backtesting data staging)
|
|
201
|
+
from . import v2 # noqa: E402 (submodule: Databento-shaped explicit API)
|
|
202
|
+
|
|
203
|
+
# Exported at the root as well as on ``v2``: the sandbox module reaches it
|
|
204
|
+
# as ``qpd.Historical()``, and one spelling across both surfaces means
|
|
205
|
+
# notebook code moves between them unchanged.
|
|
206
|
+
Historical = v2.Historical
|
|
207
|
+
|
|
208
|
+
__all__ = [
|
|
209
|
+
"Client", "QuantPadClient", "QuantPadDataError", "QuantPadDataNotReady", "AuthenticationError",
|
|
210
|
+
"PermissionDeniedError", "RateLimitError", "QuotaExceededError",
|
|
211
|
+
"NotFoundError", "V2NotAvailableError", "ValidationError", "UpstreamError",
|
|
212
|
+
"ResponseTooLargeError", "TICK_SCHEMAS",
|
|
213
|
+
"ROLL_ADJUST_MODES", "get_bars", "get_bars_with_retry", "get_ticks",
|
|
214
|
+
"get_trades", "get_mbp1", "get_mbp10", "get_trades_rth", "get_mbp1_rth",
|
|
215
|
+
"get_mbp10_rth", "get_spread_1m_rth", "symbology_resolve", "get_universe",
|
|
216
|
+
"get_coverage", "iter_trades", "iter_mbp1", "iter_l1_df_chunks",
|
|
217
|
+
"iter_l1_trades_df_chunks", "iter_l1_mbp1_df_chunks", "cache", "lean",
|
|
218
|
+
"v2", "Historical", "np", "pd", "pa",
|
|
219
|
+
]
|