finzdata 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.
finzdata/__init__.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime as dt
|
|
4
|
+
import io
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
|
12
|
+
|
|
13
|
+
_DATE_COLUMNS = ("date", "ts", "start", "end", "filed")
|
|
14
|
+
_DATE_COLUMNS = tuple(dict.fromkeys(tuple(_DATE_COLUMNS) + ("realtime_start",)))
|
|
15
|
+
_DATE_COLUMNS = tuple(dict.fromkeys(tuple(_DATE_COLUMNS) + ("trade_date",)))
|
|
16
|
+
# realtime_end stays text: the open vintage is 9999-12-31, outside pandas timestamp range
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FinzError(Exception):
|
|
20
|
+
def __init__(self, status: int, detail: str):
|
|
21
|
+
super().__init__(f"{status}: {detail}")
|
|
22
|
+
self.status = int(status)
|
|
23
|
+
self.detail = str(detail)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _iso(value: Any) -> Any:
|
|
27
|
+
if value is None:
|
|
28
|
+
return None
|
|
29
|
+
if isinstance(value, (dt.date, dt.datetime)):
|
|
30
|
+
return value.isoformat()
|
|
31
|
+
if isinstance(value, pd.Timestamp):
|
|
32
|
+
return value.isoformat()
|
|
33
|
+
return value
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _clean(params: dict) -> dict:
|
|
37
|
+
out = {}
|
|
38
|
+
for key, value in params.items():
|
|
39
|
+
if value is None:
|
|
40
|
+
continue
|
|
41
|
+
out[key] = _iso(value)
|
|
42
|
+
return out
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Client:
|
|
46
|
+
def __init__(self, api_key=None, base_url=None, http=None, timeout=60):
|
|
47
|
+
self.api_key = api_key if api_key is not None else os.environ.get("FINZDATA_API_KEY")
|
|
48
|
+
self.base_url = base_url if base_url is not None else (
|
|
49
|
+
os.environ.get("FINZDATA_URL") or "http://127.0.0.1:8787"
|
|
50
|
+
)
|
|
51
|
+
self.timeout = timeout
|
|
52
|
+
if http is not None:
|
|
53
|
+
self.http = http
|
|
54
|
+
else:
|
|
55
|
+
self.http = httpx.Client(base_url=self.base_url, timeout=timeout)
|
|
56
|
+
|
|
57
|
+
def _get(self, path: str, params: Optional[dict] = None):
|
|
58
|
+
response = self.http.get(
|
|
59
|
+
path,
|
|
60
|
+
params=_clean(params or {}),
|
|
61
|
+
headers={"X-API-Key": self.api_key},
|
|
62
|
+
)
|
|
63
|
+
if not (200 <= response.status_code < 300):
|
|
64
|
+
detail = None
|
|
65
|
+
try:
|
|
66
|
+
payload = response.json()
|
|
67
|
+
if isinstance(payload, dict):
|
|
68
|
+
detail = payload.get("detail")
|
|
69
|
+
except Exception:
|
|
70
|
+
detail = None
|
|
71
|
+
if detail is None:
|
|
72
|
+
detail = response.text
|
|
73
|
+
if not isinstance(detail, str):
|
|
74
|
+
detail = str(detail)
|
|
75
|
+
raise FinzError(response.status_code, detail)
|
|
76
|
+
return response.json()
|
|
77
|
+
|
|
78
|
+
def _frame(self, path: str, params: Optional[dict] = None) -> pd.DataFrame:
|
|
79
|
+
payload = self._get(path, params)
|
|
80
|
+
rows = payload.get("data", []) if isinstance(payload, dict) else []
|
|
81
|
+
frame = pd.DataFrame(rows)
|
|
82
|
+
for column in _DATE_COLUMNS:
|
|
83
|
+
if column in frame.columns:
|
|
84
|
+
frame[column] = pd.to_datetime(frame[column])
|
|
85
|
+
return frame
|
|
86
|
+
|
|
87
|
+
def me(self) -> dict:
|
|
88
|
+
return self._get("/v1/me")
|
|
89
|
+
|
|
90
|
+
def datasets(self):
|
|
91
|
+
return self._frame("/v1/datasets", {})
|
|
92
|
+
|
|
93
|
+
def cot(self, report=None, code=None, market=None, start=None, end=None) -> pd.DataFrame:
|
|
94
|
+
return self._frame(
|
|
95
|
+
"/v1/cot",
|
|
96
|
+
{"report": report, "code": code, "market": market, "start": start, "end": end},
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def cot_markets(self, report="legacy_fo"):
|
|
100
|
+
return self._frame("/v1/cot/markets", {"report": report})
|
|
101
|
+
|
|
102
|
+
def prices(self, symbol, start=None, end=None, adjust="splits") -> pd.DataFrame:
|
|
103
|
+
return self._frame(
|
|
104
|
+
"/v1/prices/daily",
|
|
105
|
+
{"symbol": symbol, "start": start, "end": end, "adjust": adjust},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def minutes(self, symbol, start=None, end=None, interval="1min", adjust="none") -> pd.DataFrame:
|
|
109
|
+
"""Intraday bars. interval: 1min | 5min | 1hour. adjust: none | all (splits + dividends)."""
|
|
110
|
+
return self._frame(
|
|
111
|
+
"/v1/prices/minute",
|
|
112
|
+
{"symbol": symbol, "start": start, "end": end, "interval": interval, "adjust": adjust},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def actions(self, symbol) -> pd.DataFrame:
|
|
116
|
+
return self._frame("/v1/actions", {"symbol": symbol})
|
|
117
|
+
|
|
118
|
+
def fundamentals(self, ticker=None, cik=None, concept=None, as_of=None) -> pd.DataFrame:
|
|
119
|
+
return self._frame(
|
|
120
|
+
"/v1/fundamentals",
|
|
121
|
+
{"ticker": ticker, "cik": cik, "concept": concept, "as_of": as_of},
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
_default: Optional[Client] = None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def set_default_client(client: Optional[Client]) -> None:
|
|
129
|
+
global _default
|
|
130
|
+
_default = client
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_client() -> Client:
|
|
134
|
+
global _default
|
|
135
|
+
if _default is None:
|
|
136
|
+
_default = Client()
|
|
137
|
+
return _default
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def period_start(period, today=None):
|
|
141
|
+
if today is None:
|
|
142
|
+
today = dt.date.today()
|
|
143
|
+
if isinstance(today, dt.datetime):
|
|
144
|
+
today = today.date()
|
|
145
|
+
if period is None:
|
|
146
|
+
raise ValueError(f"invalid period: {period!r}")
|
|
147
|
+
text = str(period).strip().lower()
|
|
148
|
+
if text == "max":
|
|
149
|
+
return None
|
|
150
|
+
if text == "ytd":
|
|
151
|
+
return dt.date(today.year, 1, 1)
|
|
152
|
+
for suffix, days in (("wk", 7), ("mo", 31), ("d", 1), ("y", 366)):
|
|
153
|
+
if text.endswith(suffix):
|
|
154
|
+
number = text[: -len(suffix)]
|
|
155
|
+
if number.isdigit():
|
|
156
|
+
return today - dt.timedelta(days=days * int(number))
|
|
157
|
+
raise ValueError(f"invalid period: {period!r}")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
_HISTORY_COLUMNS = ["Open", "High", "Low", "Close", "Volume"]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _empty_history(index_name: str = "Date") -> pd.DataFrame:
|
|
164
|
+
index = pd.DatetimeIndex([], name=index_name)
|
|
165
|
+
return pd.DataFrame(columns=_HISTORY_COLUMNS, index=index)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class Ticker:
|
|
169
|
+
def __init__(self, symbol, client=None):
|
|
170
|
+
self.ticker = str(symbol).upper()
|
|
171
|
+
self.client = client if client is not None else get_client()
|
|
172
|
+
|
|
173
|
+
def history(self, period="1mo", interval="1d", start=None, end=None, auto_adjust=True) -> pd.DataFrame:
|
|
174
|
+
if start is None:
|
|
175
|
+
start = period_start(period)
|
|
176
|
+
if interval == "1d":
|
|
177
|
+
frame = self.client.prices(
|
|
178
|
+
self.ticker,
|
|
179
|
+
start=start,
|
|
180
|
+
end=end,
|
|
181
|
+
adjust="all" if auto_adjust else "none",
|
|
182
|
+
)
|
|
183
|
+
index_name = "Date"
|
|
184
|
+
elif interval in _INTRADAY:
|
|
185
|
+
frame = self.client.minutes(
|
|
186
|
+
self.ticker,
|
|
187
|
+
start=start,
|
|
188
|
+
end=end,
|
|
189
|
+
interval=_INTRADAY[interval],
|
|
190
|
+
adjust="all" if auto_adjust else "none",
|
|
191
|
+
)
|
|
192
|
+
index_name = "Datetime"
|
|
193
|
+
else:
|
|
194
|
+
raise ValueError(f"invalid interval: {interval!r}")
|
|
195
|
+
return _normalize_history(frame, index_name)
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def dividends(self) -> pd.Series:
|
|
199
|
+
return self._action_series("dividend", "Dividends")
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def splits(self) -> pd.Series:
|
|
203
|
+
return self._action_series("split", "Stock Splits")
|
|
204
|
+
|
|
205
|
+
def _action_series(self, kind: str, name: str) -> pd.Series:
|
|
206
|
+
frame = self.client.actions(self.ticker)
|
|
207
|
+
index = pd.DatetimeIndex([], name="Date")
|
|
208
|
+
series = pd.Series([], dtype="float64", index=index, name=name)
|
|
209
|
+
if frame is None or frame.empty or "type" not in frame.columns:
|
|
210
|
+
return series
|
|
211
|
+
mask = frame["type"].astype(str).str.lower().str.contains(kind)
|
|
212
|
+
subset = frame[mask]
|
|
213
|
+
if subset.empty:
|
|
214
|
+
return series
|
|
215
|
+
dates = None
|
|
216
|
+
for column in ("date", "ts"):
|
|
217
|
+
if column in subset.columns:
|
|
218
|
+
dates = pd.to_datetime(subset[column])
|
|
219
|
+
break
|
|
220
|
+
if dates is None:
|
|
221
|
+
dates = pd.to_datetime(subset.index)
|
|
222
|
+
values = None
|
|
223
|
+
for column in ("value", "amount", "dividend", "split"):
|
|
224
|
+
if column in subset.columns:
|
|
225
|
+
values = subset[column]
|
|
226
|
+
break
|
|
227
|
+
if values is None:
|
|
228
|
+
numeric = [c for c in subset.columns if c not in ("type", "date", "ts")]
|
|
229
|
+
values = subset[numeric[0]] if numeric else pd.Series([1.0] * len(subset))
|
|
230
|
+
series = pd.Series(
|
|
231
|
+
pd.to_numeric(values, errors="coerce").to_numpy(),
|
|
232
|
+
index=pd.DatetimeIndex(dates, name="Date"),
|
|
233
|
+
name=name,
|
|
234
|
+
)
|
|
235
|
+
return series.sort_index()
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def actions(self) -> pd.DataFrame:
|
|
239
|
+
dividends = self.dividends
|
|
240
|
+
splits = self.splits
|
|
241
|
+
frame = pd.DataFrame({"Dividends": dividends, "Stock Splits": splits})
|
|
242
|
+
frame = frame.fillna(0.0)
|
|
243
|
+
frame.index = pd.DatetimeIndex(frame.index, name="Date")
|
|
244
|
+
return frame
|
|
245
|
+
|
|
246
|
+
def facts(self, concept=None, as_of=None) -> pd.DataFrame:
|
|
247
|
+
return self.client.fundamentals(ticker=self.ticker, concept=concept, as_of=as_of)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _normalize_history(frame: pd.DataFrame, index_name: str) -> pd.DataFrame:
|
|
251
|
+
if frame is None or frame.empty:
|
|
252
|
+
return _empty_history(index_name)
|
|
253
|
+
frame = frame.copy()
|
|
254
|
+
date_column = None
|
|
255
|
+
for column in ("date", "ts"):
|
|
256
|
+
if column in frame.columns:
|
|
257
|
+
date_column = column
|
|
258
|
+
break
|
|
259
|
+
if date_column is not None:
|
|
260
|
+
frame.index = pd.DatetimeIndex(pd.to_datetime(frame[date_column]), name=index_name)
|
|
261
|
+
frame = frame.drop(columns=[date_column])
|
|
262
|
+
else:
|
|
263
|
+
frame.index = pd.DatetimeIndex(pd.to_datetime(frame.index), name=index_name)
|
|
264
|
+
rename = {}
|
|
265
|
+
for column in frame.columns:
|
|
266
|
+
lowered = str(column).lower()
|
|
267
|
+
for target in _HISTORY_COLUMNS:
|
|
268
|
+
if lowered == target.lower():
|
|
269
|
+
rename[column] = target
|
|
270
|
+
frame = frame.rename(columns=rename)
|
|
271
|
+
for column in _HISTORY_COLUMNS:
|
|
272
|
+
if column not in frame.columns:
|
|
273
|
+
frame[column] = pd.Series(dtype="float64")
|
|
274
|
+
frame = frame[_HISTORY_COLUMNS]
|
|
275
|
+
return frame.sort_index()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _parse_tickers(tickers) -> list:
|
|
279
|
+
if isinstance(tickers, str):
|
|
280
|
+
parts = tickers.replace(",", " ").split()
|
|
281
|
+
return [part.upper() for part in parts]
|
|
282
|
+
return [str(ticker).upper() for ticker in tickers]
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
_INTRADAY = {"1m": "1min", "5m": "5min", "60m": "1hour", "1h": "1hour"}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def download(tickers, start=None, end=None, period="1mo", interval="1d", auto_adjust=True, client=None) -> pd.DataFrame:
|
|
289
|
+
symbols = _parse_tickers(tickers)
|
|
290
|
+
frames = {}
|
|
291
|
+
for symbol in symbols:
|
|
292
|
+
ticker = Ticker(symbol, client=client)
|
|
293
|
+
frames[symbol] = ticker.history(
|
|
294
|
+
period=period,
|
|
295
|
+
interval=interval,
|
|
296
|
+
start=start,
|
|
297
|
+
end=end,
|
|
298
|
+
auto_adjust=auto_adjust,
|
|
299
|
+
)
|
|
300
|
+
if not frames:
|
|
301
|
+
return pd.DataFrame()
|
|
302
|
+
combined = pd.concat(frames, axis=1)
|
|
303
|
+
combined.columns = combined.columns.swaplevel(0, 1)
|
|
304
|
+
combined = combined.sort_index(axis=1, level=0)
|
|
305
|
+
return combined.sort_index()
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def cot(**kw) -> pd.DataFrame:
|
|
309
|
+
return get_client().cot(**kw)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# --- macro data (FRED/ALFRED, point-in-time) -------------------------------------------
|
|
313
|
+
def _clean(params: dict) -> dict:
|
|
314
|
+
return {k: (v.isoformat() if hasattr(v, "isoformat") else v) for k, v in params.items() if v is not None}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _macro(self, series, start=None, end=None, vintage="latest", as_of=None) -> pd.DataFrame:
|
|
318
|
+
"""Macro series. vintage: 'latest' (current values), 'first' (as first released) or 'all';
|
|
319
|
+
as_of='YYYY-MM-DD' returns only what was known on that date (point-in-time, Researcher+)."""
|
|
320
|
+
return self._frame("/v1/macro", _clean({"series": series, "start": start, "end": end, "vintage": vintage, "as_of": as_of}))
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _macro_series(self, category=None) -> pd.DataFrame:
|
|
324
|
+
"""Catalog of available macro series (id, title, units, frequency, category, ...)."""
|
|
325
|
+
return self._frame("/v1/macro/series", _clean({"category": category}))
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _macro_calendar(self, start=None, end=None) -> pd.DataFrame:
|
|
329
|
+
"""Upcoming and recent economic release dates."""
|
|
330
|
+
return self._frame("/v1/macro/calendar", _clean({"start": start, "end": end}))
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
Client.macro = _macro
|
|
334
|
+
Client.macro_series = _macro_series
|
|
335
|
+
Client.macro_calendar = _macro_calendar
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def macro(series, **kw):
|
|
339
|
+
return get_client().macro(series, **kw)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _licensed(self, series_id, start=None, end=None) -> pd.DataFrame:
|
|
343
|
+
"""Third-party licensed series (paid plans). Each row carries its `source` citation."""
|
|
344
|
+
return self._frame("/v1/licensed/series", _clean({"id": series_id, "start": start, "end": end}))
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _licensed_catalog(self) -> pd.DataFrame:
|
|
348
|
+
"""Licensed series, their owners, citations and availability."""
|
|
349
|
+
return self._frame("/v1/licensed/catalog", {})
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _vix_futures(self, start=None, end=None) -> pd.DataFrame:
|
|
353
|
+
"""Daily VIX futures settlements by expiry (paid plans)."""
|
|
354
|
+
return self._frame("/v1/licensed/vix-futures", _clean({"start": start, "end": end}))
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
Client.licensed = _licensed
|
|
358
|
+
Client.licensed_catalog = _licensed_catalog
|
|
359
|
+
Client.vix_futures = _vix_futures
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _model(self, what="latest", series=None, start=None, end=None):
|
|
363
|
+
"""FinzData Quantitative Model (paid plans): what = latest | labels | nowcast | stress | catalog."""
|
|
364
|
+
if what not in ("latest", "labels", "nowcast", "stress", "catalog"):
|
|
365
|
+
raise ValueError("what must be latest, labels, nowcast, stress or catalog")
|
|
366
|
+
if what == "latest":
|
|
367
|
+
return self._get("/v1/model/latest", {})
|
|
368
|
+
return self._frame(f"/v1/model/{what}", _clean({"series": series, "start": start, "end": end}))
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
Client.model = _model
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: finzdata
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FinzData market data client: yfinance-style prices plus COT, SEC fundamentals, point-in-time macro and more
|
|
5
|
+
Project-URL: Homepage, https://finzdata.com
|
|
6
|
+
Project-URL: Documentation, https://finzdata.com/docs
|
|
7
|
+
Project-URL: Pricing, https://finzdata.com/pricing
|
|
8
|
+
Project-URL: Status, https://finzdata.com/status
|
|
9
|
+
Keywords: market data,stocks,yfinance,cot,sec,fred,finance,api
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
12
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: pandas>=1.5
|
|
17
|
+
Requires-Dist: httpx>=0.24
|
|
18
|
+
Requires-Dist: pyarrow>=12
|
|
19
|
+
|
|
20
|
+
# finzdata
|
|
21
|
+
|
|
22
|
+
Python client for the [FinzData](https://finzdata.com) market data API. It works like yfinance and adds
|
|
23
|
+
the datasets yfinance doesn't have.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
pip install finzdata
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Get a free API key at https://api.finzdata.com/ and set `FINZDATA_API_KEY`.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import finzdata as yf
|
|
33
|
+
|
|
34
|
+
msft = yf.Ticker("MSFT")
|
|
35
|
+
daily = msft.history(start="2020-01-01") # daily bars, split and dividend adjusted
|
|
36
|
+
bars = msft.history(interval="5m", start="2026-10-01") # 1m, 5m, 1h bars (Researcher+)
|
|
37
|
+
both = yf.download(["SPY", "QQQ"], period="max")
|
|
38
|
+
|
|
39
|
+
c = yf.Client()
|
|
40
|
+
gold = c.cot(report="legacy_fo", market="GOLD") # CFTC Commitments of Traders
|
|
41
|
+
rev = c.fundamentals(ticker="AAPL", concept="Revenues", as_of="2025-06-30") # SEC, point-in-time
|
|
42
|
+
cpi = c.macro("CPIAUCSL", as_of="2022-06-30") # US macro, as known on that date
|
|
43
|
+
regime = c.model("latest") # FinzData Quantitative Model (Researcher+)
|
|
44
|
+
hy = c.licensed("BAMLH0A0HYM2") # licensed third-party series (Researcher+)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Datasets
|
|
48
|
+
|
|
49
|
+
- US equity bars, daily (incl. delisted names) and intraday 1-minute, 5-minute and hourly
|
|
50
|
+
- Splits and dividends
|
|
51
|
+
- CFTC Commitments of Traders, all reports, since 1986
|
|
52
|
+
- SEC XBRL fundamentals, as reported and point-in-time
|
|
53
|
+
- US macro series with every vintage (FRED/ALFRED), plus a release calendar
|
|
54
|
+
- FinzData Quantitative Model: macro regime labels, a nowcast and a market-stress probability
|
|
55
|
+
- Licensed series: Moody's, University of Michigan, Case-Shiller, ICE BofA, Cboe (incl. the VIX futures curve)
|
|
56
|
+
|
|
57
|
+
Free samples and the full file specification: https://finzdata.com/samples ·
|
|
58
|
+
Pricing: https://finzdata.com/pricing · Data status: https://finzdata.com/status ·
|
|
59
|
+
API reference: https://api.finzdata.com/docs
|
|
60
|
+
|
|
61
|
+
Bulk downloads (Researcher and above): `POST /v1/batch`, then fetch the `download_url`.
|
|
62
|
+
Full-history bundles and nightly updates: `GET /v1/bundles`.
|
|
63
|
+
|
|
64
|
+
Data is provided under the FinzData subscriber licence; redistribution is prohibited.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
finzdata/__init__.py,sha256=epQ6sCN6c7LLbfSU27I8e4KzGiAO5LeRaDEvfngU7SM,13099
|
|
2
|
+
finzdata-0.1.0.dist-info/METADATA,sha256=L-mwEwYtkE_vHHw3NNHJsxvEZJ1joUkJxtRiu2NVDb0,2825
|
|
3
|
+
finzdata-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
4
|
+
finzdata-0.1.0.dist-info/top_level.txt,sha256=2NSdEKSomijSh8gY7wh4u2-oRqmt5Ou46AMCubaCv0M,9
|
|
5
|
+
finzdata-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
finzdata
|