Metadata-Version: 2.4
Name: agualpha
Version: 1.3.6
Summary: Python client for AGuAlpha Investment Platform API
Home-page: https://github.com/agualpha/agualpha-python
Author: AGuAlpha
Author-email: contact@agualpha.com
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Office/Business :: Financial
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28.0
Provides-Extra: async
Requires-Dist: aiohttp>=3.8.0; extra == "async"
Provides-Extra: pandas
Requires-Dist: pandas>=1.5.0; extra == "pandas"
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: black>=22.0.0; extra == "dev"
Requires-Dist: mypy>=0.950; extra == "dev"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# AGuAlpha Python Client

Official Python library for accessing AGuAlpha Investment Platform data.

## Installation

```bash
pip install agualpha
```

For async support:
```bash
pip install agualpha[async]
```

For pandas integration:
```bash
pip install agualpha[pandas]
```

## Quick Start

### Synchronous Usage

```python
from agualpha import AGuAlphaClient

# Initialize client
client = AGuAlphaClient(api_key="sk-your-api-key-here")

# Get positions data
positions = client.get_positions()
print(f"Total positions: {positions.total}")

for position in positions.data:
    print(f"Date: {position.date}, Outstanding: {position.outstanding}")

# Get ideas data
ideas = client.get_ideas(status="active")
print(f"Total active ideas: {ideas.total}")

for idea in ideas.data:
    print(f"Symbol: {idea.ticker_symbol}, Status: {idea.status}")

# Close the connection
client.close()
```

### Using Context Manager

```python
from agualpha import AGuAlphaClient

with AGuAlphaClient(api_key="sk-your-api-key-here") as client:
    positions = client.get_positions(
        start_date="2024-01-01",
        end_date="2024-12-31"
    )
    print(f"Found {positions.total} positions")
```

### Asynchronous Usage

```python
import asyncio
from agualpha import AGuAlphaAsyncClient

async def main():
    async with AGuAlphaAsyncClient(api_key="sk-your-api-key-here") as client:
        # Fetch data concurrently
        positions, ideas = await asyncio.gather(
            client.get_positions(),
            client.get_ideas(status="active")
        )

        print(f"Positions: {positions.total}, Ideas: {ideas.total}")

asyncio.run(main())
```

### Pandas Integration

```python
from agualpha import AGuAlphaClient
from agualpha.utils import positions_to_dataframe, export_to_csv

client = AGuAlphaClient(api_key="sk-your-api-key-here")

# Get positions and convert to DataFrame
positions_response = client.get_positions()
df = positions_to_dataframe(positions_response.data)

# Export to CSV
export_to_csv(positions_response.data, "positions.csv")
```

### CSI Weights

Read `csi_weights` rows from the `prices` database filtered by index code.
The `index` parameter is **required** — the server rejects requests without it.

Three mutually-exclusive date filters are supported:

```python
from agualpha import AGuAlphaClient

with AGuAlphaClient(api_key="sk-your-api-key-here") as client:
    # 1) Single day
    df = client.get_csi_weights_dataframe(index="000300", date="2026-07-15")

    # 2) Inclusive range
    df = client.get_csi_weights_dataframe(
        index="000300",
        start_date="2026-07-01",
        end_date="2026-07-15",
    )

    # 3) Rolling window — last 30 days up to today
    df = client.get_csi_weights_dataframe(index="000300", days=30)

    print(df.head())
    print(f"Shape: {df.shape}")
```

If you don't need pandas, drop the `_dataframe` suffix to get a list of dicts:

```python
with AGuAlphaClient(api_key="sk-your-api-key-here") as client:
    rows = client.get_csi_weights(index="000300", days=30)
    print(f"Total records: {len(rows)}")
```

## API Reference

### AGuAlphaClient

#### `__init__(api_key: str, base_url: str = "http://www.agualpha.cn:8090/api")`
Initialize the client with your API key.

#### `get_positions(start_date: Optional[str] = None, end_date: Optional[str] = None) -> PositionsResponse`
Get position data from subscribed analysts.

**Parameters:**
- `start_date` (str): Filter by start date (YYYY-MM-DD format)
- `end_date` (str): Filter by end date (YYYY-MM-DD format)

**Returns:** `PositionsResponse`

