## 使用指南更新(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)
372 lines
11 KiB
Markdown
372 lines
11 KiB
Markdown
# 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 测试通过
|
||
- 完整文档
|