dataquery-sdk 0.0.7__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,996 @@
1
+ Metadata-Version: 2.4
2
+ Name: dataquery-sdk
3
+ Version: 0.0.7
4
+ Summary: Python SDK for DATAQUERY Data API - Query, download, and check availability of economic data files
5
+ Author-email: DATAQUERY SDK Team <support@dataquery.com>
6
+ Project-URL: Homepage, https://github.com/dataquery/dataquery-sdk
7
+ Project-URL: Bug Tracker, https://github.com/dataquery/dataquery-sdk/issues
8
+ Project-URL: Documentation, https://github.com/dataquery/dataquery-sdk/wiki
9
+ Project-URL: Source Code, https://github.com/dataquery/dataquery-sdk
10
+ Keywords: dataquery,data,api,economic,financial,download,async,oauth,rate-limiting,sdk
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Financial and Insurance Industry
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Office/Business :: Financial
24
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
25
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
26
+ Requires-Python: >=3.10
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: aiohttp<4.0.0,>=3.8.0
30
+ Requires-Dist: pydantic<3.0.0,>=2.0.0
31
+ Requires-Dist: structlog>=23.0.0
32
+ Requires-Dist: python-dotenv>=1.0.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
35
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
36
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
37
+ Requires-Dist: black>=23.0.0; extra == "dev"
38
+ Requires-Dist: flake8>=6.0.0; extra == "dev"
39
+ Requires-Dist: mypy>=1.5.0; extra == "dev"
40
+ Requires-Dist: isort>=5.12.0; extra == "dev"
41
+ Requires-Dist: pre-commit>=3.0.0; extra == "dev"
42
+ Requires-Dist: pip-audit>=2.7.0; extra == "dev"
43
+ Requires-Dist: build>=1.3.0; extra == "dev"
44
+ Requires-Dist: twine>=4.0.0; extra == "dev"
45
+ Provides-Extra: docs
46
+ Requires-Dist: mkdocs>=1.5.0; extra == "docs"
47
+ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
48
+ Requires-Dist: mkdocstrings[python]>=0.24.0; extra == "docs"
49
+ Requires-Dist: mike>=1.1.0; extra == "docs"
50
+ Provides-Extra: pandas
51
+ Requires-Dist: pandas>=2.0.0; extra == "pandas"
52
+ Provides-Extra: all
53
+ Requires-Dist: pytest>=7.0.0; extra == "all"
54
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "all"
55
+ Requires-Dist: pytest-cov>=4.0.0; extra == "all"
56
+ Requires-Dist: black>=23.0.0; extra == "all"
57
+ Requires-Dist: flake8>=6.0.0; extra == "all"
58
+ Requires-Dist: mypy>=1.5.0; extra == "all"
59
+ Requires-Dist: isort>=5.12.0; extra == "all"
60
+ Requires-Dist: pre-commit>=3.0.0; extra == "all"
61
+ Requires-Dist: mkdocs>=1.5.0; extra == "all"
62
+ Requires-Dist: mkdocs-material>=9.0.0; extra == "all"
63
+ Requires-Dist: mkdocstrings[python]>=0.24.0; extra == "all"
64
+ Requires-Dist: mike>=1.1.0; extra == "all"
65
+ Requires-Dist: pandas>=2.0.0; extra == "all"
66
+ Requires-Dist: pip-audit>=2.7.0; extra == "all"
67
+ Requires-Dist: build>=1.3.0; extra == "all"
68
+ Dynamic: license-file
69
+
70
+ # DataQuery SDK
71
+
72
+ Python SDK for J.P. Morgan DataQuery API - high-performance file downloads and time series data access.
73
+
74
+ **Key capabilities:**
75
+ - Parallel file downloads with progress tracking
76
+ - Time series queries (expressions, instruments, groups)
77
+ - OAuth 2.0 authentication with auto-refresh
78
+ - Connection pooling and rate limiting
79
+ - Pandas DataFrame integration
80
+
81
+ ## Table of Contents
82
+
83
+ - [Quick Start](#quick-start)
84
+ - [Installation](#installation)
85
+ - [Set Credentials](#set-credentials)
86
+ - [Execution Modes](#execution-modes-async-and-sync)
87
+ - [Download Files](#download-jpmaqs-files-simplest)
88
+ - [Query Time Series](#query-time-series-and-convert-to-dataframe)
89
+ - [Discover Datasets](#discover-available-datasets)
90
+ - [Common Use Cases](#common-use-cases)
91
+ - [File Downloads](#file-downloads)
92
+ - [Time Series Queries](#time-series-queries)
93
+ - [Advanced Usage](#advanced-usage)
94
+ - [API Reference](#api-reference)
95
+ - [Configuration](#configuration)
96
+ - [Error Handling](#error-handling)
97
+ - [Examples & Recipes](#examples--recipes)
98
+
99
+ ---
100
+
101
+ ## Quick Start
102
+
103
+ ### Installation
104
+
105
+ ```bash
106
+ pip install dataquery-sdk
107
+ ```
108
+
109
+ ### Set Credentials
110
+
111
+ ```bash
112
+ export DATAQUERY_CLIENT_ID="your_client_id"
113
+ export DATAQUERY_CLIENT_SECRET="your_client_secret"
114
+ ```
115
+
116
+ ### Execution Modes: Async and Sync
117
+
118
+ **Python scripts** can use either async or sync methods:
119
+ - **Sync methods** (simpler): Use `with DataQuery()` and call methods without `await`
120
+ - **Async methods** (requires `asyncio.run()`): Use `async with DataQuery()` and `await` method calls
121
+
122
+ **Jupyter notebooks** must use async methods with `await` (event loop already running)
123
+
124
+ > Performance is identical - sync methods are lightweight wrappers around async implementations.
125
+
126
+ ---
127
+
128
+ ### Download JPMAQS Files (Simplest)
129
+
130
+ **Jupyter Notebooks:**
131
+ ```python
132
+ from dataquery import DataQuery
133
+
134
+ # Download all files for January 2025
135
+ async with DataQuery() as dq:
136
+ await dq.run_group_download_async(
137
+ group_id="JPMAQS_GENERIC_RETURNS",
138
+ start_date="20250101",
139
+ end_date="20250131",
140
+ destination_dir="./data"
141
+ )
142
+ ```
143
+
144
+ **Python Scripts (Sync):**
145
+ ```python
146
+ from dataquery import DataQuery
147
+
148
+ # Download all files for January 2025
149
+ with DataQuery() as dq:
150
+ dq.run_group_download(
151
+ group_id="JPMAQS_GENERIC_RETURNS",
152
+ start_date="20250101",
153
+ end_date="20250131",
154
+ destination_dir="./data"
155
+ )
156
+ ```
157
+
158
+ **Python Scripts (Async):**
159
+ ```python
160
+ import asyncio
161
+ from dataquery import DataQuery
162
+
163
+ async def main():
164
+ async with DataQuery() as dq:
165
+ await dq.run_group_download_async(
166
+ group_id="JPMAQS_GENERIC_RETURNS",
167
+ start_date="20250101",
168
+ end_date="20250131",
169
+ destination_dir="./data"
170
+ )
171
+
172
+ asyncio.run(main())
173
+ ```
174
+
175
+ All JPMAQS Generic Returns files for January are now in `./data/`.
176
+
177
+ ### Query Time Series and Convert to DataFrame
178
+
179
+ **Jupyter Notebooks:**
180
+ ```python
181
+ from dataquery import DataQuery
182
+
183
+ # Get time series data and convert to pandas DataFrame
184
+ async with DataQuery() as dq:
185
+ result = await dq.get_expressions_time_series_async(
186
+ expressions=["DB(MTE,IRISH EUR 1.100 15-May-2029 LON,,IE00BH3SQ895,MIDPRC)"],
187
+ start_date="20240101",
188
+ end_date="20240131"
189
+ )
190
+
191
+ # Convert to DataFrame for analysis
192
+ df = dq.time_series_to_dataframe(result)
193
+ print(df.head())
194
+ ```
195
+
196
+ **Python Scripts:**
197
+ ```python
198
+ import asyncio
199
+ from dataquery import DataQuery
200
+
201
+ async def main():
202
+ async with DataQuery() as dq:
203
+ result = await dq.get_expressions_time_series_async(
204
+ expressions=["DB(MTE,IRISH EUR 1.100 15-May-2029 LON,,IE00BH3SQ895,MIDPRC)"],
205
+ start_date="20240101",
206
+ end_date="20240131"
207
+ )
208
+ df = dq.time_series_to_dataframe(result)
209
+ return df
210
+
211
+ df = asyncio.run(main())
212
+ print(df.head())
213
+ ```
214
+
215
+ ### Discover Available Datasets
216
+
217
+ **Jupyter Notebooks:**
218
+ ```python
219
+ from dataquery import DataQuery
220
+
221
+ # List all available groups and convert to DataFrame
222
+ async with DataQuery() as dq:
223
+ groups = await dq.list_groups_async(limit=100)
224
+
225
+ # Convert to DataFrame - generic method works with any response
226
+ df = dq.to_dataframe(groups)
227
+ print(df[['group_id', 'group_name', 'description']].head())
228
+ ```
229
+
230
+ **Python Scripts:**
231
+ ```python
232
+ from dataquery import DataQuery
233
+
234
+ # Sync version
235
+ with DataQuery() as dq:
236
+ groups = dq.list_groups(limit=100)
237
+
238
+ # Convert to DataFrame - generic method works with any response
239
+ df = dq.to_dataframe(groups)
240
+ print(df[['group_id', 'group_name', 'description']].head())
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Common Use Cases
246
+
247
+ ### File Downloads
248
+
249
+ #### Download All Files for Date Range (Recommended)
250
+
251
+ ```python
252
+ # Jupyter notebooks - use async/await
253
+ async with DataQuery() as dq:
254
+ results = await dq.run_group_download_async(
255
+ group_id="JPMAQS_GENERIC_RETURNS",
256
+ start_date="20250101",
257
+ end_date="20250131",
258
+ destination_dir="./data",
259
+ max_concurrent=3 # Download 3 files at once
260
+ )
261
+ print(f"Downloaded {results['successful']} files")
262
+ ```
263
+
264
+ ```python
265
+ # Python scripts - use sync methods
266
+ with DataQuery() as dq:
267
+ results = dq.run_group_download(
268
+ group_id="JPMAQS_GENERIC_RETURNS",
269
+ start_date="20250101",
270
+ end_date="20250131",
271
+ destination_dir="./data"
272
+ )
273
+ ```
274
+
275
+ ```python
276
+ # Python scripts - async requires asyncio.run()
277
+ import asyncio
278
+ from dataquery import DataQuery
279
+
280
+ async def download():
281
+ async with DataQuery() as dq:
282
+ return await dq.run_group_download_async(
283
+ group_id="JPMAQS_GENERIC_RETURNS",
284
+ start_date="20250101",
285
+ end_date="20250131",
286
+ destination_dir="./data",
287
+ max_concurrent=3
288
+ )
289
+
290
+ results = asyncio.run(download())
291
+ ```
292
+
293
+ **When to use:** You want all files for a file group within a date range.
294
+
295
+ #### Download Single File
296
+
297
+ ```python
298
+ async with DataQuery() as dq:
299
+ result = await dq.download_file_async(
300
+ file_group_id="JPMAQS_GENERIC_RETURNS",
301
+ file_datetime="20250115", # Specific date
302
+ destination_path="./downloads"
303
+ )
304
+ print(f"Downloaded: {result.local_path}")
305
+ ```
306
+
307
+ **When to use:** You need just one specific file.
308
+
309
+ ---
310
+
311
+ ### Time Series Queries
312
+
313
+ #### Query by Expression (Fastest for known data)
314
+
315
+ ```python
316
+ async with DataQuery() as dq:
317
+ result = await dq.get_expressions_time_series_async(
318
+ expressions=[
319
+ "DB(MTE,IRISH EUR 1.100 15-May-2029 LON,,IE00BH3SQ895,MIDPRC)",
320
+ "DB(MTE,IRISH EUR 2.400 15-May-2030 LON,,IE00BJ38CR43,MIDPRC)"
321
+ ],
322
+ start_date="20240101",
323
+ end_date="20240131"
324
+ )
325
+ ```
326
+
327
+ **When to use:** You know the exact expression syntax (from DataQuery Web or documentation).
328
+
329
+ #### Query by Instrument ID
330
+
331
+ ```python
332
+ async with DataQuery() as dq:
333
+ result = await dq.get_instrument_time_series_async(
334
+ instruments=[
335
+ "477f892d3cc8745578887a92d35c2a3e-DQGNMTBNDFIM",
336
+ "67e1bfca56bdee7a0fd5fe4c62a1e0dc-DQGNMTBNDFIM"
337
+ ],
338
+ attributes=["MIDPRC", "REPO_1M"],
339
+ start_date="20240101",
340
+ end_date="20240131"
341
+ )
342
+ ```
343
+
344
+ **When to use:** You have instrument IDs from a previous search or list.
345
+
346
+ #### Query Entire Group with Filter
347
+
348
+ ```python
349
+ async with DataQuery() as dq:
350
+ result = await dq.get_group_time_series_async(
351
+ group_id="FI_GO_BO_EA",
352
+ attributes=["MIDPRC", "REPO_1M"],
353
+ filter="country(IRL)", # Ireland bonds only
354
+ start_date="20240101",
355
+ end_date="20240131"
356
+ )
357
+ ```
358
+
359
+ **When to use:** You want all instruments in a dataset, optionally filtered by country/currency.
360
+
361
+ #### Search for Instruments
362
+
363
+ ```python
364
+ async with DataQuery() as dq:
365
+ # Find instruments matching keywords
366
+ results = await dq.search_instruments_async(
367
+ group_id="FI_GO_BO_EA",
368
+ keywords="irish"
369
+ )
370
+ print(f"Found {results.items} instruments")
371
+
372
+ # Use the results to query time series
373
+ instrument_ids = [inst.instrument_id for inst in results.instruments]
374
+ data = await dq.get_instrument_time_series_async(
375
+ instruments=instrument_ids[:5], # First 5 results
376
+ attributes=["MIDPRC"],
377
+ start_date="20240101",
378
+ end_date="20240131"
379
+ )
380
+ ```
381
+
382
+ **When to use:** You're discovering data and don't know instrument IDs yet.
383
+
384
+ ---
385
+
386
+ ## Advanced Usage
387
+
388
+ ### High-Performance File Downloads
389
+
390
+ Use parallel HTTP range requests to download large files faster:
391
+
392
+ ```python
393
+ async with DataQuery() as dq:
394
+ result = await dq.download_file_async(
395
+ file_group_id="JPMAQS_GENERIC_RETURNS",
396
+ file_datetime="20250115",
397
+ destination_path="./downloads",
398
+ num_parts=8, # Split into 8 parallel chunks
399
+ progress_callback=lambda fid, p: print(f"{p.bytes_downloaded:,} bytes")
400
+ )
401
+ ```
402
+
403
+ **Performance tuning:**
404
+ ```python
405
+ # Download multiple files concurrently
406
+ await dq.run_group_download_async(
407
+ group_id="JPMAQS_GENERIC_RETURNS",
408
+ start_date="20250101",
409
+ end_date="20250131",
410
+ destination_dir="./data",
411
+ max_concurrent=5, # 5 files at once
412
+ num_parts=4 # Each file in 4 chunks = 20 total parallel requests
413
+ )
414
+ ```
415
+
416
+ **Optimal settings:**
417
+ - `num_parts`: 2-8 (higher = faster on good connections)
418
+ - `max_concurrent`: 3-5 (too high may hit rate limits)
419
+
420
+ **Note:** Jupyter notebooks use `await` directly. Python scripts use sync methods or wrap async code in `asyncio.run()`.
421
+
422
+ ### Grid Data (Premium Datasets)
423
+
424
+ ```python
425
+ async with DataQuery() as dq:
426
+ # Query grid data by expression
427
+ grid = await dq.get_grid_data_async(
428
+ expr="DBGRID(FXOVOL,FXO,CW,AUD,USD)",
429
+ date="20240216"
430
+ )
431
+ ```
432
+
433
+ ### DataFrame Conversion
434
+
435
+ The SDK provides a universal `to_dataframe()` method that automatically handles any API response type:
436
+
437
+ ```python
438
+ async with DataQuery() as dq:
439
+ # Generic to_dataframe() works with all response types
440
+ groups = await dq.list_groups_async(limit=100)
441
+ groups_df = dq.to_dataframe(groups)
442
+
443
+ # Time series
444
+ ts_data = await dq.get_expressions_time_series_async(
445
+ expressions=["DB(MTE,IRISH EUR 1.100 15-May-2029 LON,,IE00BH3SQ895,MIDPRC)"],
446
+ start_date="20240101",
447
+ end_date="20240131"
448
+ )
449
+ ts_df = dq.to_dataframe(ts_data)
450
+
451
+ # Instruments
452
+ instruments = await dq.list_instruments_async(group_id="FI_GO_BO_EA")
453
+ instruments_df = dq.to_dataframe(instruments.instruments)
454
+
455
+ # Files
456
+ files = await dq.list_available_files_async(
457
+ group_id="JPMAQS_GENERIC_RETURNS",
458
+ start_date="20250101",
459
+ end_date="20250131"
460
+ )
461
+ files_df = dq.to_dataframe(files)
462
+ ```
463
+
464
+ **Advanced options:**
465
+ ```python
466
+ # Include metadata fields
467
+ df = dq.to_dataframe(groups, include_metadata=True)
468
+
469
+ # Parse specific columns as dates
470
+ df = dq.to_dataframe(files, date_columns=['last_modified', 'created_date'])
471
+
472
+ # Convert specific columns to numeric
473
+ df = dq.to_dataframe(files, numeric_columns=['file_size'])
474
+
475
+ # Apply custom transformations
476
+ df = dq.to_dataframe(
477
+ data,
478
+ custom_transformations={
479
+ 'price': lambda x: float(x) if x else 0.0
480
+ }
481
+ )
482
+ ```
483
+
484
+ **Type-specific convenience methods** (same functionality as generic method):
485
+ - `groups_to_dataframe(groups, include_metadata=False)`
486
+ - `time_series_to_dataframe(time_series, include_metadata=False)`
487
+ - `instruments_to_dataframe(instruments, include_metadata=False)`
488
+ - `files_to_dataframe(files, include_metadata=False)`
489
+
490
+ ---
491
+
492
+ ## API Reference
493
+
494
+ ### File Download Methods
495
+
496
+ #### `download_file_async()`
497
+
498
+ Download a single file with optional parallel chunks.
499
+
500
+ ```python
501
+ result = await dq.download_file_async(
502
+ file_group_id: str, # Required: File group identifier
503
+ file_datetime: str, # Required: File date (YYYY-MM-DD)
504
+ destination_path: Path, # Required: Where to save
505
+ num_parts: int = 1, # Optional: Parallel chunks (1-10)
506
+ part_size_mb: int = 100, # Optional: Chunk size in MB
507
+ progress_callback: Callable = None, # Optional: Progress updates
508
+ overwrite: bool = False # Optional: Overwrite existing
509
+ ) -> DownloadResult
510
+ ```
511
+
512
+ **Returns:** `DownloadResult` with status, file size, download time, and local path.
513
+
514
+ #### `download_files_by_date_range_async()`
515
+
516
+ Download all files in a date range.
517
+
518
+ ```python
519
+ results = await dq.download_files_by_date_range_async(
520
+ file_group_id: str, # Required: File group identifier
521
+ start_date: str, # Required: Start date (YYYY-MM-DD)
522
+ end_date: str, # Required: End date (YYYY-MM-DD)
523
+ destination_path: Path, # Required: Where to save
524
+ num_parts: int = 1, # Optional: Parallel chunks per file
525
+ max_concurrent: int = 3, # Optional: Concurrent downloads
526
+ progress_callback: Callable = None # Optional: Progress updates
527
+ ) -> List[DownloadResult]
528
+ ```
529
+
530
+ **Returns:** List of `DownloadResult` for each file.
531
+
532
+ #### `list_available_files_async()`
533
+
534
+ Check what files are available for a group and date range.
535
+
536
+ ```python
537
+ files = await dq.list_available_files_async(
538
+ group_id: str, # Required: Group identifier
539
+ start_date: str, # Required: Start date (YYYY-MM-DD)
540
+ end_date: str # Required: End date (YYYY-MM-DD)
541
+ ) -> List[dict]
542
+ ```
543
+
544
+ **Returns:** List of dictionaries with `file_group_id`, `file_datetime`, and `file_size`.
545
+
546
+ ---
547
+
548
+ ### Time Series Query Methods
549
+
550
+ #### `get_expressions_time_series_async()`
551
+
552
+ Query using DataQuery expressions (fastest when you know the expression).
553
+
554
+ **Minimal usage:**
555
+ ```python
556
+ result = await dq.get_expressions_time_series_async(
557
+ expressions=["DB(MTE,IRISH EUR 1.100 15-May-2029 LON,,IE00BH3SQ895,MIDPRC)"],
558
+ start_date="20240101",
559
+ end_date="20240131"
560
+ )
561
+ ```
562
+
563
+ **Complete signature:**
564
+ ```python
565
+ result = await dq.get_expressions_time_series_async(
566
+ expressions: List[str], # Required: List of expressions (max 20)
567
+ format: str = "JSON", # Optional: Response format
568
+ start_date: str = None, # Optional: YYYYMMDD or TODAY-1M
569
+ end_date: str = None, # Optional: YYYYMMDD or TODAY
570
+ calendar: str = "CAL_USBANK", # Optional: Calendar convention
571
+ frequency: str = "FREQ_DAY", # Optional: FREQ_DAY, FREQ_WEEK, etc.
572
+ conversion: str = "CONV_LASTBUS_ABS", # Optional: Conversion method
573
+ nan_treatment: str = "NA_NOTHING", # Optional: NA_NOTHING, NA_LAST, etc.
574
+ data: str = "REFERENCE_DATA", # Optional: Data domain
575
+ page: str = None # Optional: Pagination token
576
+ ) -> TimeSeriesResponse
577
+ ```
578
+
579
+ **Recommended parameters:**
580
+ - `calendar="CAL_WEEKDAYS"` - For international coverage
581
+ - `data="ALL"` - To include market data
582
+
583
+ ---
584
+
585
+ #### `get_instrument_time_series_async()`
586
+
587
+ Query specific instruments by their IDs.
588
+
589
+ **Minimal usage:**
590
+ ```python
591
+ result = await dq.get_instrument_time_series_async(
592
+ instruments=["477f892d3cc8745578887a92d35c2a3e-DQGNMTBNDFIM"],
593
+ attributes=["MIDPRC"],
594
+ start_date="20240101",
595
+ end_date="20240131"
596
+ )
597
+ ```
598
+
599
+ **Complete signature:**
600
+ ```python
601
+ result = await dq.get_instrument_time_series_async(
602
+ instruments: List[str], # Required: List of instrument IDs (max 20)
603
+ attributes: List[str], # Required: List of attributes
604
+ data: str = "REFERENCE_DATA", # Optional: Data domain
605
+ format: str = "JSON", # Optional: Response format
606
+ start_date: str = None, # Optional: YYYYMMDD or TODAY-2W
607
+ end_date: str = None, # Optional: YYYYMMDD or TODAY
608
+ calendar: str = "CAL_USBANK", # Optional: Calendar convention
609
+ frequency: str = "FREQ_DAY", # Optional: Frequency
610
+ conversion: str = "CONV_LASTBUS_ABS", # Optional: Conversion
611
+ nan_treatment: str = "NA_NOTHING", # Optional: Missing data
612
+ page: str = None # Optional: Pagination token
613
+ ) -> TimeSeriesResponse
614
+ ```
615
+
616
+ **When to use:** You have instrument IDs from search or list operations.
617
+
618
+ ---
619
+
620
+ #### `get_group_time_series_async()`
621
+
622
+ Query all instruments in a group, optionally with filters.
623
+
624
+ **Minimal usage:**
625
+ ```python
626
+ result = await dq.get_group_time_series_async(
627
+ group_id="FI_GO_BO_EA",
628
+ attributes=["MIDPRC"],
629
+ start_date="20240101",
630
+ end_date="20240131"
631
+ )
632
+ ```
633
+
634
+ **With filter:**
635
+ ```python
636
+ result = await dq.get_group_time_series_async(
637
+ group_id="FI_GO_BO_EA",
638
+ attributes=["MIDPRC", "REPO_1M"],
639
+ filter="country(IRL)", # Ireland bonds only
640
+ start_date="20240101",
641
+ end_date="20240131",
642
+ calendar="CAL_WEEKDAYS"
643
+ )
644
+ ```
645
+
646
+ **Complete signature:**
647
+ ```python
648
+ result = await dq.get_group_time_series_async(
649
+ group_id: str, # Required: Group identifier
650
+ attributes: List[str], # Required: List of attributes
651
+ filter: str = None, # Optional: Filter expression
652
+ data: str = "REFERENCE_DATA", # Optional: Data domain
653
+ format: str = "JSON", # Optional: Response format
654
+ start_date: str = None, # Optional: YYYYMMDD or TODAY-1M
655
+ end_date: str = None, # Optional: YYYYMMDD or TODAY
656
+ calendar: str = "CAL_USBANK", # Optional: Calendar
657
+ frequency: str = "FREQ_DAY", # Optional: Frequency
658
+ conversion: str = "CONV_LASTBUS_ABS", # Optional: Conversion
659
+ nan_treatment: str = "NA_NOTHING", # Optional: Missing data
660
+ page: str = None # Optional: Pagination
661
+ ) -> TimeSeriesResponse
662
+ ```
663
+
664
+ **Available filters:** Check with `get_group_filters_async()` - typically `country(CODE)` or `currency(CODE)`.
665
+
666
+ ---
667
+
668
+ ### Discovery Methods
669
+
670
+ #### `search_instruments_async()`
671
+
672
+ Search for instruments within a group by keywords.
673
+
674
+ ```python
675
+ results = await dq.search_instruments_async(
676
+ group_id: str, # Required: Group to search
677
+ keywords: str, # Required: Search terms
678
+ page: str = None # Optional: Pagination token
679
+ ) -> InstrumentsResponse
680
+ ```
681
+
682
+ #### `list_instruments_async()`
683
+
684
+ List all instruments in a group (paginated).
685
+
686
+ ```python
687
+ instruments = await dq.list_instruments_async(
688
+ group_id: str, # Required: Group identifier
689
+ instrument_id: str = None, # Optional: Specific instrument
690
+ page: str = None # Optional: Pagination token
691
+ ) -> InstrumentsResponse
692
+ ```
693
+
694
+ #### `list_groups_async()`
695
+
696
+ List available data groups/datasets.
697
+
698
+ ```python
699
+ groups = await dq.list_groups_async(
700
+ limit: int = 100 # Optional: Results per page (100-1000)
701
+ ) -> List[Group]
702
+ ```
703
+
704
+ **Note:** Minimum limit is 100 per API specification.
705
+
706
+ #### `get_group_attributes_async()`
707
+
708
+ Get available attributes for a group.
709
+
710
+ ```python
711
+ attrs = await dq.get_group_attributes_async(
712
+ group_id: str, # Required: Group identifier
713
+ instrument_id: str = None, # Optional: Specific instrument
714
+ page: str = None # Optional: Pagination token
715
+ ) -> AttributesResponse
716
+ ```
717
+
718
+ #### `get_group_filters_async()`
719
+
720
+ Get available filter dimensions for a group.
721
+
722
+ ```python
723
+ filters = await dq.get_group_filters_async(
724
+ group_id: str, # Required: Group identifier
725
+ page: str = None # Optional: Pagination token
726
+ ) -> FiltersResponse
727
+ ```
728
+
729
+ ---
730
+
731
+ ## Configuration
732
+
733
+ ### Environment Variables
734
+
735
+ ```bash
736
+ # Required
737
+ export DATAQUERY_CLIENT_ID="your_client_id"
738
+ export DATAQUERY_CLIENT_SECRET="your_client_secret"
739
+
740
+ # Optional - API endpoints (defaults shown)
741
+ export DATAQUERY_BASE_URL="https://api-developer.jpmorgan.com"
742
+ export DATAQUERY_CONTEXT_PATH="/research/dataquery-authe/api/v2"
743
+ export DATAQUERY_FILES_BASE_URL="https://api-strm-gw01.jpmchase.com"
744
+
745
+ # Optional - OAuth
746
+ export DATAQUERY_OAUTH_TOKEN_URL="https://authe.jpmorgan.com/as/token.oauth2"
747
+ export DATAQUERY_OAUTH_RESOURCE_ID="JPMC:URI:RS-06785-DataQueryExternalApi-PROD"
748
+
749
+ # Optional - Performance
750
+ export DATAQUERY_MAX_RETRIES="3"
751
+ export DATAQUERY_TIMEOUT="60"
752
+ export DATAQUERY_RATE_LIMIT_RPM="300" # Requests per minute
753
+ ```
754
+
755
+ ### Programmatic Configuration
756
+
757
+ ```python
758
+ from dataquery import DataQuery, ClientConfig
759
+
760
+ config = ClientConfig(
761
+ client_id="your_client_id",
762
+ client_secret="your_client_secret",
763
+ max_retries=3,
764
+ timeout=60.0,
765
+ rate_limit_rpm=300
766
+ )
767
+
768
+ async with DataQuery(config=config) as dq:
769
+ # Your code here
770
+ pass
771
+ ```
772
+
773
+ ### Performance Tuning
774
+
775
+ **For file downloads:**
776
+ ```python
777
+ # Optimal settings for most use cases
778
+ num_parts=4 # 2-8 parallel chunks per file
779
+ part_size_mb=50 # 25-100 MB chunks
780
+ max_concurrent=3 # 1-5 concurrent file downloads
781
+ ```
782
+
783
+ **For time series:**
784
+ ```python
785
+ # Recommended date range
786
+ start_date="TODAY-1Y" # Max 1 year per request
787
+ calendar="CAL_WEEKDAYS" # International coverage
788
+ data="ALL" # Include market data
789
+ ```
790
+
791
+ ---
792
+
793
+ ## Date and Calendar Reference
794
+
795
+ ### Date Formats
796
+
797
+ **Absolute dates:**
798
+ ```python
799
+ start_date="20240101" # YYYYMMDD format
800
+ end_date="20241231"
801
+ ```
802
+
803
+ **Relative dates:**
804
+ ```python
805
+ start_date="TODAY" # Today
806
+ start_date="TODAY-1D" # Yesterday
807
+ start_date="TODAY-1W" # 1 week ago
808
+ start_date="TODAY-1M" # 1 month ago
809
+ start_date="TODAY-1Y" # 1 year ago
810
+ ```
811
+
812
+ ### Calendar Conventions
813
+
814
+ Choose based on your use case:
815
+
816
+ | Calendar | Description | Use Case |
817
+ |----------|-------------|----------|
818
+ | `CAL_WEEKDAYS` | Monday-Friday (recommended for international) | Multi-country data |
819
+ | `CAL_USBANK` | US banking days (default) | US-only data |
820
+ | `CAL_WEEKDAY_NOHOLIDAY` | All weekdays | Generic business days |
821
+ | `CAL_DEFAULT` | Calendar day | Include weekends |
822
+
823
+ **30+ calendars supported.** See API documentation for complete list.
824
+
825
+ ### Frequency Conventions
826
+
827
+ | Frequency | Description |
828
+ |-----------|-------------|
829
+ | `FREQ_DAY` | Daily (default) |
830
+ | `FREQ_WEEK` | Weekly |
831
+ | `FREQ_MONTH` | Monthly |
832
+ | `FREQ_QUARTER` | Quarterly |
833
+ | `FREQ_YEAR` | Annual |
834
+
835
+ ---
836
+
837
+ ## Error Handling
838
+
839
+ ### Common Patterns
840
+
841
+ ```python
842
+ from dataquery import DataQuery, DataQueryError
843
+
844
+ async def safe_query():
845
+ try:
846
+ async with DataQuery() as dq:
847
+ result = await dq.get_expressions_time_series_async(
848
+ expressions=["DB(...)"],
849
+ start_date="20240101",
850
+ end_date="20240131"
851
+ )
852
+ return result
853
+ except DataQueryError as e:
854
+ print(f"DataQuery API error: {e}")
855
+ except Exception as e:
856
+ print(f"Unexpected error: {e}")
857
+ ```
858
+
859
+ ### Check Health
860
+
861
+ ```python
862
+ async with DataQuery() as dq:
863
+ if await dq.health_check_async():
864
+ print("API is healthy")
865
+ else:
866
+ print("API health check failed")
867
+ ```
868
+
869
+ ### Monitor Statistics
870
+
871
+ ```python
872
+ async with DataQuery() as dq:
873
+ # Make some requests...
874
+ await dq.list_groups_async(limit=100)
875
+
876
+ # Get statistics
877
+ stats = dq.get_stats()
878
+ print(f"Total requests: {stats.get('total_requests', 0)}")
879
+ print(f"Success rate: {stats.get('success_rate', 0):.1f}%")
880
+ ```
881
+
882
+ ---
883
+
884
+ ## Examples & Recipes
885
+
886
+ ### Download and Process JPMAQS Data
887
+
888
+ ```python
889
+ import asyncio
890
+ from dataquery import DataQuery
891
+ from pathlib import Path
892
+
893
+ async def download_jpmaqs_month():
894
+ """Download and process a month of JPMAQS data"""
895
+ async with DataQuery() as dq:
896
+ results = await dq.download_files_by_date_range_async(
897
+ file_group_id="JPMAQS",
898
+ start_date="2025-01-01",
899
+ end_date="2025-01-31",
900
+ destination_path="./jpmaqs_data",
901
+ num_parts=4
902
+ )
903
+
904
+ for result in results:
905
+ if result.status.value == "completed":
906
+ print(f"Downloaded {result.local_path.name}: {result.file_size:,} bytes")
907
+ # Process the file here
908
+ else:
909
+ print(f"Failed {result.file_id}: {result.error_message}")
910
+
911
+ asyncio.run(download_jpmaqs_month())
912
+ ```
913
+
914
+ ### Build Time Series for Portfolio
915
+
916
+ ```python
917
+ async def get_portfolio_data(instrument_ids: list):
918
+ """Get time series data for a portfolio of instruments"""
919
+ async with DataQuery() as dq:
920
+ # Get 1 year of daily data
921
+ ts_data = await dq.get_instrument_time_series_async(
922
+ instruments=instrument_ids,
923
+ attributes=["MIDPRC", "REPO_1M"],
924
+ start_date="TODAY-1Y",
925
+ end_date="TODAY",
926
+ calendar="CAL_WEEKDAYS",
927
+ data="ALL"
928
+ )
929
+
930
+ # Convert to DataFrame for analysis
931
+ df = dq.time_series_to_dataframe(ts_data)
932
+ return df
933
+
934
+ # Usage
935
+ portfolio_ids = [
936
+ "477f892d3cc8745578887a92d35c2a3e-DQGNMTBNDFIM",
937
+ "67e1bfca56bdee7a0fd5fe4c62a1e0dc-DQGNMTBNDFIM"
938
+ ]
939
+ df = asyncio.run(get_portfolio_data(portfolio_ids))
940
+ print(df.head())
941
+ ```
942
+
943
+ ### Discover and Query New Dataset
944
+
945
+ ```python
946
+ async def explore_dataset(group_id: str, keywords: str):
947
+ """Explore a dataset and get sample data"""
948
+ async with DataQuery() as dq:
949
+ # Search for instruments
950
+ search_results = await dq.search_instruments_async(
951
+ group_id=group_id,
952
+ keywords=keywords
953
+ )
954
+ print(f"Found {search_results.items} instruments matching '{keywords}'")
955
+
956
+ # Get first 5 instrument IDs
957
+ instrument_ids = [inst.instrument_id for inst in search_results.instruments[:5]]
958
+
959
+ # Get available attributes
960
+ attrs = await dq.get_group_attributes_async(group_id=group_id)
961
+ print(f"Available attributes: {len(attrs.instruments)} instruments")
962
+
963
+ # Query sample data
964
+ ts_data = await dq.get_instrument_time_series_async(
965
+ instruments=instrument_ids,
966
+ attributes=["MIDPRC"], # Common attribute
967
+ start_date="TODAY-1W",
968
+ end_date="TODAY",
969
+ calendar="CAL_WEEKDAYS"
970
+ )
971
+
972
+ return ts_data
973
+
974
+ # Usage
975
+ data = asyncio.run(explore_dataset("FI_GO_BO_EA", "irish"))
976
+ ```
977
+
978
+ ---
979
+
980
+ ## Requirements
981
+
982
+ - **Python 3.10+** (Python 3.11+ recommended for better performance)
983
+ - **Dependencies:** aiohttp, pydantic, structlog (auto-installed)
984
+ - **Optional:** pandas (for DataFrame conversion)
985
+
986
+ ---
987
+
988
+ ## Support
989
+
990
+ For issues, questions, or feature requests, contact DataQuery support at DataQuery_Support@jpmorgan.com
991
+
992
+ ---
993
+
994
+ ## License
995
+
996
+ See LICENSE file for details.