agualphacn 0.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 AGuAlpha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include agualpha *.py
@@ -0,0 +1,483 @@
1
+ Metadata-Version: 2.4
2
+ Name: agualphacn
3
+ Version: 0.1.0
4
+ Summary: Python client for AGuAlpha Investment Platform API
5
+ Home-page: https://github.com/agualpha/agualpha-python
6
+ Author: AGuAlpha
7
+ Author-email: contact@agualpha.com
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Office/Business :: Financial
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: requests>=2.28.0
21
+ Provides-Extra: async
22
+ Requires-Dist: aiohttp>=3.8.0; extra == "async"
23
+ Provides-Extra: pandas
24
+ Requires-Dist: pandas>=1.5.0; extra == "pandas"
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
27
+ Requires-Dist: black>=22.0.0; extra == "dev"
28
+ Requires-Dist: mypy>=0.950; extra == "dev"
29
+ Dynamic: author
30
+ Dynamic: author-email
31
+ Dynamic: classifier
32
+ Dynamic: description
33
+ Dynamic: description-content-type
34
+ Dynamic: home-page
35
+ Dynamic: license-file
36
+ Dynamic: provides-extra
37
+ Dynamic: requires-dist
38
+ Dynamic: requires-python
39
+ Dynamic: summary
40
+
41
+ # AGuAlpha Python Client
42
+
43
+ Official Python library for accessing AGuAlpha Investment Platform data.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install agualphacn
49
+ ```
50
+
51
+ For async support:
52
+ ```bash
53
+ pip install agualphacn[async]
54
+ ```
55
+
56
+ For pandas integration:
57
+ ```bash
58
+ pip install agualphacn[pandas]
59
+ ```
60
+
61
+ ## Quick Start
62
+
63
+ ### Synchronous Usage
64
+
65
+ ```python
66
+ from agualphacn import AGuAlphaClient
67
+
68
+ # Initialize client
69
+ client = AGuAlphaClient(api_key="sk-your-api-key-here")
70
+
71
+ # Get positions data
72
+ positions = client.get_positions()
73
+ print(f"Total positions: {positions.total}")
74
+
75
+ for position in positions.data:
76
+ print(f"Date: {position.date}, Outstanding: {position.outstanding}")
77
+
78
+ # Get ideas data
79
+ ideas = client.get_ideas(status="active")
80
+ print(f"Total active ideas: {ideas.total}")
81
+
82
+ for idea in ideas.data:
83
+ print(f"Symbol: {idea.ticker_symbol}, Status: {idea.status}")
84
+
85
+ # Close the connection
86
+ client.close()
87
+ ```
88
+
89
+ ### Using Context Manager
90
+
91
+ ```python
92
+ from agualphacn import AGuAlphaClient
93
+
94
+ with AGuAlphaClient(api_key="sk-your-api-key-here") as client:
95
+ positions = client.get_positions(
96
+ start_date="2024-01-01",
97
+ end_date="2024-12-31"
98
+ )
99
+ print(f"Found {positions.total} positions")
100
+ ```
101
+
102
+ ### Asynchronous Usage
103
+
104
+ ```python
105
+ import asyncio
106
+ from agualphacn import AGuAlphaAsyncClient
107
+
108
+ async def main():
109
+ async with AGuAlphaAsyncClient(api_key="sk-your-api-key-here") as client:
110
+ # Fetch data concurrently
111
+ positions, ideas = await asyncio.gather(
112
+ client.get_positions(),
113
+ client.get_ideas(status="active")
114
+ )
115
+
116
+ print(f"Positions: {positions.total}, Ideas: {ideas.total}")
117
+
118
+ asyncio.run(main())
119
+ ```
120
+
121
+ ### Pandas Integration
122
+
123
+ ```python
124
+ from agualphacn import AGuAlphaClient
125
+ from agualphacn.utils import positions_to_dataframe, export_to_csv
126
+
127
+ client = AGuAlphaClient(api_key="sk-your-api-key-here")
128
+
129
+ # Get positions and convert to DataFrame
130
+ positions_response = client.get_positions()
131
+ df = positions_to_dataframe(positions_response.data)
132
+
133
+ # Export to CSV
134
+ export_to_csv(positions_response.data, "positions.csv")
135
+ ```
136
+
137
+ ### CSI Weights
138
+
139
+ Read `csi_weights` rows from the `prices` database filtered by index.
140
+ The `index` parameter is **required** and must be one of these semantic names:
141
+
142
+ | Value | Index |
143
+ |------------|--------------------------------|
144
+ | `CSI300` | CSI 300 |
145
+ | `CSI500` | CSI 500 |
146
+ | `CSI1000` | CSI 1000 |
147
+ | `CSI2000` | CSI 2000 |
148
+
149
+ Numeric codes (e.g. `000300`) are **rejected** with HTTP 400.
150
+
151
+ Three mutually-exclusive date filters are supported:
152
+
153
+ ```python
154
+ from agualphacn import AGuAlphaClient
155
+
156
+ with AGuAlphaClient(api_key="sk-your-api-key-here") as client:
157
+ # 1) Single day
158
+ df = client.get_csi_weights_dataframe(index="CSI300", date="2026-07-15")
159
+
160
+ # 2) Inclusive range
161
+ df = client.get_csi_weights_dataframe(
162
+ index="CSI300",
163
+ start_date="2026-07-01",
164
+ end_date="2026-07-15",
165
+ )
166
+
167
+ # 3) Rolling window — last 30 days up to today
168
+ df = client.get_csi_weights_dataframe(index="CSI300", days=30)
169
+
170
+ print(df.head())
171
+ print(f"Shape: {df.shape}")
172
+ ```
173
+
174
+ If you don't need pandas, drop the `_dataframe` suffix to get a list of dicts:
175
+
176
+ ```python
177
+ with AGuAlphaClient(api_key="sk-your-api-key-here") as client:
178
+ rows = client.get_csi_weights(index="CSI300", days=30)
179
+ print(f"Total records: {len(rows)}")
180
+ ```
181
+
182
+ ### Adjusted Close Prices
183
+
184
+ Read `adj_close` rows from the `prices` database. `membership` and a date
185
+ filter are **both required**. The membership selects a universe of tickers;
186
+ choose from:
187
+
188
+ | Value | Universe |
189
+ |------------|-----------------------------------------------------------|
190
+ | `csi300` | CSI 300 constituents |
191
+ | `csi500` | CSI 500 constituents |
192
+ | `csi1000` | CSI 1000 constituents |
193
+ | `csi2000` | CSI 2000 constituents |
194
+ | `csi800` | CSI 800 universe |
195
+ | `csi1800` | CSI 1800 universe |
196
+ | `csi2800` | CSI 2800 universe |
197
+ | `x-csi` | Cross-listed / uncategorized (NULL membership) |
198
+ | `a-shares` | All A-shares (tickers ending `.SZ`, `.SH`, `.BJ`) |
199
+ | `hk` | Hong Kong listed (tickers ending `.HK`) |
200
+ | `etf` | ETFs |
201
+
202
+ ```python
203
+ from agualphacn import AGuAlphaClient
204
+
205
+ with AGuAlphaClient(api_key="sk-your-api-key-here") as client:
206
+ # Single day for CSI 300 constituents
207
+ df = client.get_adj_close_dataframe(membership="csi300", date="2026-07-15")
208
+
209
+ # Inclusive range for HK tickers
210
+ df = client.get_adj_close_dataframe(
211
+ membership="hk",
212
+ start_date="2026-07-01",
213
+ end_date="2026-07-15",
214
+ )
215
+ print(df.head())
216
+ ```
217
+
218
+ ### P-CIES (HK / CN)
219
+
220
+ Read `p_cies` rows from the `prices` database filtered by zone. `zone` is
221
+ **required** and must be exactly `"hk"` or `"cn"`.
222
+
223
+ ```python
224
+ from agualphacn import AGuAlphaClient
225
+
226
+ with AGuAlphaClient(api_key="sk-your-api-key-here") as client:
227
+ df_hk = client.get_p_cies_dataframe(zone="hk")
228
+ df_cn = client.get_p_cies_dataframe(zone="cn")
229
+ ```
230
+
231
+ ### SP Rolling Index (Commodities)
232
+
233
+ Read commodity rolling-index data from the `sp_rolling_index` table (prices
234
+ database). At least **one** filter is required: a product and/or a date filter.
235
+
236
+ ```python
237
+ from agualphacn import AGuAlphaClient
238
+
239
+ with AGuAlphaClient(api_key="sk-your-api-key-here") as client:
240
+ # List available product codes first (e.g. ["CU", "RB", ...])
241
+ products = client.get_sp_rolling_index_products()
242
+
243
+ # All rows for one product
244
+ rows = client.get_sp_rolling_index(product="CU")
245
+
246
+ # Multiple products at once (no count limit)
247
+ rows = client.get_sp_rolling_index(products=["CU", "RB"])
248
+
249
+ # All products in a date range (no product = all commodities)
250
+ rows = client.get_sp_rolling_index(start_date="2025-06-01", end_date="2025-06-30")
251
+
252
+ # Product + date range (intersection)
253
+ df = client.get_sp_rolling_index_dataframe(
254
+ product="CU", start_date="2025-06-01", end_date="2025-06-30",
255
+ )
256
+
257
+ # Single day
258
+ rows = client.get_sp_rolling_index(date="2025-06-15")
259
+ ```
260
+
261
+ > `date` and `start_date`/`end_date` are mutually exclusive. With no product
262
+ > and no date filter, the server returns `400 Bad Request`.
263
+
264
+ ### Limit Events
265
+
266
+ Read `limit_evts` rows (daily limit-up / limit-down events) from the `prices`
267
+ database. A **date filter is required** — either a single day or an inclusive
268
+ range.
269
+
270
+ ```python
271
+ from agualphacn import AGuAlphaClient
272
+
273
+ with AGuAlphaClient(api_key="sk-your-api-key-here") as client:
274
+ # Single day
275
+ rows = client.get_limit_evts(date="2024-06-15")
276
+
277
+ # Inclusive range as a pandas DataFrame
278
+ df = client.get_limit_evts_dataframe(
279
+ start_date="2024-06-01",
280
+ end_date="2024-06-30",
281
+ )
282
+ print(df.head())
283
+ ```
284
+
285
+ > `date` and `start_date`/`end_date` are mutually exclusive. With no date
286
+ > filter, the server returns `400 Bad Request`.
287
+
288
+ ## API Reference
289
+
290
+ ### AGuAlphaClient
291
+
292
+ #### `__init__(api_key: str, base_url: str = "https://www.agualpha.cn/api")`
293
+ Initialize the client with your API key.
294
+
295
+ #### `get_positions(start_date: Optional[str] = None, end_date: Optional[str] = None) -> PositionsResponse`
296
+ Get position data from subscribed analysts.
297
+
298
+ **Parameters:**
299
+ - `start_date` (str): Filter by start date (YYYY-MM-DD format)
300
+ - `end_date` (str): Filter by end date (YYYY-MM-DD format)
301
+
302
+ **Returns:** `PositionsResponse`
303
+
304
+ #### `get_ideas(status: Optional[str] = None, direction: Optional[str] = None) -> IdeasResponse`
305
+ Get trade ideas from subscribed analysts.
306
+
307
+ **Parameters:**
308
+ - `status` (str): Filter by status ("active", "closed")
309
+ - `direction` (str): Filter by direction ("long", "short")
310
+
311
+ **Returns:** `IdeasResponse`
312
+
313
+ #### `get_csi_weights(index: str, date=None, start_date=None, end_date=None, days=None, page=None, page_size=None) -> list`
314
+ Get CSI weights data filtered by index. `index` is **required** and must be one of
315
+ `CSI1000`, `CSI2000`, `CSI300`, `CSI500`. At most one of the date filters may be
316
+ applied: `date` (single day), `start_date`+`end_date` (range), or `days` (rolling
317
+ window). Auto-paginates unless `page` is given.
318
+
319
+ **Returns:** `list` of dicts, each representing a row from the `csi_weights` table.
320
+
321
+ #### `get_csi_weights_dataframe(index: str, date=None, start_date=None, end_date=None, days=None, page=None, page_size=None) -> pandas.DataFrame`
322
+ Same parameters as `get_csi_weights`. Returns a DataFrame. Requires the `pandas` extra (`pip install agualphacn[pandas]`).
323
+
324
+ **Returns:** `pandas.DataFrame`
325
+
326
+ #### `get_revision_fy2(date=None, start_date=None, end_date=None, days=None, ticker=None, type=None, page=None, page_size=None) -> list`
327
+ Get revision_fy2 data from the `cn_af` database. A date filter is **required** —
328
+ exactly one of `date` (single day), `start_date`+`end_date` (range), or `days`
329
+ (rolling window). Optional: `ticker` (e.g. `"000009.SZ"`) and `type`
330
+ (`"eps"`/`"np"`/`"sales"`, no value = all types). Auto-paginates unless `page` is given.
331
+
332
+ **Returns:** `list` of dicts, each representing a row from the `revision_fy2` table.
333
+
334
+ #### `get_revision_fy2_dataframe(date=None, start_date=None, end_date=None, days=None, ticker=None, type=None, page=None, page_size=None) -> pandas.DataFrame`
335
+ Same parameters as `get_revision_fy2`. A date filter is **required**. Requires the `pandas` extra (`pip install agualphacn[pandas]`).
336
+
337
+ **Returns:** `pandas.DataFrame`
338
+
339
+ #### `get_adj_close(membership: str, date=None, start_date=None, end_date=None, page=None, page_size=None) -> list`
340
+ Get adj_close data from the `prices` database. `membership` is **required**
341
+ (universe selector — see the table above); a date filter is **required** —
342
+ either `date` (single day) or `start_date`+`end_date` (inclusive range, either
343
+ bound optional). Auto-paginates unless `page` is given.
344
+
345
+ **Returns:** `list` of dicts, each representing a row from `adj_close`.
346
+
347
+ #### `get_adj_close_dataframe(membership: str, date=None, start_date=None, end_date=None, page=None, page_size=None) -> pandas.DataFrame`
348
+ Same parameters as `get_adj_close`. `membership` and a date filter are **required**. Requires the `pandas` extra (`pip install agualphacn[pandas]`).
349
+
350
+ **Returns:** `pandas.DataFrame`
351
+
352
+ #### `get_p_cies(zone: str, page=None, page_size=None) -> list`
353
+ Get p_cies data from the `prices` database. `zone` is **required** and must be
354
+ `"hk"` or `"cn"`. Auto-paginates unless `page` is given.
355
+
356
+ **Returns:** `list` of dicts, each representing a row from `p_cies`.
357
+
358
+ #### `get_p_cies_dataframe(zone: str, page=None, page_size=None) -> pandas.DataFrame`
359
+ Same parameters as `get_p_cies`. `zone` is **required**. Requires the `pandas` extra (`pip install agualphacn[pandas]`).
360
+
361
+ #### `get_sp_rolling_index(product=None, products=None, date=None, start_date=None, end_date=None, page=None, page_size=None) -> list`
362
+ Get commodity rolling-index rows from the `prices` database. At least **one**
363
+ of `product(s)` or a date filter is **required**. `date` and `start_date`/`end_date`
364
+ are mutually exclusive.
365
+
366
+ - `product` (single string) and `products` (list) are merged; you can pass either or both.
367
+ - With no `product(s)` and no date filter, the server returns `400`.
368
+
369
+ **Returns:** `list` of dicts, each representing a row from `sp_rolling_index`.
370
+
371
+ #### `get_sp_rolling_index_dataframe(product=None, products=None, date=None, start_date=None, end_date=None, page=None, page_size=None) -> pandas.DataFrame`
372
+ Same parameters as `get_sp_rolling_index`. Requires the `pandas` extra.
373
+
374
+ #### `get_sp_rolling_index_products() -> list`
375
+ Return all distinct product codes available in `sp_rolling_index`, sorted ascending.
376
+ Useful for discovering which values are valid for the `product(s)` parameter.
377
+
378
+ **Returns:** `pandas.DataFrame`
379
+
380
+ #### `get_limit_evts(date=None, start_date=None, end_date=None, page=None, page_size=None) -> list`
381
+ Get `limit_evts` rows (daily limit-up / limit-down events) from the `prices`
382
+ database. A date filter is **required**: either `date` (single day) or
383
+ `start_date`/`end_date` (inclusive range). `date` and `start_date`/`end_date`
384
+ are mutually exclusive.
385
+
386
+ **Returns:** `list` of dicts, each representing a row from `limit_evts`.
387
+
388
+ #### `get_limit_evts_dataframe(date=None, start_date=None, end_date=None, page=None, page_size=None) -> pandas.DataFrame`
389
+ Same parameters as `get_limit_evts`. Requires the `pandas` extra.
390
+
391
+ ### Response Models
392
+
393
+ #### `PositionsResponse`
394
+ - `success` (bool): Request success status
395
+ - `total` (int): Total number of records
396
+ - `data` (List[Position]): List of position objects
397
+ - `error` (str, optional): Error message if failed
398
+
399
+ #### `IdeasResponse`
400
+ - `success` (bool): Request success status
401
+ - `total` (int): Total number of records
402
+ - `data` (List[Idea]): List of idea objects
403
+ - `error` (str, optional): Error message if failed
404
+
405
+ ## Error Handling
406
+
407
+ ```python
408
+ from agualphacn import AGuAlphaClient
409
+ from agualphacn.exceptions import APIError, AuthenticationError, RateLimitError
410
+
411
+ try:
412
+ client = AGuAlphaClient(api_key="invalid-key")
413
+ positions = client.get_positions()
414
+ except AuthenticationError:
415
+ print("Invalid API key")
416
+ except RateLimitError as e:
417
+ print(f"Too many requests: {e}")
418
+ except APIError as e:
419
+ print(f"API error: {e}")
420
+ ```
421
+
422
+ ## Rate Limiting
423
+
424
+ The data API limits each API key to **4 requests per second** (sliding 1-second
425
+ window). When the limit is exceeded, the server returns HTTP `429 Too Many
426
+ Requests` with a `Retry-After` header indicating how many seconds to wait. The
427
+ SDK surfaces this as a `RateLimitError` (subclass of `APIError`), so you can
428
+ catch it and back off:
429
+
430
+ ```python
431
+ import time
432
+ from agualphacn import AGuAlphaClient
433
+ from agualphacn.exceptions import RateLimitError
434
+
435
+ client = AGuAlphaClient(api_key="your-api-key")
436
+ while True:
437
+ try:
438
+ data = client.get_positions()
439
+ break
440
+ except RateLimitError as e:
441
+ time.sleep(1)
442
+ ```
443
+
444
+ ## Pagination
445
+
446
+ The server caps each request at **2000 rows**. By default, the SDK
447
+ **auto-paginates** — calling `get_positions()`, `get_ideas()`,
448
+ `get_csi_weights(index=...)`, or `get_revision_fy2(date=...)` with no page
449
+ argument walks every page and returns the full result set in one call. Each
450
+ underlying page request counts against your 4 req/s limit, so large tables
451
+ cost multiple requests.
452
+
453
+ If you want a single page, pass `page` (1-indexed) and optionally `page_size`
454
+ (server caps at 2000):
455
+
456
+ ```python
457
+ # Fetch only the first 500 rows of revision_fy2 in the last 30 days
458
+ rows = client.get_revision_fy2(days=30, page=1, page_size=500)
459
+ ```
460
+
461
+ When paginating manually, the response object exposes `page`, `page_size`,
462
+ `total_pages`, and `has_more` so you can loop pages yourself:
463
+
464
+ ```python
465
+ page = 1
466
+ all_rows = []
467
+ while True:
468
+ resp = client.get_positions(page=page, page_size=1000)
469
+ all_rows.extend(resp.data)
470
+ if not resp.has_more:
471
+ break
472
+ page += 1
473
+ ```
474
+
475
+
476
+ ## Requirements
477
+
478
+ - Python 3.8+
479
+ - requests 2.28.0+
480
+
481
+ ## License
482
+
483
+ MIT License