vanda-api 1.0.2__tar.gz → 1.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.
- vanda_api-1.1.0/CHANGELOG.md +118 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/PKG-INFO +114 -8
- {vanda_api-1.0.2 → vanda_api-1.1.0}/README.md +113 -7
- {vanda_api-1.0.2 → vanda_api-1.1.0}/pyproject.toml +1 -1
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/__init__.py +1 -1
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/_base/async_base_client.py +19 -6
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/_base/base_client.py +4 -1
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/async_auth.py +6 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/async_client.py +6 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/auth.py +32 -4
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/client.py +9 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/models.py +10 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/retry.py +27 -2
- vanda_api-1.1.0/src/vanda/services/async_flow.py +385 -0
- vanda_api-1.1.0/src/vanda/services/async_positioning.py +253 -0
- vanda_api-1.1.0/src/vanda/services/flow.py +383 -0
- vanda_api-1.1.0/src/vanda/services/positioning.py +253 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/utils/normalize.py +47 -2
- vanda_api-1.1.0/src/vanda/utils/params.py +18 -0
- vanda_api-1.1.0/tests/test_async_flow_client.py +425 -0
- vanda_api-1.1.0/tests/test_async_positioning_client.py +269 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/tests/test_auth.py +56 -0
- vanda_api-1.1.0/tests/test_flow_client.py +423 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/tests/test_normalize.py +25 -0
- vanda_api-1.1.0/tests/test_positioning_client.py +257 -0
- vanda_api-1.1.0/tests/test_retry.py +63 -0
- vanda_api-1.0.2/CHANGELOG.md +0 -85
- vanda_api-1.0.2/tests/test_retry.py +0 -24
- {vanda_api-1.0.2 → vanda_api-1.1.0}/.gitignore +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/LICENSE +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/_base/__init__.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/errors.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/services/__init__.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/services/aggregates.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/services/async_aggregates.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/services/async_series.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/services/series.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/utils/__init__.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/utils/dates.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/utils/deprecation.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/src/vanda/utils/io.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/tests/__init__.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/tests/test_aggregates_client.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/tests/test_async_aggregates_client.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/tests/test_async_series_client.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/tests/test_dates.py +0 -0
- {vanda_api-1.0.2 → vanda_api-1.1.0}/tests/test_series_client.py +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.1.0] - 2026-09-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `client.positioning` / `AsyncVandaClient.positioning` - Positioning API support
|
|
12
|
+
- `client.flow` / `AsyncVandaClient.flow` - Flow API support
|
|
13
|
+
- Both expose `get_timeseries()`, `get_latest()`, `export_timeseries()` and `list_catalog()`; flow adds the two forecast methods below
|
|
14
|
+
- `series_ids`, `metrics` and `frequency` accept a sequence as well as a comma-separated string
|
|
15
|
+
- `normalize_result()` accepts `preserve` so responses can keep envelope keys such as `pagination` and `count`
|
|
16
|
+
- Flow's `unresolved` list is preserved, so identifiers that matched nothing are visible to the caller
|
|
17
|
+
- `normalisation` / `normalisation_period` on `positioning.get_timeseries()`, including the positioning-only `"none"` that opts out of the service's default
|
|
18
|
+
- `flow.get_forecast_runs()` - the run dates, horizons, components and scenario tokens a forecast series publishes
|
|
19
|
+
- `flow.get_forecast()` - one scenario of the forecast cube, grouped one object per (series, component, horizon)
|
|
20
|
+
- `dimensions` on `flow.get_timeseries()`, to pin one cell of a dimensioned dataset; accepts a dict and sends it as JSON
|
|
21
|
+
- `sdk_key=` on `VandaClient` / `AsyncVandaClient`, and the `VANDA_SDK_KEY` environment variable - the user API key generated at https://www.vanda-analytics.com/tools/api-keys, sent as the `x-api-key` header
|
|
22
|
+
- Unit tests and examples for both services
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- A failed transformation no longer discards the response. positioning and flow serve the untransformed rows under the upstream's status code; `get_timeseries()` now returns those rows with `transformation.status` reporting `"Failure"` instead of raising `ServerError`
|
|
26
|
+
- Such responses are no longer retried - they are deterministic, and retrying only added latency
|
|
27
|
+
|
|
28
|
+
### Notes
|
|
29
|
+
- `transformation` requires `window`, and vice versa
|
|
30
|
+
- Positioning normalises by default (`zscore` over `All`), so omitting both params is not the same as raw data; pass `normalisation="none"` for that. Flow's normalisation is opt-in and pairs its two params
|
|
31
|
+
- `data` always holds the raw stored series; transformed and normalised values arrive under `transformation`
|
|
32
|
+
- `flow.get_forecast()` takes neither a transformation nor a normalisation. Horizon 0 is the run date, so on the z-score axis it exists only for the `"All"` scenario, and omitting `horizons` returns every one the series publishes
|
|
33
|
+
- The forecast scenario axis is named per component - `zscore` for `cta`, `rp` and `vt`, `ret` for `gamma` - so read `scenario_key` from `get_forecast_runs()` rather than assuming
|
|
34
|
+
- `dimensions` is matched by JSON containment, so the value types are load-bearing: `{"projection": "10"}` matches nothing where `{"projection": 10}` matches
|
|
35
|
+
- Flow's `unresolved` list only appears when at least one identifier resolved; a request where none do raises `NotFoundError`
|
|
36
|
+
- An SDK key resolves to its owner, so that user's entitlement and tier limits apply; it needs no token exchange and nothing is cached on disk. It authenticates all four services, but does not widen entitlement - a full-history export without Data Licence access still raises `RateLimitError`
|
|
37
|
+
- When several credentials are supplied, `sdk_key` wins, then `token`, then email/password
|
|
38
|
+
- Intraday flow series are capped at 31 days per request, an undated request returns the latest 31 days, and transformations on them are refused by the service
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## [1.0.1] - 2026-06-03
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
- `get_timeseries()` now returns `dict[str, Any]`
|
|
45
|
+
- `get_timeseries_many()` now returns `dict[str, Any]`
|
|
46
|
+
- `get_leaderboard()` now returns `dict[str, Any]`
|
|
47
|
+
- `list_securities()` now returns `dict[str, Any]`
|
|
48
|
+
- `get_constituents()` now returns `dict[str, Any]`
|
|
49
|
+
- `bulk_securities()` now returns `Union[str, dict[str, Any]]`
|
|
50
|
+
- `get_daily_snapshot()` now returns `Union[str, dict[str, Any]]`
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
- Support for transformations (`rolling_sum`, `moving_avg`)
|
|
54
|
+
- Support for normalisation (`zscore`, `percentile_rank`)
|
|
55
|
+
- Support for configurable rolling windows
|
|
56
|
+
- Support for configurable normalisation periods
|
|
57
|
+
- Support for `is_full_history`
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## [1.0.0] - 2026-03-26
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
- Moved API methods under namespaces:
|
|
65
|
+
- client.get_timeseries() to client.series.get_timeseries()
|
|
66
|
+
- client.get_timeseries_many to client.series.get_timeseries_many()
|
|
67
|
+
- client.get_leaderboard to client.series.get_leaderboard()
|
|
68
|
+
- client.create_bulk_securities_job to client.series.create_bulk_securities_job()
|
|
69
|
+
- client.bulk_securities to client.series.bulk_securities()
|
|
70
|
+
- client.get_daily_snapshot to client.series.get_daily_snapshot()
|
|
71
|
+
- client.get_job to client.series.get_job()
|
|
72
|
+
- client.get_job_status to client.series.get_job_status()
|
|
73
|
+
- client.poll_job to client.series.poll_job()
|
|
74
|
+
- client.wait_for_job to client.series.wait_for_job()
|
|
75
|
+
- client.export_job_result to client.series.export_job_result()
|
|
76
|
+
- client.stream_job_result to client.series.stream_job_result()
|
|
77
|
+
- client.export_timeseries to client.series.export_timeseries()
|
|
78
|
+
- client.list_fields to client.series.list_fields()
|
|
79
|
+
- client.list_intervals to client.series.list_intervals()
|
|
80
|
+
- client.list_securities to client.series.list_securities()
|
|
81
|
+
- Old methods still work but are deprecated.
|
|
82
|
+
- Added new endpoints for aggregates timeseries and constituents
|
|
83
|
+
- Refactored code to make use of common functions in series and aggregates endpoints
|
|
84
|
+
- Updated unit test cases with new changes for calling the endpoints
|
|
85
|
+
- Updated examples file to use series and aggregates endpoints
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## [0.1.1] - 2026-02-20
|
|
89
|
+
|
|
90
|
+
### Added
|
|
91
|
+
|
|
92
|
+
- Email/password authentication support with automatic token caching
|
|
93
|
+
- Token cache with expiration management in `~/.vanda/token_cache.json`
|
|
94
|
+
- Entitlement validation on authentication
|
|
95
|
+
- Support for `VANDA_LOGIN_EMAIL` and `VANDA_PASSWORD` environment variables
|
|
96
|
+
- Async authentication methods in `AsyncAuth` class
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
|
|
100
|
+
- Fixed `asset_class` parameter format in bulk operations (now sent as list)
|
|
101
|
+
- Fixed async authentication to include entitlement check
|
|
102
|
+
|
|
103
|
+
## [0.1.0] - 2024-02-06
|
|
104
|
+
|
|
105
|
+
### Added
|
|
106
|
+
|
|
107
|
+
- Initial release
|
|
108
|
+
- Synchronous VandaClient
|
|
109
|
+
- Asynchronous AsyncVandaClient
|
|
110
|
+
- All 12 API endpoints implemented
|
|
111
|
+
- Retry logic with exponential backoff
|
|
112
|
+
- Comprehensive error handling
|
|
113
|
+
- Job polling utilities
|
|
114
|
+
- CSV/JSONL export support
|
|
115
|
+
- Optional pandas support
|
|
116
|
+
- Type hints throughout
|
|
117
|
+
- Full test coverage
|
|
118
|
+
- CI/CD with GitHub Actions
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vanda-api
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Official Python SDK for Vanda Analytics Data API
|
|
5
5
|
Project-URL: Homepage, https://gitlab.com/yourusername/vanda-api
|
|
6
6
|
Project-URL: Documentation, https://gitlab.com/yourusername/vanda-api#readme
|
|
@@ -218,6 +218,34 @@ export VANDA_PASSWORD="your_password"
|
|
|
218
218
|
|
|
219
219
|
## Authentication
|
|
220
220
|
|
|
221
|
+
### SDK API key
|
|
222
|
+
|
|
223
|
+
Generate a key in the Vanda frontend at
|
|
224
|
+
[www.vanda-analytics.com/tools/api-keys](https://www.vanda-analytics.com/tools/api-keys),
|
|
225
|
+
then pass it to the client:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
client = VandaClient(sdk_key="your_key_here")
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
or set it in the environment:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
export VANDA_SDK_KEY="your_key_here"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
A key belongs to your user, so your own entitlement and tier limits apply to
|
|
238
|
+
every request made with it. It is sent as a header and needs no token exchange,
|
|
239
|
+
so there is nothing to refresh and nothing cached on disk. Keep it secret and
|
|
240
|
+
revoke it from the same page if it leaks.
|
|
241
|
+
|
|
242
|
+
An SDK key authenticates every service the client covers - `series`,
|
|
243
|
+
`aggregates`, `positioning` and `flow`. What it does not do is widen your
|
|
244
|
+
entitlement: a full-history export, for example, still needs approved Data
|
|
245
|
+
Licence access and is otherwise refused with `RateLimitError`.
|
|
246
|
+
|
|
247
|
+
### Token, or email and password
|
|
248
|
+
|
|
221
249
|
Set your API token via environment variable:
|
|
222
250
|
|
|
223
251
|
```bash
|
|
@@ -243,6 +271,9 @@ or
|
|
|
243
271
|
client = VandaClient(email="your_email@example.com", password="your_password")
|
|
244
272
|
```
|
|
245
273
|
|
|
274
|
+
If more than one credential is supplied, `sdk_key` wins, then `token`, then
|
|
275
|
+
email and password.
|
|
276
|
+
|
|
246
277
|
## Features
|
|
247
278
|
|
|
248
279
|
- Sync and async clients with consistent interfaces
|
|
@@ -316,9 +347,28 @@ Responses keep their envelope: alongside `data` you get `count`, `metric`,
|
|
|
316
347
|
`revision`, `series_ids`, `version` and `pagination`. Use `pagination` to page -
|
|
317
348
|
`records_per_page` defaults to 2000 and is capped by your access tier.
|
|
318
349
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
350
|
+
`transformation` and `window` must be passed together, and a transformation has
|
|
351
|
+
to resolve to exactly one series.
|
|
352
|
+
|
|
353
|
+
Positioning normalises by default: omitting `normalisation` /
|
|
354
|
+
`normalisation_period` is the same as asking for `zscore` over `All`. Neither
|
|
355
|
+
needs its partner, since the service defaults both. Pass `normalisation="none"`
|
|
356
|
+
for the raw stored values.
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
raw = client.positioning.get_timeseries(series_ids=series_label, normalisation="none")
|
|
360
|
+
ranked = client.positioning.get_timeseries(
|
|
361
|
+
series_ids=series_label,
|
|
362
|
+
normalisation="percentile_rank",
|
|
363
|
+
normalisation_period="1y",
|
|
364
|
+
)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Note that `data` always holds the raw stored series - transformed and normalised
|
|
368
|
+
values arrive under `transformation["results"]["data"]`. Asking for more than one
|
|
369
|
+
series skips the normalisation rather than failing, reporting
|
|
370
|
+
`transformation.status` as `"skipped"`. `normalisation_period` is a calendar
|
|
371
|
+
lookback (`3m` to `All`), where `window` counts observations.
|
|
322
372
|
|
|
323
373
|
### Flow Data
|
|
324
374
|
|
|
@@ -328,6 +378,8 @@ Accessed via `client.flow`.
|
|
|
328
378
|
- `get_latest()` - Get the latest observation per series
|
|
329
379
|
- `export_timeseries()` - Export flow timeseries to file
|
|
330
380
|
- `list_catalog()` - Search the catalog and filter by dimension
|
|
381
|
+
- `get_forecast_runs()` - List the runs, horizons and scenarios a forecast series publishes
|
|
382
|
+
- `get_forecast()` - Get one scenario of the forecast cube
|
|
331
383
|
|
|
332
384
|
```python
|
|
333
385
|
from vanda import VandaClient
|
|
@@ -352,11 +404,64 @@ with VandaClient(token="YOUR_TOKEN_HERE") as client:
|
|
|
352
404
|
`series_ids`, `metrics` and `frequency` accept either a comma-separated string
|
|
353
405
|
or a sequence. Identifiers that matched nothing are reported in `unresolved`
|
|
354
406
|
rather than failing the request, so check it before treating a short result as
|
|
355
|
-
complete.
|
|
407
|
+
complete. That holds as long as *something* resolved - a request where no
|
|
408
|
+
identifier matches is a 404 and raises `NotFoundError`.
|
|
409
|
+
|
|
410
|
+
Some flow datasets are published as a cube rather than a flat series, with a
|
|
411
|
+
value per combination of dimensions. Pass `dimensions` to pin one cell:
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
data = client.flow.get_timeseries(
|
|
415
|
+
series_ids=slug, dimensions={"projection": 10, "zscore": "All"}
|
|
416
|
+
)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
It is sent as JSON because the types decide what matches - `projection` is
|
|
420
|
+
stored as a number and `zscore` as a string, so `{"projection": "10"}` matches
|
|
421
|
+
nothing at all.
|
|
422
|
+
|
|
423
|
+
Intraday series (FX options) are capped at 31 days per request. A request with
|
|
424
|
+
no dates returns the most recent 31 days rather than the full history, a longer
|
|
425
|
+
span is refused, and transformations are not available on them yet.
|
|
426
|
+
|
|
427
|
+
Flow normalisation is opt-in, where positioning's is on by default, and flow
|
|
428
|
+
pairs its params: `normalisation` / `normalisation_period` must be passed
|
|
429
|
+
together, as must `transformation` / `window`. A transformation has to resolve to
|
|
430
|
+
exactly one series, and flow has no `none` value - simply omit them for raw
|
|
431
|
+
values.
|
|
432
|
+
|
|
433
|
+
#### Forecasts
|
|
434
|
+
|
|
435
|
+
Some flow datasets publish a forecast **cube**: every combination of a horizon
|
|
436
|
+
and a scenario, for each run date. Ask what a series publishes before reading
|
|
437
|
+
it - the scenario axis is named per component, `zscore` for `cta`, `rp` and
|
|
438
|
+
`vt`, `ret` for `gamma`:
|
|
439
|
+
|
|
440
|
+
```python
|
|
441
|
+
with VandaClient(token="YOUR_TOKEN_HERE") as client:
|
|
442
|
+
axes = client.flow.get_forecast_runs(series_ids="AUFXFUTUAUDMIXINSSPECTA")
|
|
443
|
+
series = axes["data"][0]
|
|
444
|
+
print(series["components"], series["scenario_key"], series["horizons"])
|
|
445
|
+
|
|
446
|
+
forecast = client.flow.get_forecast(
|
|
447
|
+
series_ids="AUFXFUTUAUDMIXINSSPECTA",
|
|
448
|
+
scenario="All",
|
|
449
|
+
horizons=[1, 5, 10],
|
|
450
|
+
)
|
|
451
|
+
print(forecast["run_date"], forecast["scenario"])
|
|
452
|
+
for group in forecast["data"]:
|
|
453
|
+
print(group["horizon"], group["component"], group["metrics"])
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Rows come back as one group per (series, component, horizon), with every metric
|
|
457
|
+
nested inside - a forecast is read as a point on a path. Horizon 0 is the run
|
|
458
|
+
date itself, so on the z-score axis it exists only for the unconditional
|
|
459
|
+
scenario (`"All"`). There is no `forecast_date`: pair `run_date` with `horizon`.
|
|
460
|
+
Omitting `horizons` returns every horizon the series publishes, which is the
|
|
461
|
+
largest response this endpoint produces - name the ones you need, or page.
|
|
356
462
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
transformation has to resolve to exactly one series.
|
|
463
|
+
Neither method takes a transformation or a normalisation; a projection is not a
|
|
464
|
+
series to roll up or re-standardise.
|
|
360
465
|
|
|
361
466
|
For both services, if the transformation service is unavailable the
|
|
362
467
|
untransformed rows are returned with `transformation.status` set to `"Failure"`
|
|
@@ -634,6 +739,7 @@ Supported values:
|
|
|
634
739
|
```python
|
|
635
740
|
normalisation="zscore"
|
|
636
741
|
normalisation="percentile_rank"
|
|
742
|
+
normalisation="none" # positioning only - opts out of its default
|
|
637
743
|
```
|
|
638
744
|
|
|
639
745
|
### Supported Normalisation Periods
|
|
@@ -180,6 +180,34 @@ export VANDA_PASSWORD="your_password"
|
|
|
180
180
|
|
|
181
181
|
## Authentication
|
|
182
182
|
|
|
183
|
+
### SDK API key
|
|
184
|
+
|
|
185
|
+
Generate a key in the Vanda frontend at
|
|
186
|
+
[www.vanda-analytics.com/tools/api-keys](https://www.vanda-analytics.com/tools/api-keys),
|
|
187
|
+
then pass it to the client:
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
client = VandaClient(sdk_key="your_key_here")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
or set it in the environment:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
export VANDA_SDK_KEY="your_key_here"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
A key belongs to your user, so your own entitlement and tier limits apply to
|
|
200
|
+
every request made with it. It is sent as a header and needs no token exchange,
|
|
201
|
+
so there is nothing to refresh and nothing cached on disk. Keep it secret and
|
|
202
|
+
revoke it from the same page if it leaks.
|
|
203
|
+
|
|
204
|
+
An SDK key authenticates every service the client covers - `series`,
|
|
205
|
+
`aggregates`, `positioning` and `flow`. What it does not do is widen your
|
|
206
|
+
entitlement: a full-history export, for example, still needs approved Data
|
|
207
|
+
Licence access and is otherwise refused with `RateLimitError`.
|
|
208
|
+
|
|
209
|
+
### Token, or email and password
|
|
210
|
+
|
|
183
211
|
Set your API token via environment variable:
|
|
184
212
|
|
|
185
213
|
```bash
|
|
@@ -205,6 +233,9 @@ or
|
|
|
205
233
|
client = VandaClient(email="your_email@example.com", password="your_password")
|
|
206
234
|
```
|
|
207
235
|
|
|
236
|
+
If more than one credential is supplied, `sdk_key` wins, then `token`, then
|
|
237
|
+
email and password.
|
|
238
|
+
|
|
208
239
|
## Features
|
|
209
240
|
|
|
210
241
|
- Sync and async clients with consistent interfaces
|
|
@@ -278,9 +309,28 @@ Responses keep their envelope: alongside `data` you get `count`, `metric`,
|
|
|
278
309
|
`revision`, `series_ids`, `version` and `pagination`. Use `pagination` to page -
|
|
279
310
|
`records_per_page` defaults to 2000 and is capped by your access tier.
|
|
280
311
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
312
|
+
`transformation` and `window` must be passed together, and a transformation has
|
|
313
|
+
to resolve to exactly one series.
|
|
314
|
+
|
|
315
|
+
Positioning normalises by default: omitting `normalisation` /
|
|
316
|
+
`normalisation_period` is the same as asking for `zscore` over `All`. Neither
|
|
317
|
+
needs its partner, since the service defaults both. Pass `normalisation="none"`
|
|
318
|
+
for the raw stored values.
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
raw = client.positioning.get_timeseries(series_ids=series_label, normalisation="none")
|
|
322
|
+
ranked = client.positioning.get_timeseries(
|
|
323
|
+
series_ids=series_label,
|
|
324
|
+
normalisation="percentile_rank",
|
|
325
|
+
normalisation_period="1y",
|
|
326
|
+
)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Note that `data` always holds the raw stored series - transformed and normalised
|
|
330
|
+
values arrive under `transformation["results"]["data"]`. Asking for more than one
|
|
331
|
+
series skips the normalisation rather than failing, reporting
|
|
332
|
+
`transformation.status` as `"skipped"`. `normalisation_period` is a calendar
|
|
333
|
+
lookback (`3m` to `All`), where `window` counts observations.
|
|
284
334
|
|
|
285
335
|
### Flow Data
|
|
286
336
|
|
|
@@ -290,6 +340,8 @@ Accessed via `client.flow`.
|
|
|
290
340
|
- `get_latest()` - Get the latest observation per series
|
|
291
341
|
- `export_timeseries()` - Export flow timeseries to file
|
|
292
342
|
- `list_catalog()` - Search the catalog and filter by dimension
|
|
343
|
+
- `get_forecast_runs()` - List the runs, horizons and scenarios a forecast series publishes
|
|
344
|
+
- `get_forecast()` - Get one scenario of the forecast cube
|
|
293
345
|
|
|
294
346
|
```python
|
|
295
347
|
from vanda import VandaClient
|
|
@@ -314,11 +366,64 @@ with VandaClient(token="YOUR_TOKEN_HERE") as client:
|
|
|
314
366
|
`series_ids`, `metrics` and `frequency` accept either a comma-separated string
|
|
315
367
|
or a sequence. Identifiers that matched nothing are reported in `unresolved`
|
|
316
368
|
rather than failing the request, so check it before treating a short result as
|
|
317
|
-
complete.
|
|
369
|
+
complete. That holds as long as *something* resolved - a request where no
|
|
370
|
+
identifier matches is a 404 and raises `NotFoundError`.
|
|
371
|
+
|
|
372
|
+
Some flow datasets are published as a cube rather than a flat series, with a
|
|
373
|
+
value per combination of dimensions. Pass `dimensions` to pin one cell:
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
data = client.flow.get_timeseries(
|
|
377
|
+
series_ids=slug, dimensions={"projection": 10, "zscore": "All"}
|
|
378
|
+
)
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
It is sent as JSON because the types decide what matches - `projection` is
|
|
382
|
+
stored as a number and `zscore` as a string, so `{"projection": "10"}` matches
|
|
383
|
+
nothing at all.
|
|
384
|
+
|
|
385
|
+
Intraday series (FX options) are capped at 31 days per request. A request with
|
|
386
|
+
no dates returns the most recent 31 days rather than the full history, a longer
|
|
387
|
+
span is refused, and transformations are not available on them yet.
|
|
388
|
+
|
|
389
|
+
Flow normalisation is opt-in, where positioning's is on by default, and flow
|
|
390
|
+
pairs its params: `normalisation` / `normalisation_period` must be passed
|
|
391
|
+
together, as must `transformation` / `window`. A transformation has to resolve to
|
|
392
|
+
exactly one series, and flow has no `none` value - simply omit them for raw
|
|
393
|
+
values.
|
|
394
|
+
|
|
395
|
+
#### Forecasts
|
|
396
|
+
|
|
397
|
+
Some flow datasets publish a forecast **cube**: every combination of a horizon
|
|
398
|
+
and a scenario, for each run date. Ask what a series publishes before reading
|
|
399
|
+
it - the scenario axis is named per component, `zscore` for `cta`, `rp` and
|
|
400
|
+
`vt`, `ret` for `gamma`:
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
with VandaClient(token="YOUR_TOKEN_HERE") as client:
|
|
404
|
+
axes = client.flow.get_forecast_runs(series_ids="AUFXFUTUAUDMIXINSSPECTA")
|
|
405
|
+
series = axes["data"][0]
|
|
406
|
+
print(series["components"], series["scenario_key"], series["horizons"])
|
|
407
|
+
|
|
408
|
+
forecast = client.flow.get_forecast(
|
|
409
|
+
series_ids="AUFXFUTUAUDMIXINSSPECTA",
|
|
410
|
+
scenario="All",
|
|
411
|
+
horizons=[1, 5, 10],
|
|
412
|
+
)
|
|
413
|
+
print(forecast["run_date"], forecast["scenario"])
|
|
414
|
+
for group in forecast["data"]:
|
|
415
|
+
print(group["horizon"], group["component"], group["metrics"])
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Rows come back as one group per (series, component, horizon), with every metric
|
|
419
|
+
nested inside - a forecast is read as a point on a path. Horizon 0 is the run
|
|
420
|
+
date itself, so on the z-score axis it exists only for the unconditional
|
|
421
|
+
scenario (`"All"`). There is no `forecast_date`: pair `run_date` with `horizon`.
|
|
422
|
+
Omitting `horizons` returns every horizon the series publishes, which is the
|
|
423
|
+
largest response this endpoint produces - name the ones you need, or page.
|
|
318
424
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
transformation has to resolve to exactly one series.
|
|
425
|
+
Neither method takes a transformation or a normalisation; a projection is not a
|
|
426
|
+
series to roll up or re-standardise.
|
|
322
427
|
|
|
323
428
|
For both services, if the transformation service is unavailable the
|
|
324
429
|
untransformed rows are returned with `transformation.status` set to `"Failure"`
|
|
@@ -596,6 +701,7 @@ Supported values:
|
|
|
596
701
|
```python
|
|
597
702
|
normalisation="zscore"
|
|
598
703
|
normalisation="percentile_rank"
|
|
704
|
+
normalisation="none" # positioning only - opts out of its default
|
|
599
705
|
```
|
|
600
706
|
|
|
601
707
|
### Supported Normalisation Periods
|
|
@@ -28,6 +28,7 @@ class AsyncBaseClient:
|
|
|
28
28
|
timeout: float = 600.0,
|
|
29
29
|
max_retries: int = 3,
|
|
30
30
|
cache_file: Optional[str] = None,
|
|
31
|
+
sdk_key: Optional[str] = None,
|
|
31
32
|
):
|
|
32
33
|
"""
|
|
33
34
|
Initialize async base client.
|
|
@@ -40,13 +41,19 @@ class AsyncBaseClient:
|
|
|
40
41
|
timeout: Request timeout in seconds.
|
|
41
42
|
max_retries: Maximum retry attempts.
|
|
42
43
|
cache_file: Path to token cache file.
|
|
44
|
+
sdk_key: SDK API key. If None, reads from VANDA_SDK_KEY environment variable.
|
|
43
45
|
|
|
44
46
|
Raises:
|
|
45
|
-
AuthError: If
|
|
47
|
+
AuthError: If no credential is provided.
|
|
46
48
|
"""
|
|
47
49
|
|
|
48
50
|
self.auth = AsyncAuth(
|
|
49
|
-
token=token,
|
|
51
|
+
token=token,
|
|
52
|
+
email=email,
|
|
53
|
+
password=password,
|
|
54
|
+
base_url=base_url,
|
|
55
|
+
cache_file=cache_file,
|
|
56
|
+
sdk_key=sdk_key,
|
|
50
57
|
)
|
|
51
58
|
self.base_url = base_url.rstrip("/")
|
|
52
59
|
self.timeout = timeout
|
|
@@ -85,10 +92,16 @@ class AsyncBaseClient:
|
|
|
85
92
|
|
|
86
93
|
return self._client
|
|
87
94
|
async def _refresh_client_headers(self) -> None:
|
|
88
|
-
"""Refresh client headers with updated
|
|
89
|
-
if self._client:
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
"""Refresh client headers with updated credentials."""
|
|
96
|
+
if not self._client:
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
if self.auth._auth_mode == "sdk_key":
|
|
100
|
+
self._client.headers.update(self.auth.get_headers())
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
token = await self.auth.get_token_async()
|
|
104
|
+
self._client.headers.update({"Authorization": f"Bearer {token}"})
|
|
92
105
|
|
|
93
106
|
async def _request(
|
|
94
107
|
self,
|
|
@@ -29,6 +29,7 @@ class BaseClient:
|
|
|
29
29
|
timeout: float,
|
|
30
30
|
max_retries: int,
|
|
31
31
|
cache_file: Optional[str],
|
|
32
|
+
sdk_key: Optional[str] = None,
|
|
32
33
|
):
|
|
33
34
|
"""
|
|
34
35
|
Initialize Base client.
|
|
@@ -41,9 +42,10 @@ class BaseClient:
|
|
|
41
42
|
timeout: Request timeout in seconds.
|
|
42
43
|
max_retries: Maximum retry attempts.
|
|
43
44
|
cache_file: Path to token cache file.
|
|
45
|
+
sdk_key: SDK API key. If None, reads from VANDA_SDK_KEY environment variable.
|
|
44
46
|
|
|
45
47
|
Raises:
|
|
46
|
-
AuthError: If
|
|
48
|
+
AuthError: If no credential is provided.
|
|
47
49
|
"""
|
|
48
50
|
|
|
49
51
|
self.auth = Auth(
|
|
@@ -52,6 +54,7 @@ class BaseClient:
|
|
|
52
54
|
password=password,
|
|
53
55
|
base_url=base_url,
|
|
54
56
|
cache_file=cache_file,
|
|
57
|
+
sdk_key=sdk_key,
|
|
55
58
|
)
|
|
56
59
|
|
|
57
60
|
self.base_url = base_url.rstrip("/")
|
|
@@ -80,7 +80,13 @@ class AsyncAuth(Auth):
|
|
|
80
80
|
|
|
81
81
|
Returns:
|
|
82
82
|
Valid access token.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
AuthError: In SDK key mode, where there is no bearer token.
|
|
83
86
|
"""
|
|
87
|
+
if self._auth_mode == "sdk_key":
|
|
88
|
+
raise AuthError("No bearer token in SDK key mode; the key is sent as a header.")
|
|
89
|
+
|
|
84
90
|
if self._auth_mode == "basic":
|
|
85
91
|
cached_token = self._token_cache.load()
|
|
86
92
|
if cached_token:
|
|
@@ -2,6 +2,8 @@ from typing import Optional
|
|
|
2
2
|
|
|
3
3
|
from vanda._base.async_base_client import AsyncBaseClient
|
|
4
4
|
from vanda.services.async_aggregates import AsyncAggregatesAPI
|
|
5
|
+
from vanda.services.async_flow import AsyncFlowAPI
|
|
6
|
+
from vanda.services.async_positioning import AsyncPositioningAPI
|
|
5
7
|
from vanda.services.async_series import AsyncSeriesAPI
|
|
6
8
|
from vanda.utils.deprecation import deprecated
|
|
7
9
|
|
|
@@ -17,6 +19,7 @@ class AsyncVandaClient(AsyncBaseClient):
|
|
|
17
19
|
timeout: float = 600.0,
|
|
18
20
|
max_retries: int = 3,
|
|
19
21
|
cache_file: Optional[str] = None,
|
|
22
|
+
sdk_key: Optional[str] = None,
|
|
20
23
|
):
|
|
21
24
|
|
|
22
25
|
super().__init__(
|
|
@@ -27,10 +30,13 @@ class AsyncVandaClient(AsyncBaseClient):
|
|
|
27
30
|
timeout,
|
|
28
31
|
max_retries,
|
|
29
32
|
cache_file,
|
|
33
|
+
sdk_key=sdk_key,
|
|
30
34
|
)
|
|
31
35
|
|
|
32
36
|
self.series = AsyncSeriesAPI(self)
|
|
33
37
|
self.aggregates = AsyncAggregatesAPI(self)
|
|
38
|
+
self.positioning = AsyncPositioningAPI(self)
|
|
39
|
+
self.flow = AsyncFlowAPI(self)
|
|
34
40
|
|
|
35
41
|
async def get_timeseries(self, *args, **kwargs):
|
|
36
42
|
deprecated("client.get_timeseries", "client.series.get_timeseries")
|