# FlaskAPIFetcher 使用指南

## 概述

`FlaskAPIFetcher` 是 framework_v2 的数据获取器实现，通过 HTTP API 获取线上数据（指数、ETF）。

**核心优势**：
- ✅ 无需本地 SSH 隧道配置
- ✅ 支持远程调用（生产环境）
- ✅ 自动重试 + 超时处理
- ✅ Pydantic Schema 验证响应
- ✅ ETF 数据自动附加净值和溢价率

---

## 快速开始

### 1. 基础使用

```python
from framework_v2.shared.data import FlaskAPIFetcher

# 创建数据获取器
fetcher = FlaskAPIFetcher(
    base_url="https://k3s.tokenpluse.xyz",  # 或从环境变量读取
    timeout=120,
    retries=3
)

# 获取指数数据
data = fetcher.fetch_indices(
    codes=["000300.SH", "000905.SH"],
    start="2024-01-01",
    end="2024-12-31"
)

# 访问数据
df_300 = data["000300.SH"]
print(df_300.head())
```

### 2. 获取 ETF 数据

```python
# 获取 ETF 数据（自动附加净值和溢价率）
data = fetcher.fetch_etf(
    codes=["510300.SH", "159919.SZ"],
    start="2024-01-01",
    end="2024-12-31"
)

# 访问价格数据
df = data["510300.SH"]
print(df.head())

# 访问净值数据
nav = df.attrs.get('nav')
if nav is not None:
    print(f"净值数据: {len(nav)} 条")

# 访问溢价率
premium = df.attrs.get('latest_premium')
if premium is not None:
    print(f"最新溢价率: {premium:.2f}%")
```

---

## 完整示例：结合 CrossMarketAligner

### 场景：获取跨市场数据并对齐到 A 股日历

```python
from framework_v2.shared.data import FlaskAPIFetcher, CrossMarketAligner

# 1. 创建数据获取器
fetcher = FlaskAPIFetcher()

# 2. 获取 A 股交易日历（通过 API）
a_share_calendar = fetcher.get_trading_calendar(
    market='A',
    start='2024-01-01',
    end='2024-12-31'
)

# 3. 创建对齐器
aligner = CrossMarketAligner(target_calendar=a_share_calendar)

# 4. 获取跨市场指数数据
us_indices = fetcher.fetch_indices(
    codes=["^GSPC", "^IXIC"],  # 美股
    start="2024-01-01",
    end="2024-12-31"
)

cn_indices = fetcher.fetch_indices(
    codes=["000300.SH", "000905.SH"],  # A股
    start="2024-01-01",
    end="2024-12-31"
)

# 5. 对齐收益率到 A 股日历
returns_aligned = aligner.align_multi_asset(
    close_dict={
        "SP500": us_indices["^GSPC"]["close"],
        "NASDAQ": us_indices["^IXIC"]["close"],
        "CSI300": cn_indices["000300.SH"]["close"],
        "CSI500": cn_indices["000905.SH"]["close"],
    }
)

# 6. 验证对齐结果
print(returns_aligned.head())
print(f"\nNaN 数量: {returns_aligned.isna().sum().sum()}")  # 应该为 0
```

---

## API 参考

### FlaskAPIFetcher

#### 初始化

```python
FlaskAPIFetcher(
    base_url: str = None,      # API 地址（默认从环境变量读取）
    timeout: int = 120,        # 请求超时时间（秒）
    retries: int = 3           # 重试次数
)
```

#### 核心方法

| 方法 | 说明 | 返回类型 |
|------|------|----------|
| `fetch_indices(codes, start, end)` | 获取指数 OHLCV 数据 | `Dict[str, DataFrame]` |
| `fetch_etf(codes, start, end)` | 获取 ETF 数据（价格+净值） | `Dict[str, DataFrame]` |
| `get_trading_calendar(market, start, end)` | 获取交易日历（API） | `pd.DatetimeIndex` |
| `get_benchmark(code, start, end)` | 获取基准数据 | `pd.Series` |
| `get_health()` | 检查 API 健康状态 | `Dict` |

#### fetch_indices 参数

```python
fetcher.fetch_indices(
    codes=["000300.SH", "000905.SH"],  # 指数代码列表
    start="2024-01-01",                # 开始日期
    end="2024-12-31"                   # 结束日期
)
```

**返回 DataFrame 结构**：
```
            code     open     high      low    close     volume
date
2024-01-02  000300.SH  3388.30  3395.40  3372.50  3390.20  12345678
2024-01-03  000300.SH  3390.20  3405.60  3385.10  3398.50  13456789
```

#### fetch_etf 参数

```python
fetcher.fetch_etf(
    codes=["510300.SH", "159919.SZ"],  # ETF 代码列表
    start="2024-01-01",                # 开始日期
    end="2024-12-31"                   # 结束日期
)
```

**返回 DataFrame 结构**：
```
            code     open     high      low    close     volume
date
2024-01-02  510300.SH  3.520    3.545    3.510    3.540    45678901
```

