quantpad-data 0.1.0__py3-none-any.whl

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,206 @@
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
+ UpstreamError,
21
+ ValidationError,
22
+ )
23
+
24
+ __version__ = VERSION
25
+ QuantPadClient = Client
26
+
27
+
28
+ def _client(**kwargs) -> Client:
29
+ return Client(**kwargs)
30
+
31
+
32
+ def get_bars(symbol, timeframe, start_ms, end_ms, *, max_rows=None, as_df=True,
33
+ roll_adjust=None):
34
+ return _client().get_bars(
35
+ symbol, timeframe, start_ms, end_ms, max_rows=max_rows,
36
+ as_df=as_df, roll_adjust=roll_adjust,
37
+ )
38
+
39
+
40
+ def get_bars_with_retry(symbol, timeframe, start_ms, end_ms, *, max_rows=None,
41
+ retries=3, sleep_seconds=10, roll_adjust=None):
42
+ # Client retries use Retry-After and exponential jitter. ``sleep_seconds``
43
+ # remains accepted so existing notebooks do not break.
44
+ return Client(max_retries=max(0, int(retries) - 1)).get_bars(
45
+ symbol, timeframe, start_ms, end_ms, max_rows=max_rows,
46
+ roll_adjust=roll_adjust,
47
+ )
48
+
49
+
50
+ def get_ticks(symbol, schema, start_ms, end_ms, *, columns=None, chunksize=250_000):
51
+ return _client().get_ticks(
52
+ symbol, schema, start_ms, end_ms, columns=columns, chunksize=chunksize
53
+ )
54
+
55
+
56
+ def get_trades(symbol, start_ms, end_ms, *, columns=None, chunk_rows=250_000):
57
+ return get_ticks(
58
+ symbol, "trades", start_ms, end_ms, columns=columns, chunksize=chunk_rows
59
+ )
60
+
61
+
62
+ def get_mbp1(symbol, start_ms, end_ms, *, columns=None, chunk_rows=250_000):
63
+ return get_ticks(
64
+ symbol, "mbp-1", start_ms, end_ms, columns=columns, chunksize=chunk_rows
65
+ )
66
+
67
+
68
+ def get_mbp10(symbol, start_ms, end_ms, *, columns=None, chunk_rows=250_000):
69
+ return get_ticks(
70
+ symbol, "mbp-10", start_ms, end_ms, columns=columns, chunksize=chunk_rows
71
+ )
72
+
73
+
74
+ def symbology_resolve(dataset=None, symbols=None, stype_in=None, stype_out=None,
75
+ start_date=None, end_date=None):
76
+ return _client().symbology_resolve(
77
+ dataset, symbols, stype_in, stype_out, start_date, end_date
78
+ )
79
+
80
+
81
+ def get_universe(query: str, *, limit: int = 20, asset_class: str | None = None):
82
+ return _client().get_universe(query, limit=limit, asset_class=asset_class)
83
+
84
+
85
+ def get_coverage(symbol: str):
86
+ return _client().get_coverage(symbol)
87
+
88
+
89
+ def _iter_rth_windows_ms(start_ms: int, end_ms: int):
90
+ try:
91
+ import exchange_calendars as ec
92
+ except ImportError as exc:
93
+ raise RuntimeError(
94
+ "RTH helpers require: pip install 'quantpad-data[rth]'"
95
+ ) from exc
96
+ cal = ec.get_calendar("XNYS")
97
+ start_day = pd.Timestamp(start_ms, unit="ms", tz="UTC").tz_convert(
98
+ "America/New_York"
99
+ ).date()
100
+ end_day = pd.Timestamp(end_ms - 1, unit="ms", tz="UTC").tz_convert(
101
+ "America/New_York"
102
+ ).date()
103
+ for session in cal.sessions_in_range(pd.Timestamp(start_day), pd.Timestamp(end_day)):
104
+ open_ms = int(cal.session_open(session).timestamp() * 1000)
105
+ close_ms = int(cal.session_close(session).timestamp() * 1000)
106
+ left, right = max(open_ms, int(start_ms)), min(close_ms, int(end_ms))
107
+ if left < right:
108
+ yield left, right
109
+
110
+
111
+ def _stream_ticks_rth(symbol, schema, start_ms, end_ms, *, columns, chunk_rows):
112
+ for open_ms, close_ms in _iter_rth_windows_ms(start_ms, end_ms):
113
+ for frame in get_ticks(
114
+ symbol, schema, open_ms, close_ms,
115
+ columns=columns, chunksize=chunk_rows,
116
+ ):
117
+ if "t" in frame:
118
+ frame = frame.loc[
119
+ (frame["t"] >= open_ms * 1_000_000)
120
+ & (frame["t"] < close_ms * 1_000_000)
121
+ ]
122
+ if not frame.empty:
123
+ yield frame
124
+
125
+
126
+ def get_trades_rth(symbol, start_ms, end_ms, *, columns=None, chunk_rows=250_000):
127
+ return _stream_ticks_rth(
128
+ symbol, "trades", start_ms, end_ms, columns=columns, chunk_rows=chunk_rows
129
+ )
130
+
131
+
132
+ def get_mbp1_rth(symbol, start_ms, end_ms, *, columns=None, chunk_rows=250_000):
133
+ return _stream_ticks_rth(
134
+ symbol, "mbp-1", start_ms, end_ms, columns=columns, chunk_rows=chunk_rows
135
+ )
136
+
137
+
138
+ def get_mbp10_rth(symbol, start_ms, end_ms, *, columns=None, chunk_rows=250_000):
139
+ return _stream_ticks_rth(
140
+ symbol, "mbp-10", start_ms, end_ms, columns=columns, chunk_rows=chunk_rows
141
+ )
142
+
143
+
144
+ def get_spread_1m_rth(symbol, start_ms, end_ms, *, chunk_rows=250_000):
145
+ pieces = []
146
+ for frame in get_mbp1_rth(
147
+ symbol, start_ms, end_ms,
148
+ columns=["t", "bid_px", "ask_px"], chunk_rows=chunk_rows,
149
+ ):
150
+ spread = frame["ask_px"] - frame["bid_px"]
151
+ valid = spread.notna() & (spread >= 0)
152
+ if valid.any():
153
+ index = pd.to_datetime(frame.loc[valid, "t"], unit="ns", utc=True)
154
+ pieces.append(pd.Series(spread.loc[valid].to_numpy(), index=index))
155
+ if not pieces:
156
+ return pd.DataFrame(
157
+ {"spread": pd.Series(dtype="float64")},
158
+ index=pd.DatetimeIndex([], name="t", tz="UTC"),
159
+ )
160
+ result = pd.concat(pieces).resample("1min").mean().to_frame("spread")
161
+ result.index.name = "t"
162
+ return result
163
+
164
+
165
+ def iter_trades(symbol, start_ms, end_ms, *, columns=None, chunksize=250_000):
166
+ return get_trades(
167
+ symbol, start_ms, end_ms, columns=columns, chunk_rows=chunksize
168
+ )
169
+
170
+
171
+ def iter_mbp1(symbol, start_ms, end_ms, *, columns=None, chunksize=250_000):
172
+ return get_mbp1(symbol, start_ms, end_ms, columns=columns, chunk_rows=chunksize)
173
+
174
+
175
+ def iter_l1_df_chunks(symbol, schema="trades", start_ms=0, end_ms=0,
176
+ chunksize=250_000, usecols: Optional[list[str]] = None,
177
+ time_col="ts_event", set_datetime_index=True, channel=None,
178
+ chunk_size=None):
179
+ return get_ticks(
180
+ symbol, channel or schema, start_ms, end_ms, columns=usecols,
181
+ chunksize=chunk_size or chunksize,
182
+ )
183
+
184
+
185
+ def iter_l1_trades_df_chunks(symbol, start_ms, end_ms, chunksize=250_000,
186
+ usecols=None):
187
+ return iter_trades(
188
+ symbol, start_ms, end_ms, columns=usecols, chunksize=chunksize
189
+ )
190
+
191
+
192
+ def iter_l1_mbp1_df_chunks(symbol, start_ms, end_ms, chunksize=250_000,
193
+ usecols=None):
194
+ return iter_mbp1(symbol, start_ms, end_ms, columns=usecols, chunksize=chunksize)
195
+
196
+
197
+ __all__ = [
198
+ "Client", "QuantPadClient", "QuantPadDataError", "QuantPadDataNotReady", "AuthenticationError",
199
+ "PermissionDeniedError", "RateLimitError", "QuotaExceededError",
200
+ "NotFoundError", "ValidationError", "UpstreamError", "TICK_SCHEMAS",
201
+ "ROLL_ADJUST_MODES", "get_bars", "get_bars_with_retry", "get_ticks",
202
+ "get_trades", "get_mbp1", "get_mbp10", "get_trades_rth", "get_mbp1_rth",
203
+ "get_mbp10_rth", "get_spread_1m_rth", "symbology_resolve", "get_universe",
204
+ "get_coverage", "iter_trades", "iter_mbp1", "iter_l1_df_chunks",
205
+ "iter_l1_trades_df_chunks", "iter_l1_mbp1_df_chunks", "np", "pd", "pa",
206
+ ]
@@ -0,0 +1,273 @@
1
+ """Synchronous QuantPad Data API client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import os
7
+ import random
8
+ import time
9
+ from datetime import date
10
+ from typing import Iterator, Sequence
11
+
12
+ import pandas as pd
13
+ import pyarrow.ipc as ipc
14
+ import requests
15
+
16
+ from .errors import (
17
+ AuthenticationError,
18
+ NotFoundError,
19
+ PermissionDeniedError,
20
+ QuantPadDataError,
21
+ QuotaExceededError,
22
+ RateLimitError,
23
+ UpstreamError,
24
+ ValidationError,
25
+ )
26
+
27
+ VERSION = "0.1.0"
28
+ USER_AGENT = f"quantpad-data-python/{VERSION}"
29
+ TICK_SCHEMAS = (
30
+ "trades", "mbp-1", "mbp-10", "cmbp-1", "tcbbo", "cbbo-1s",
31
+ "cbbo-1m", "definition", "statistics", "status",
32
+ )
33
+ ROLL_ADJUST_MODES = ("none", "back", "ratio")
34
+
35
+
36
+ class Client:
37
+ def __init__(
38
+ self,
39
+ api_key: str | None = None,
40
+ *,
41
+ token: str | None = None,
42
+ base_url: str | None = None,
43
+ timeout: float = 120,
44
+ max_retries: int = 3,
45
+ session: requests.Session | None = None,
46
+ ) -> None:
47
+ self.api_key = (api_key or os.getenv("QUANTPAD_API_KEY") or "").strip() or None
48
+ self.token = (
49
+ token or os.getenv("QUANTPAD_NOTEBOOK_DATA_TOKEN") or ""
50
+ ).strip() or None
51
+ self.base_url = (
52
+ base_url
53
+ or os.getenv("QUANTPAD_API_BASE")
54
+ or os.getenv("QUANTPAD_NOTEBOOK_DATA_API_BASE")
55
+ or "https://api.quantpad.ai"
56
+ ).rstrip("/")
57
+ self.timeout = float(timeout)
58
+ self.max_retries = max(0, int(max_retries))
59
+ self.session = session or requests.Session()
60
+ self.session.headers.update({"User-Agent": USER_AGENT})
61
+
62
+ def _headers(self) -> dict[str, str]:
63
+ if self.api_key:
64
+ return {"X-API-Key": self.api_key}
65
+ if self.token:
66
+ return {"Authorization": f"Bearer {self.token}"}
67
+ raise AuthenticationError(
68
+ "Set QUANTPAD_API_KEY, or run inside QuantPad with "
69
+ "QUANTPAD_NOTEBOOK_DATA_TOKEN."
70
+ )
71
+
72
+ @staticmethod
73
+ def _message(response: requests.Response) -> str:
74
+ try:
75
+ body = response.json()
76
+ if isinstance(body, dict):
77
+ return str(body.get("error") or body.get("detail") or body)
78
+ except ValueError:
79
+ pass
80
+ return (response.text or f"HTTP {response.status_code}").strip()
81
+
82
+ def _raise_for_status(self, response: requests.Response) -> None:
83
+ if response.ok:
84
+ return
85
+ message = self._message(response)
86
+ retry_after: float | None = None
87
+ try:
88
+ retry_after = float(response.headers["Retry-After"])
89
+ except (KeyError, TypeError, ValueError):
90
+ pass
91
+ if response.status_code == 400:
92
+ raise ValidationError(message)
93
+ if response.status_code == 401:
94
+ raise AuthenticationError(message)
95
+ if response.status_code == 403:
96
+ raise PermissionDeniedError(message)
97
+ if response.status_code == 404:
98
+ raise NotFoundError(message)
99
+ if response.status_code == 429:
100
+ cls = QuotaExceededError if "quota" in message.lower() else RateLimitError
101
+ raise cls(message, retry_after)
102
+ if response.status_code >= 500:
103
+ raise UpstreamError(message)
104
+ raise QuantPadDataError(message)
105
+
106
+ def _request(self, method: str, path: str, **kwargs) -> requests.Response:
107
+ kwargs.setdefault("timeout", self.timeout)
108
+ headers = {**self._headers(), **kwargs.pop("headers", {})}
109
+ last_error: Exception | None = None
110
+ for attempt in range(self.max_retries + 1):
111
+ response: requests.Response | None = None
112
+ try:
113
+ response = self.session.request(
114
+ method, f"{self.base_url}{path}", headers=headers, **kwargs
115
+ )
116
+ self._raise_for_status(response)
117
+ return response
118
+ except RateLimitError as exc:
119
+ if response is not None:
120
+ response.close()
121
+ last_error = exc
122
+ if attempt >= self.max_retries:
123
+ raise
124
+ delay = exc.retry_after
125
+ except (requests.ConnectionError, requests.Timeout, UpstreamError) as exc:
126
+ if response is not None:
127
+ response.close()
128
+ last_error = exc
129
+ if attempt >= self.max_retries:
130
+ raise
131
+ delay = None
132
+ base_delay = delay if delay is not None else min(8.0, 0.5 * (2**attempt))
133
+ time.sleep(max(0.0, base_delay) + random.uniform(0, 0.25))
134
+ raise QuantPadDataError(str(last_error or "Request failed"))
135
+
136
+ @staticmethod
137
+ def _roll_adjust(symbol: str, value: str | None) -> str:
138
+ if value is None:
139
+ return "back" if symbol.strip().upper().endswith(".FUT") else "none"
140
+ mode = str(value).strip().lower()
141
+ if mode not in ROLL_ADJUST_MODES:
142
+ raise ValidationError(f"roll_adjust must be one of {ROLL_ADJUST_MODES}")
143
+ return mode
144
+
145
+ def get_bars(
146
+ self,
147
+ symbol: str,
148
+ timeframe: str,
149
+ start_ms: int,
150
+ end_ms: int,
151
+ *,
152
+ max_rows: int | None = None,
153
+ as_df: bool = True,
154
+ roll_adjust: str | None = None,
155
+ ) -> pd.DataFrame | list[dict]:
156
+ if int(end_ms) <= int(start_ms):
157
+ raise ValidationError("end_ms must be > start_ms")
158
+ params = {
159
+ "symbol": symbol.strip().upper(),
160
+ "timeframe": timeframe.strip().lower(),
161
+ "start": int(start_ms),
162
+ "end": int(end_ms),
163
+ "format": "arrow",
164
+ "roll_adjust": self._roll_adjust(symbol, roll_adjust),
165
+ }
166
+ response = self._request("GET", "/v1/bars", params=params, stream=True)
167
+ try:
168
+ table = ipc.open_stream(response.content).read_all()
169
+ frame = table.to_pandas()
170
+ except Exception:
171
+ response.close()
172
+ params["format"] = "csv"
173
+ fallback = self._request("GET", "/v1/bars", params=params, stream=True)
174
+ try:
175
+ frame = pd.read_csv(io.StringIO(fallback.text))
176
+ finally:
177
+ fallback.close()
178
+ finally:
179
+ response.close()
180
+ if "t" in frame.columns:
181
+ frame.index = pd.to_datetime(frame.pop("t"), unit="ms", utc=True)
182
+ frame.index.name = "t"
183
+ for short, long_name in (
184
+ ("o", "open"), ("h", "high"), ("l", "low"),
185
+ ("c", "close"), ("v", "volume"),
186
+ ):
187
+ if short in frame.columns and long_name not in frame.columns:
188
+ frame[long_name] = frame[short]
189
+ if max_rows is not None:
190
+ frame = frame.head(int(max_rows))
191
+ return frame if as_df else frame.reset_index().to_dict("records")
192
+
193
+ def get_ticks(
194
+ self,
195
+ symbol: str,
196
+ schema: str,
197
+ start_ms: int,
198
+ end_ms: int,
199
+ *,
200
+ columns: Sequence[str] | None = None,
201
+ chunksize: int = 250_000,
202
+ ) -> Iterator[pd.DataFrame]:
203
+ schema = schema.strip().lower()
204
+ if schema not in TICK_SCHEMAS:
205
+ raise ValidationError(f"schema must be one of {TICK_SCHEMAS}")
206
+ if chunksize <= 0 or int(end_ms) <= int(start_ms):
207
+ raise ValidationError("chunksize must be positive and end_ms > start_ms")
208
+ params: dict[str, str | int] = {
209
+ "symbol": symbol.strip().upper(),
210
+ "schema": schema,
211
+ "start": int(start_ms),
212
+ "end": int(end_ms),
213
+ }
214
+ if columns:
215
+ params["columns"] = ",".join(str(value).strip() for value in columns)
216
+ response = self._request(
217
+ "GET", "/v1/ticks", params=params, timeout=(30, 1800), stream=True
218
+ )
219
+ response.raw.decode_content = True
220
+ buffered: list[pd.DataFrame] = []
221
+ buffered_rows = 0
222
+ try:
223
+ for batch in ipc.open_stream(response.raw):
224
+ if not batch.num_rows:
225
+ continue
226
+ buffered.append(batch.to_pandas(split_blocks=True, self_destruct=True))
227
+ buffered_rows += batch.num_rows
228
+ while buffered_rows >= chunksize:
229
+ combined = pd.concat(buffered, ignore_index=True)
230
+ yield combined.iloc[:chunksize].copy()
231
+ remainder = combined.iloc[chunksize:]
232
+ buffered = [remainder] if len(remainder) else []
233
+ buffered_rows = len(remainder)
234
+ if buffered_rows:
235
+ yield pd.concat(buffered, ignore_index=True)
236
+ finally:
237
+ response.close()
238
+
239
+ def symbology_resolve(
240
+ self,
241
+ dataset=None,
242
+ symbols=None,
243
+ stype_in=None,
244
+ stype_out=None,
245
+ start_date=None,
246
+ end_date=None,
247
+ ) -> dict:
248
+ if symbols is None or stype_out is None or start_date is None:
249
+ raise ValidationError("symbols, stype_out, and start_date are required")
250
+ if isinstance(start_date, date):
251
+ start_date = start_date.isoformat()
252
+ if isinstance(end_date, date):
253
+ end_date = end_date.isoformat()
254
+ payload = {
255
+ "dataset": str(dataset) if dataset is not None else None,
256
+ "symbols": symbols if isinstance(symbols, (str, int)) else list(symbols),
257
+ "stype_in": str(stype_in) if stype_in is not None else None,
258
+ "stype_out": str(stype_out),
259
+ "start_date": start_date,
260
+ "end_date": end_date,
261
+ }
262
+ return self._request("POST", "/v1/symbology/resolve", json=payload).json()
263
+
264
+ def get_universe(
265
+ self, query: str, *, limit: int = 20, asset_class: str | None = None
266
+ ) -> dict:
267
+ params = {"q": query, "limit": int(limit)}
268
+ if asset_class:
269
+ params["asset_class"] = asset_class
270
+ return self._request("GET", "/v1/universe", params=params).json()
271
+
272
+ def get_coverage(self, symbol: str) -> dict:
273
+ return self._request("GET", "/v1/coverage", params={"symbol": symbol}).json()
@@ -0,0 +1,41 @@
1
+ """Typed QuantPad Data API exceptions."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class QuantPadDataError(RuntimeError):
7
+ """Base error for API and transport failures."""
8
+
9
+
10
+ class AuthenticationError(QuantPadDataError):
11
+ pass
12
+
13
+
14
+ class PermissionDeniedError(QuantPadDataError):
15
+ pass
16
+
17
+
18
+ class RateLimitError(QuantPadDataError):
19
+ def __init__(self, message: str, retry_after: float | None = None):
20
+ super().__init__(message)
21
+ self.retry_after = retry_after
22
+
23
+
24
+ class QuotaExceededError(RateLimitError):
25
+ pass
26
+
27
+
28
+ class NotFoundError(ValueError, QuantPadDataError):
29
+ pass
30
+
31
+
32
+ class ValidationError(ValueError, QuantPadDataError):
33
+ pass
34
+
35
+
36
+ class UpstreamError(QuantPadDataError):
37
+ pass
38
+
39
+
40
+ class QuantPadDataNotReady(QuantPadDataError):
41
+ """Backward-compatible error retained for older notebooks."""
quantpad_data/py.typed ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.4
2
+ Name: quantpad-data
3
+ Version: 0.1.0
4
+ Summary: Official Python client for the QuantPad market-data API
5
+ Project-URL: Documentation, https://api.quantpad.ai/external/docs
6
+ Author: QuantPad
7
+ License: Proprietary
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: Typing :: Typed
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: pandas<3,>=2.2
13
+ Requires-Dist: pyarrow>=18
14
+ Requires-Dist: requests>=2.32
15
+ Provides-Extra: rth
16
+ Requires-Dist: exchange-calendars>=4.5; extra == 'rth'
17
+ Provides-Extra: test
18
+ Requires-Dist: build>=1.2; extra == 'test'
19
+ Requires-Dist: pytest>=8; extra == 'test'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # QuantPad Data Python SDK
23
+
24
+ ```bash
25
+ pip install quantpad-data
26
+ export QUANTPAD_API_KEY=qp_live_...
27
+ ```
28
+
29
+ ```python
30
+ import time
31
+ import quantpad_data as qpd
32
+
33
+ end = int(time.time() * 1000)
34
+ bars = qpd.get_bars("ES.FUT", "1m", end - 86_400_000, end)
35
+
36
+ for chunk in qpd.get_ticks(
37
+ "AAPL", "trades", end - 3_600_000, end, columns=["t", "price", "size"]
38
+ ):
39
+ print(chunk.head())
40
+
41
+ matches = qpd.get_universe("apple", asset_class="equity")
42
+ coverage = qpd.get_coverage("AAPL")
43
+ ```
44
+
45
+ `QuantPadClient` (also available as `Client`) accepts `api_key=`, `base_url=`,
46
+ and `max_retries=`. The default
47
+ client honors `Retry-After` and uses exponential backoff for transient failures.
48
+ QuantPad notebooks remain compatible through `QUANTPAD_NOTEBOOK_DATA_TOKEN`.
49
+
50
+ Bars preserve the notebook helper's OHLCV aliases and smart futures
51
+ back-adjustment default. Tick timestamps are epoch nanoseconds. Install
52
+ `quantpad-data[rth]` for XNYS regular-session helpers.
53
+
54
+ This SDK intentionally exposes only bars, ticks, universe, coverage, and
55
+ symbology. It does not proxy FRED or SEC EDGAR.
@@ -0,0 +1,7 @@
1
+ quantpad_data/__init__.py,sha256=TZjloh2prMSz7QcsoCJzkhpjOE-IOQJ0O5OoOCPFZ04,7247
2
+ quantpad_data/client.py,sha256=Mzj69znNq1y4nek0Ebcr_zr3bznxSnDOyAwrIa9DskU,10126
3
+ quantpad_data/errors.py,sha256=MZrPSzy6SyQY_FcMx623Bspv1xN2TMn01ftxnAnl1ok,823
4
+ quantpad_data/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
+ quantpad_data-0.1.0.dist-info/METADATA,sha256=MJbeCRJMPUbJ-KOM2a7mYJ0H8UVNNqYfbEq-1WQwo5Q,1752
6
+ quantpad_data-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
7
+ quantpad_data-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any