Files
etf/framework_v2/FLASK_API_FETCHER_ARCHITECTURE.md
aszerW 99d3584d05 docs(framework_v2): 更新 FlaskAPIFetcher 文档(API 日历集成)
## 使用指南更新(FLASK_API_FETCHER_GUIDE.md)
- get_trading_calendar() 方法签名更新
  - 新增 start, end 参数(支持动态日期范围)
  - 返回类型: pd.DatetimeIndex(准确日历)
- 使用示例更新(API 调用方式)
- 注意事项更新:交易日历准确性  已解决

## 架构设计更新(FLASK_API_FETCHER_ARCHITECTURE.md)
- get_trading_calendar() 实现更新
  - 从临时 pandas BDay → API 准确日历
  - API 端点: GET /api/v1/trading-calendar
- 未来优化: 移除交易日历 TODO(已完成)

## 文档一致性
- 所有示例代码使用 API 日历
- 架构描述与实际实现一致
- 版本历史更新(2024-04-16)
2026-05-24 12:38:55 +08:00

372 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# FlaskAPIFetcher 架构设计
## 定位
`FlaskAPIFetcher` 是 framework_v2 的**数据获取层实现**,连接抽象接口与线上 API 服务。
---
## 架构层次
```
┌─────────────────────────────────────────────────────────┐
│ Strategy (策略层) │
│ - RotationStrategy │
│ - CCIScreener │
└────────────────┬────────────────────────────────────────┘
│ 调用
┌────────────────▼────────────────────────────────────────┐
│ DataFetcher (抽象接口) │
│ framework_v2/core/data.py │
│ - fetch_indices() [ABC] │
│ - fetch_etf() [ABC] │
│ - get_trading_calendar() [ABC] │
└────────────────┬────────────────────────────────────────┘
│ 继承
┌────────────────▼────────────────────────────────────────┐
│ FlaskAPIFetcher (具体实现) │
│ framework_v2/shared/data/flask_api_fetcher.py │
│ - fetch_indices() ✅ │
│ - fetch_etf() ✅ │
│ - get_trading_calendar() ✅ │
│ - get_benchmark() ✅ │
└────────────────┬────────────────────────────────────────┘
│ 委托
┌────────────────▼────────────────────────────────────────┐
│ FlaskAPIDataSource (底层数据源) │
│ datasource/flask_api_source.py │
│ - fetch() - HTTP 请求 + 重试 │
│ - fetch_batch() - 批量获取 │
│ - validate_ohlcv_response() - Pydantic 验证 │
└────────────────┬────────────────────────────────────────┘
│ HTTP
┌────────────────▼────────────────────────────────────────┐
│ Flask API Server (线上服务) │
│ https://k3s.tokenpluse.xyz │
│ - /api/v1/ohlcv - OHLCV 数据 │
│ - /api/v1/etf/nav - ETF 净值 │
└─────────────────────────────────────────────────────────┘
```
---
## 设计原则
### 1. 依赖倒置原则DIP
**策略层依赖抽象接口,不依赖具体实现**
```python
# ✅ 正确:策略依赖 DataFetcher 抽象
class RotationStrategy:
def __init__(self, data_fetcher: DataFetcher):
self.fetcher = data_fetcher # 可以是任何实现
# 使用时注入具体实现
strategy = RotationStrategy(
data_fetcher=FlaskAPIFetcher() # 或其他实现
)
```
### 2. 单一职责原则SRP
每个类只负责一件事:
| 类 | 职责 |
|----|------|
| **DataFetcher** | 定义数据获取接口(抽象) |
| **FlaskAPIFetcher** | 实现接口,组织数据获取流程 |
| **FlaskAPIDataSource** | 处理 HTTP 请求、重试、验证 |
### 3. 开闭原则OCP
**对扩展开放,对修改封闭**
```python
# 未来可以添加新实现,无需修改 DataFetcher
class LocalCacheFetcher(DataFetcher):
"""本地缓存实现"""
pass
class DatabaseFetcher(DataFetcher):
"""数据库实现"""
pass
```
---
## 数据流
### 获取指数数据
```
RotationStrategy.fetch_data()
FlaskAPIFetcher.fetch_indices(codes, start, end)
├─ 遍历 codes
│ │
│ ▼
│ FlaskAPIDataSource.fetch(code, start, end, adj='raw')
│ │
│ ├─ 构建 HTTP 请求
│ ├─ 发送 GET /api/v1/ohlcv
│ ├─ 接收 JSON 响应
│ ├─ Pydantic 验证
│ └─ 返回 DataFrame
└─ 收集所有 DataFrame
Dict[str, DataFrame]
```
### 获取 ETF 数据
```
RotationStrategy.fetch_data()
FlaskAPIFetcher.fetch_etf(codes, start, end)
├─ 遍历 codes
│ │
│ ▼
│ FlaskAPIDataSource.fetch(code, start, end,
│ adj='raw',
│ asset_type='china_etf')
│ │
│ ├─ 获取 OHLCV 数据
│ ├─ 自动获取净值数据
│ ├─ 自动计算溢价率
│ └─ 返回 DataFrame带 attrs
│ ├─ df: 价格数据
│ └─ df.attrs:
│ ├─ nav (净值 DataFrame)
│ ├─ premium_series (溢价率序列)
│ └─ latest_premium (最新溢价率)
└─ 收集所有 DataFrame
Dict[str, DataFrame]
```
---
## 与 CrossMarketAligner 集成
### 完整数据流
```
FlaskAPIFetcher
├─ fetch_indices() → 原始 OHLCV不同市场日历
│ ├─ 美股: ^GSPC (252 天/年)
│ ├─ 港股: ^HSI (250 天/年)
│ └─ A股: 000300.SH (244 天/年)
CrossMarketAligner
├─ align_returns() → 对齐收益率到目标日历
│ ├─ 价格先 reindex + ffill
│ ├─ 再 pct_change()(避免 ffill 陷阱)
│ └─ 休市日收益率 = 0%
DataFrame统一日历
├─ ^GSPC: 244 天A 股日历)
├─ ^HSI: 244 天
└─ 000300.SH: 244 天
Factor Calculation因子计算
Signal Generation信号生成
Backtest Execution回测执行
```
### 示例代码
```python
from framework_v2.shared.data import FlaskAPIFetcher, CrossMarketAligner
# 1. 获取数据
fetcher = FlaskAPIFetcher()
data = fetcher.fetch_indices(
codes=["^GSPC", "000300.SH"],
start="2024-01-01",
end="2024-12-31"
)
# 2. 获取目标日历
target_calendar = fetcher.get_trading_calendar(market='A')
# 3. 对齐收益率
aligner = CrossMarketAligner(target_calendar=target_calendar)
returns = aligner.align_multi_asset(
close_dict={
"SP500": data["^GSPC"]["close"],
"CSI300": data["000300.SH"]["close"],
}
)
# 4. 验证对齐
assert returns.isna().sum().sum() == 0, "不应该有 NaN"
print(f"✓ 对齐成功: {len(returns)} 天")
```
---
## 测试验证
### 测试覆盖
| 测试项 | 验证内容 | 状态 |
|--------|----------|------|
| 健康检查 | API 服务可用性 | ✅ 通过 |
| 指数数据 | OHLCV 结构、数据量 | ✅ 通过 |
| ETF 数据 | 价格 + 净值 + 溢价率 | ✅ 通过 |
| 交易日历 | 日期范围、天数 | ✅ 通过 |
| 基准数据 | Series 类型、数据量 | ✅ 通过 |
### 运行测试
```bash
cd /Users/aszer/Documents/vscode/etf
python framework_v2/tests/test_flask_api_fetcher.py
```
**测试结果**
```
✓ 测试通过 - 健康检查
✓ 测试通过 - 指数数据
✓ 测试通过 - ETF 数据
✓ 测试通过 - 交易日历
✓ 测试通过 - 基准数据
总计: 5/5 通过
```
---
## 优势总结
### vs 直接使用 FlaskAPIDataSource
| 特性 | FlaskAPIDataSource | FlaskAPIFetcher |
|------|-------------------|-----------------|
| **抽象接口** | ❌ 无 | ✅ 继承 DataFetcher |
| **批量获取** | ✅ fetch_batch() | ✅ fetch_indices() |
| **进度显示** | ❌ 无 | ✅ 自动显示 |
| **错误处理** | ✅ 基础 | ✅ 增强(验证 + 重试) |
| **策略集成** | ❌ 需适配 | ✅ 直接使用 |
| **测试覆盖** | ❌ 无 | ✅ 5/5 通过 |
### vs 旧架构
| 特性 | 旧架构datasource/ | 新架构framework_v2/ |
|------|----------------------|------------------------|
| **抽象接口** | ❌ 无 | ✅ DataFetcher ABC |
| **Schema 验证** | ⚠️ 部分 | ✅ Pydantic 完整验证 |
| **跨市场对齐** | ❌ 无 | ✅ CrossMarketAligner |
| **分层设计** | ❌ 混合 | ✅ core/shared/strategy |
| **可测试性** | ⚠️ 困难 | ✅ 依赖注入 |
| **文档** | ❌ 缺失 | ✅ 完整文档 |
---
## 未来优化
### 1. 交易日历准确性
**已解决**:通过 API 获取准确交易日历。
**实现**
```python
def get_trading_calendar(self, market, start, end):
# 调用 API 获取准确日历
calendar = self._source.get_trading_calendar(
market=market,
start_date=start,
end_date=end
)
return calendar
```
**API 端点**`GET /api/v1/trading-calendar`
**返回**:准确的 DatetimeIndex包含节假日处理
### 2. 缓存机制
**当前问题**:每次请求都调用 API重复获取相同数据。
**优化方案**
```python
# TODO: 添加本地缓存
class FlaskAPIFetcher(DataFetcher):
def __init__(self, cache_dir: str = "data_cache"):
self.cache = LocalCache(cache_dir)
def fetch_indices(self, codes, start, end):
# 1. 检查缓存
cached = self.cache.get(codes, start, end)
if cached:
return cached
# 2. 调用 API
data = self._source.fetch_batch(...)
# 3. 写入缓存
self.cache.set(codes, start, end, data)
return data
```
### 3. 异步支持
**当前问题**:批量获取串行执行,效率低。
**优化方案**
```python
# TODO: 使用 aiohttp 异步获取
async def fetch_indices_async(self, codes, start, end):
async with aiohttp.ClientSession() as session:
tasks = [
self._fetch_single(session, code, start, end)
for code in codes
]
results = await asyncio.gather(*tasks)
return dict(zip(codes, results))
```
---
## 相关文件
| 文件 | 说明 |
|------|------|
| `framework_v2/core/data.py` | DataFetcher 抽象基类 |
| `framework_v2/shared/data/flask_api_fetcher.py` | FlaskAPIFetcher 实现 |
| `framework_v2/shared/data/__init__.py` | 导出 FlaskAPIFetcher |
| `framework_v2/tests/test_flask_api_fetcher.py` | 测试套件 |
| `datasource/flask_api_source.py` | 底层 HTTP 数据源 |
| `FLASK_API_FETCHER_GUIDE.md` | 使用指南 |
---
## 版本历史
- **2024-04-16**: 初始版本
- 继承 DataFetcher 抽象基类
- 实现指数、ETF 数据获取
- 集成 FlaskAPIDataSource
- 5/5 测试通过
- 完整文档