#### `get_ideas(status: Optional[str] = None, direction: Optional[str] = None) -> IdeasResponse`
Get trade ideas from subscribed analysts.

**Parameters:**
- `status` (str): Filter by status ("active", "closed")
- `direction` (str): Filter by direction ("long", "short")

**Returns:** `IdeasResponse`

#### `get_csi_weights(index: str, date=None, start_date=None, end_date=None, days=None, page=None, page_size=None) -> list`
Get CSI weights data filtered by index code. `index` is **required**. At most one
of the date filters may be applied: `date` (single day), `start_date`+`end_date`
(range), or `days` (rolling window). Auto-paginates unless `page` is given.

**Returns:** `list` of dicts, each representing a row from the `csi_weights` table.

#### `get_csi_weights_dataframe(index: str, date=None, start_date=None, end_date=None, days=None, page=None, page_size=None) -> pandas.DataFrame`
Same parameters as `get_csi_weights`. Returns a DataFrame. Requires the `pandas` extra (`pip install agualpha[pandas]`).

**Returns:** `pandas.DataFrame`

#### `get_revision_fy2(date=None, start_date=None, end_date=None, days=None, ticker=None, type=None, page=None, page_size=None) -> list`
Get revision_fy2 data from the `cn_af` database. A date filter is **required** —
exactly one of `date` (single day), `start_date`+`end_date` (range), or `days`
(rolling window). Optional: `ticker` (e.g. `"000009.SZ"`) and `type`
(`"eps"`/`"np"`/`"sales"`, no value = all types). Auto-paginates unless `page` is given.

**Returns:** `list` of dicts, each representing a row from the `revision_fy2` table.

#### `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`
Same parameters as `get_revision_fy2`. A date filter is **required**. Requires the `pandas` extra (`pip install agualpha[pandas]`).

**Returns:** `pandas.DataFrame`

### Response Models

#### `PositionsResponse`
- `success` (bool): Request success status
- `total` (int): Total number of records
- `data` (List[Position]): List of position objects
- `error` (str, optional): Error message if failed

#### `IdeasResponse`
- `success` (bool): Request success status
- `total` (int): Total number of records
- `data` (List[Idea]): List of idea objects
- `error` (str, optional): Error message if failed

## Error Handling

```python
from agualpha import AGuAlphaClient
from agualpha.exceptions import APIError, AuthenticationError, RateLimitError

try:
    client = AGuAlphaClient(api_key="invalid-key")
    positions = client.get_positions()
except AuthenticationError:
    print("Invalid API key")
except RateLimitError as e:
    print(f"Too many requests: {e}")
except APIError as e:
    print(f"API error: {e}")
```

## Rate Limiting

The data API limits each API key to **4 requests per second** (sliding 1-second
window). When the limit is exceeded, the server returns HTTP `429 Too Many
Requests` with a `Retry-After` header indicating how many seconds to wait. The
SDK surfaces this as a `RateLimitError` (subclass of `APIError`), so you can
catch it and back off:

```python
import time
from agualpha import AGuAlphaClient
from agualpha.exceptions import RateLimitError

client = AGuAlphaClient(api_key="your-api-key")
while True:
    try:
        data = client.get_positions()
        break
    except RateLimitError as e:
        time.sleep(1)
```

## Pagination

The server caps each request at **2000 rows**. By default, the SDK
**auto-paginates** — calling `get_positions()`, `get_ideas()`,
`get_csi_weights(index=...)`, or `get_revision_fy2(date=...)` with no page
argument walks every page and returns the full result set in one call. Each
underlying page request counts against your 4 req/s limit, so large tables
cost multiple requests.

If you want a single page, pass `page` (1-indexed) and optionally `page_size`
(server caps at 2000):

```python
# Fetch only the first 500 rows of revision_fy2 in the last 30 days
rows = client.get_revision_fy2(days=30, page=1, page_size=500)
```

When paginating manually, the response object exposes `page`, `page_size`,
`total_pages`, and `has_more` so you can loop pages yourself:

```python
page = 1
all_rows = []
while True:
    resp = client.get_positions(page=page, page_size=1000)
    all_rows.extend(resp.data)
    if not resp.has_more:
        break
    page += 1
```


## Requirements

- Python 3.8+
- requests 2.28.0+

## License

MIT License
