Metadata-Version: 2.3
Name: GlamQuant
Version: 0.0.3
Summary: GlamQuant Python SDK
Author: chenzq
Author-email: chenzq <>
Requires-Dist: httpx>=0.27.0
Requires-Dist: pandas>=2.0.0
Requires-Dist: pydantic>=2.0
Requires-Dist: pydantic-settings>=2.0.0
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# GlamQuant

对接 **pms-app** 的本地客户端 SDK：配置好服务地址（`base_url`）与鉴权 token 后，
按业务端点调用组合运营报表等数据接口，返回 `pandas.DataFrame`。底层客户端对
用户不可见 —— 只需通过顶层函数设置 `base_url` / `token`，再调用业务函数即可。

pms-app 的约定：

- 响应包 `{status, msg, data}`，`status == 0` 表示成功
- 鉴权用自定义 `token` 请求头（JWT）；服务端亦可配置 IP 白名单免 token
- 管理：`uv`
- HTTP 客户端：`httpx`（超时 / 重试 / 认证头自动注入 / 401 自动处理）

## 安装

```bash
uv add GlamQuant
# 或
pip install GlamQuant
```

## 快速开始（组合日频指标）

```python
import glamquant as gl

# 1. 配置共享默认客户端：base_url 手动指定（本地 / 生产切换）
gl.configure(base_url="http://localhost:18000", token="<pms JWT>")
# 生产环境：gl.configure(base_url="https://pms-prod.example.com", token="<pms JWT>")

# 2. 调用业务端点（模块级函数），返回 DataFrame
df = gl.portf_daily(
    portf_names=["产品A"],
    start_date="20251001",
    end_date="20251015",
    fields=["unit_nav", "acc_unit_nav"],
)
print(df)
```

返回 `pandas.DataFrame`：`portf_name` + `dt`（日期）+ 每个指标字段一列；空结果返回空 `DataFrame`。

### 配置 base_url / token（可直接单独设置）

底层客户端对用户不可见，所有业务函数共用同一个默认客户端，随时可改地址或换 token：

```python
import glamquant as gl

gl.set_base_url("http://localhost:8000")     # 本地
# gl.set_base_url("https://pms-prod.example.com")   # 切生产
gl.set_token("<pms JWT>")                    # 换 token

df = gl.portf_daily(portf_names=["产品A"], start_date="20251001", end_date="20251015", fields=["unit_nav"])
```

`base_url` 也可不显式设置，走环境变量：

```bash
export GLAMQUANT_BASE_URL=http://localhost:18000
```

```python
gl.configure(token="<pms JWT>")   # base_url 自动读环境变量
```

### 无 token（IP 白名单本地联调）

若在 pms 侧把调用方 IP 加入 `WHITELIST_IPS`，可不设 token，客户端不发送任何认证头：

```python
gl.set_base_url("http://localhost:18000")   # 只设地址，不带 token
df = gl.portf_daily(portf_names=["产品A"], start_date="20251001", end_date="20251015", fields=["unit_nav"])
```

## 业务端点一览

| 模块 | 函数 | 说明 |
|---|---|---|
| `quant` | `portf_daily(start_date, end_date, portf_names, portf_codes, fields)` | 组合日频指标：按组合名/代码取指标宽表 |

> `portf_daily`：`portf_names` 与 `portf_codes` 至少给一个，`fields` 不可为空（均本地即校验）；
> `start_date`/`end_date` 支持 `YYYYMMDD` 或 `YYYY-MM-DD`。返回列 `portf_name` + `dt` +
> 每个指标字段一列。**需 token 带 `role_quant_sdk` 角色**，否则 HTTP 403。

```python
df = gl.quant.portf_daily(
    portf_names=["产品A"],                    # 或 portf_codes=["P0001"]
    start_date="20251001", end_date="20251015",
    fields=["unit_nav", "acc_unit_nav"],      # tmetrics_def.field_name
)
```

> 完整字段清单（23 个指标）、返回示例与注意事项见 [docs/portf_daily.md](docs/portf_daily.md)。

## 配置

通过环境变量（前缀 `GLAMQUANT_`）或 `.env` 文件覆盖：

| 变量 | 默认值 | 说明 |
|---|---|---|
| `GLAMQUANT_BASE_URL` | 无（必填/手动指定） | pms-app 地址，如 `http://localhost:18000` |
| `GLAMQUANT_TIMEOUT` | `60.0` | 请求超时（秒） |
| `GLAMQUANT_MAX_RETRIES` | `3` | 最大重试次数（5xx/429/网络异常） |
| `GLAMQUANT_MAX_AUTH_RETRIES` | `1` | 401 后自动重登次数（`0` 关闭） |

## 异常

```python
from glamquant import (
    FinApiError, PMSAPIError, AuthError, RateLimitError,
    ParamError, APICallError, NetworkError, DataError,
)
```

| 场景 | 异常 |
|---|---|
| pms 返回 `status != 0` | `PMSAPIError`（`code` 为 status） |
| token 无效 / 401 | `AuthError` |
| 参数校验失败（如组合数超限） | `ParamError` |
| 非 2xx / 服务端错误 | `APICallError` |
| 网络异常（超时/断连） | `NetworkError` |
| 响应解析失败 | `DataError` |

## 开发

```bash
uv sync --all-groups   # 安装依赖（含 dev）
uv run pytest          # 运行测试
uv build               # 构建 wheel 与 sdist
```

## 打包与发布

### 发布到 PyPI

```bash
# 1. 升版本号：编辑 pyproject.toml 的 version（PyPI 不允许覆盖已发布版本）
#    当前版本 0.0.1

# 2. 重新构建
rm -rf dist
uv build

# 3. 本地验证 wheel 可安装、可导入（推荐，先自测再上传）
pip install dist/glamquant-*.whl
python -c "import glamquant; print('OK')"

# 4. 上传（token 用环境变量，避免写入 .pypirc）
export UV_PUBLISH_TOKEN='你的_pypi_token'     # 形如 pypi-xxxxxxx
uv publish

# 5. 从 PyPI 装回来验证
pip install GlamQuant
python -c "import glamquant; print('OK')"
```