**附加信息（df.attrs）**：
- `nav`: 净值数据 DataFrame
- `premium_series`: 溢价率序列（dict）
- `latest_premium`: 最新溢价率（float）
- `premium_stats`: 溢价率统计（dict）

---

## 与 DataFetcher 抽象基类的关系

```
framework_v2/core/data.py              # 抽象基类
    └── DataFetcher (ABC)
        ├── fetch_indices() [抽象]
        ├── fetch_etf() [抽象]
        ├── get_trading_calendar() [抽象]
        └── get_benchmark() [可选]

framework_v2/shared/data/flask_api_fetcher.py  # 具体实现
    └── FlaskAPIFetcher(DataFetcher)
        ├── fetch_indices() ✅ 实现（调用 FlaskAPIDataSource）
        ├── fetch_etf() ✅ 实现（调用 FlaskAPIDataSource）
        ├── get_trading_calendar() ✅ 实现（API 准确日历）
        └── get_benchmark() ✅ 实现
```

### 继承关系验证

```python
from framework_v2.core.data import DataFetcher
from framework_v2.shared.data import FlaskAPIFetcher

# 验证继承
assert issubclass(FlaskAPIFetcher, DataFetcher)

# 验证抽象方法已实现
fetcher = FlaskAPIFetcher()
assert hasattr(fetcher, 'fetch_indices')
assert hasattr(fetcher, 'fetch_etf')
assert hasattr(fetcher, 'get_trading_calendar')
```

---

## 环境变量配置

### FLASK_API_URL

```bash
# .env 文件
FLASK_API_URL=https://k3s.tokenpluse.xyz
```

**优先级**：
1. 构造函数参数 `base_url`
2. 环境变量 `FLASK_API_URL`
3. 默认值 `https://k3s.tokenpluse.xyz`

---

## 错误处理

### 自动重试

```python
fetcher = FlaskAPIFetcher(retries=3)

# 失败时自动重试：
# - 网络超时
# - HTTP 5xx 错误
# - JSON 解析失败
```

### 手动错误处理

```python
data = fetcher.fetch_indices(["000300.SH"], "2024-01-01", "2024-12-31")

if "000300.SH" not in data:
    print("✗ 数据获取失败")
    # 处理错误...
else:
    print(f"✓ 获取 {len(data['000300.SH'])} 条数据")
```

---

## 性能优化

### 批量获取 vs 单个获取

```python
# ✅ 推荐：批量获取（内部自动重试 + 进度显示）
data = fetcher.fetch_indices(
    codes=["000300.SH", "000905.SH", "000852.SH"],
    start="2024-01-01",
    end="2024-12-31"
)

# ❌ 不推荐：循环单个获取（无进度显示）
for code in codes:
    df = fetcher._source.fetch(code, start, end)
```

### 超时设置

```python
# 网络较慢时增加超时
fetcher = FlaskAPIFetcher(timeout=180)  # 3 分钟
```

---

## 测试

运行测试验证功能：

```bash
cd /Users/aszer/Documents/vscode/etf
python framework_v2/tests/test_flask_api_fetcher.py
```

**预期输出**：
```
✓ 测试通过 - 健康检查
✓ 测试通过 - 指数数据
✓ 测试通过 - ETF 数据
✓ 测试通过 - 交易日历
✓ 测试通过 - 基准数据

总计: 5/5 通过
```

---

## 相关文档

- **[框架总览](../README.md)** - framework_v2 架构说明
- **[数据架构方案](../DATA_ARCHITECTURE.md)** - 数据流设计
- **[跨市场对齐方案](../ALIGNMENT_GUIDE.md)** - CrossMarketAligner 使用
- **[Aligner + Schema 整合](../ALIGNMENT_SCHEMA_INTEGRATION.md)** - 验证架构

---

## 注意事项

### 1. 交易日历准确性

✅ **已解决**：通过 API 获取准确交易日历，包含所有节假日。

**使用方式**：
```python
# 获取 A 股 2024 年交易日历（准确）
calendar = fetcher.get_trading_calendar('A', '2024-01-01', '2024-12-31')
print(f"A 股交易日: {len(calendar)} 天")  # 242 天
```

### 2. ETF 净值数据量

ETF 净值数据可能远多于价格数据（历史净值 vs 交易价格）：

```python
df = data["510300.SH"]
print(f"价格: {len(df)} 条")        # ~60 条（2024 Q1）
print(f"净值: {len(df.attrs['nav'])} 条")  # ~3695 条（全历史）
```

### 3. 资产类型检测

FlaskAPIDataSource 支持自动检测资产类型，也可手动指定：

```python
# 自动检测
df = fetcher._source.fetch("510300.SH", start, end)

# 手动覆盖
df = fetcher._source.fetch("510300.SH", start, end, asset_type='china_etf')
```

---

## 版本历史

- **2024-04-16**: 初始版本
  - 继承 DataFetcher 抽象基类
  - 实现指数、ETF 数据获取
  - 集成 FlaskAPIDataSource
  - 5/5 测试通过
