Files
etf/archive/framework_v2/FLASK_API_FETCHER_ARCHITECTURE.md
aszerW c905230a40 refactor(archive): move unused modules to archive/
Archive legacy framework and utility modules that are no longer
referenced by the active core (datasource/ and rotation/):

- framework/ -> archive/framework/
- framework_v2/ -> archive/framework_v2/
- strategies/ -> archive/strategies/
- config/ -> archive/config/
- visualization/ -> archive/visualization/
- scripts/ -> archive/scripts/
- tests/ -> archive/tests/
- run_rotation.py, run_us_rotation.py -> archive/single_files/
- compare_*.py, test_api_dates.py -> archive/single_files/
2026-06-03 23:41:46 +08:00

11 KiB
Raw Blame History

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

策略层依赖抽象接口,不依赖具体实现

# ✅ 正确:策略依赖 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

对扩展开放,对修改封闭

# 未来可以添加新实现,无需修改 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回测执行

示例代码

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 类型、数据量 通过

运行测试

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 获取准确交易日历。

实现

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重复获取相同数据。

优化方案

# 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. 异步支持

当前问题:批量获取串行执行,效率低。

优化方案

# 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 测试通过
    • 完整文档