2026-09-09 23:40:35 +08:00
|
|
|
|
# 基金行情数据底座接入开发计划
|
|
|
|
|
|
|
2026-09-11 14:37:20 +08:00
|
|
|
|
> ⚠️ **MVP 完成标准已过期。** 文中「F0-F4 全部通过」为当时快照;实际 F0-F6 已完成。另有部分「当前实现」的描述已随底座演进而变化。
|
|
|
|
|
|
>
|
|
|
|
|
|
> (此批注由 2026-09-11 只读审计加入;原文未改动。详见 `docs/superpowers/ARCHIVE-2026-09-11-文档清理归档.md` §3)
|
2026-09-14 20:36:00 +08:00
|
|
|
|
>
|
|
|
|
|
|
> ---
|
|
|
|
|
|
> ## ⚠️ 2026-09-14 补充:本文**完全没写**的两件运行期事实(重要)
|
|
|
|
|
|
>
|
|
|
|
|
|
> **1. 行情有效期只有 15 分钟 —— 这是演示/验收最容易翻的一环。**
|
|
|
|
|
|
> `app/service/trade_service.py` 的 `MAX_QUOTE_AGE = timedelta(minutes=15)`(L79)。
|
|
|
|
|
|
> 超时后**所有委托一律 503 `FUND_QUOTE_UNAVAILABLE`**(`app/core/errors.py:260-264`),
|
|
|
|
|
|
> **且没有自动刷新**。症状看起来像"行情源坏了",实际只是数据放久了。
|
|
|
|
|
|
> 补刷命令(**立即生效、无需重启服务**):
|
|
|
|
|
|
> ```powershell
|
|
|
|
|
|
> .\.venv\Scripts\python.exe tools\sync_market_prices.py
|
|
|
|
|
|
> ```
|
|
|
|
|
|
>
|
|
|
|
|
|
> **2. 两个行情/净值同步脚本未被本文登记**:
|
|
|
|
|
|
> - `tools/sync_market_prices.py` —— 刷实时行情(上条)。
|
|
|
|
|
|
> - `tools/sync_nav_history.py` —— 刷历史净值(访客端 `GET /api/v1/products/{product_code}/nav-history` 用它)。
|
|
|
|
|
|
>
|
|
|
|
|
|
> **3. 工具超时的依据**:`query_fund_quote` 的工具超时是 **15 秒**(`app/service/agent/bootstrap.py:278`)。
|
|
|
|
|
|
> 理由(代码内注释):`EastmoneyAdapterFactory` 单码最坏预算 = 4.0s + 0.2s(退避) + 4.0s(重试) = 8.2s,
|
|
|
|
|
|
> 15s ≈ 8.2 × 1.8。**旧的 5 秒会在适配器自己的重试预算之前就撞工具超时**,永远拿不到结果。
|
|
|
|
|
|
>
|
|
|
|
|
|
> **启动链**:`启动金融Agent平台.bat`(桌面)/ `启动平台.bat`(仓库)会按序做:找解释器 →
|
|
|
|
|
|
> 检查 MySQL/Redis/Milvus → **刷新行情** → 起 API 与 Agent Worker → 等 API 应答 → 开浏览器。
|
|
|
|
|
|
> **解释器门槛是「版本 ≥ 3.11 且能 `import fastapi, sqlalchemy, asyncmy, pydantic`」**,
|
|
|
|
|
|
> 不能只看 `--version`(曾因此选中 3.10 环境,报错却出现在行情刷新那步,看着像行情源坏了)。
|
2026-09-11 14:37:20 +08:00
|
|
|
|
|
2026-09-09 23:40:35 +08:00
|
|
|
|
> 版本:v1.0
|
|
|
|
|
|
> 目标:将外部基金行情能力接入公共 MVC+S 底座,供客服、投顾、风控 Agent 统一只读使用。
|
|
|
|
|
|
> 当前范围:场内基金模拟交易相关行情;场外运营数据不纳入本计划。
|
|
|
|
|
|
|
|
|
|
|
|
## 一、建设目标
|
|
|
|
|
|
|
|
|
|
|
|
将外部行情脚本改造成公共底座能力:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
东方财富 Adapter
|
|
|
|
|
|
→ FundQuoteService
|
|
|
|
|
|
→ ToolRegistry / ToolExecutor
|
|
|
|
|
|
→ 客服、投顾、风控 Agent
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
业务组员只调用统一工具,不直接访问东方财富接口、HTTP 客户端、缓存或数据库。
|
|
|
|
|
|
|
|
|
|
|
|
## 二、明确不做的事情
|
|
|
|
|
|
|
|
|
|
|
|
- 第一阶段不修改已有数据库表、字段和交易流程;
|
|
|
|
|
|
- 不把行情快照写入场内交易表;
|
|
|
|
|
|
- 不实现代客下单、成交确认或持仓修改;
|
|
|
|
|
|
- 不让 Agent 直接调用外部行情接口;
|
|
|
|
|
|
- 不在本阶段创建可运行示例 Agent;
|
|
|
|
|
|
- 不把场外基金运营数据混入本行情服务。
|
|
|
|
|
|
|
|
|
|
|
|
## 三、现有脚本拆分映射
|
|
|
|
|
|
|
|
|
|
|
|
| 原 `hq.py` 能力 | 新底座位置 | 处理要求 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| 东方财富 HTTP 调用 | `app/infrastructure/fund_market_adapter.py` | 改为异步、超时、重试、响应校验 |
|
|
|
|
|
|
| 交易时段判断 | `FundQuoteService` | 固定北京时间并可测试注入时钟 |
|
|
|
|
|
|
| 基金名称缓存 | 行情缓存适配器 | 允许 Redis,不使用进程全局作为唯一缓存 |
|
|
|
|
|
|
| 历史净值和收益率合并 | `app/service/fund_quote_service.py` | 统一 DTO、Decimal、降级标识 |
|
|
|
|
|
|
| 中文字典输出 | Controller/Agent 展示层 | 内部 DTO 使用英文稳定字段 |
|
2026-09-12 12:20:01 +08:00
|
|
|
|
| 固定南方基金代码 | 配置或代码白名单 | 先保留白名单,后续配置中心化 |
|
2026-09-09 23:40:35 +08:00
|
|
|
|
|
|
|
|
|
|
## 四、阶段计划
|
|
|
|
|
|
|
|
|
|
|
|
### F0:基线和契约冻结
|
|
|
|
|
|
|
|
|
|
|
|
**目标**:确认接入不会破坏现有底座和数据库规则。
|
|
|
|
|
|
|
|
|
|
|
|
**任务**:
|
|
|
|
|
|
|
|
|
|
|
|
- 对照 `AGENTS.md`、00 基线和 02 建表设计;
|
|
|
|
|
|
- 确认本阶段只新增 Python 模块、工具注册和测试;
|
|
|
|
|
|
- 定义 `FundQuoteQuery`、`FundQuote`、`FundQuoteSource` DTO;
|
|
|
|
|
|
- 定义错误分类:外部行情不可用返回可识别的降级结果,不泄露供应商异常;
|
|
|
|
|
|
- 确认行情工具权限码为 `fund:quote:read`。
|
|
|
|
|
|
|
|
|
|
|
|
**验收**:
|
|
|
|
|
|
|
|
|
|
|
|
- DTO 字段、类型和空值规则确定;
|
|
|
|
|
|
- 没有数据库迁移;
|
|
|
|
|
|
- 没有修改既有表和字段;
|
|
|
|
|
|
- 接口契约可被 Service、ToolExecutor 和测试共同引用。
|
|
|
|
|
|
|
|
|
|
|
|
### F1:外部行情 Adapter
|
|
|
|
|
|
|
|
|
|
|
|
**目标**:把 `hq.py` 的外部请求改造成底座 Infrastructure 适配器。
|
|
|
|
|
|
|
|
|
|
|
|
**新增文件**:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
app/infrastructure/fund_market_adapter.py
|
|
|
|
|
|
app/core/fund_contracts.py
|
|
|
|
|
|
tests/unit/infrastructure/test_fund_market_adapter.py
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**任务**:
|
|
|
|
|
|
|
|
|
|
|
|
- 使用 `httpx.AsyncClient`,禁止同步网络调用阻塞 Worker;
|
|
|
|
|
|
- 封装名称、实时行情、历史净值、区间收益率四类请求;
|
|
|
|
|
|
- 统一请求超时、有限重试和指数退避;
|
|
|
|
|
|
- 校验外部 JSON 结构和基金代码;
|
|
|
|
|
|
- 将 HTTP、JSON、字段缺失转换为 `RecoverableAgentError` 或降级结果;
|
|
|
|
|
|
- 供应商日志只记录接口类别、基金数量和异常类型,不记录完整响应正文;
|
|
|
|
|
|
- 使用 `Decimal` 保存净值和收益率;
|
|
|
|
|
|
- 所有时间统一使用 Asia/Shanghai 语义。
|
|
|
|
|
|
|
|
|
|
|
|
**验收**:
|
|
|
|
|
|
|
|
|
|
|
|
- 外部接口成功、超时、HTTP 错误、非法 JSON、字段缺失均有测试;
|
|
|
|
|
|
- 测试不依赖真实东方财富网络,使用 `httpx.MockTransport`;
|
|
|
|
|
|
- Adapter 不导入 Agent、Controller 或数据库 Session。
|
|
|
|
|
|
|
|
|
|
|
|
### F2:FundQuoteService
|
|
|
|
|
|
|
|
|
|
|
|
**目标**:将多个外部接口结果合并成稳定的行情业务结果。
|
|
|
|
|
|
|
|
|
|
|
|
**新增文件**:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
app/service/fund_quote_service.py
|
|
|
|
|
|
tests/unit/service/test_fund_quote_service.py
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**任务**:
|
|
|
|
|
|
|
|
|
|
|
|
- 实现按基金代码、基金类型、日期和数量查询;
|
|
|
|
|
|
- 保留场内基金白名单;
|
|
|
|
|
|
- 合并实时行情和历史净值;
|
|
|
|
|
|
- 盘中使用实时行情,非盘中使用最新收盘净值;
|
|
|
|
|
|
- 返回 `quote_source`、`quote_time`、`is_intraday`、`degraded`;
|
|
|
|
|
|
- 单个产品失败不得导致全部产品无原因失败;
|
|
|
|
|
|
- 外部行情不可用时返回结构化降级结果;
|
|
|
|
|
|
- 不将实时行情解释为成交、委托或持仓结果。
|
|
|
|
|
|
|
|
|
|
|
|
**验收**:
|
|
|
|
|
|
|
|
|
|
|
|
- 交易时段和非交易时段测试通过;
|
|
|
|
|
|
- 基金类型筛选、数量限制、非法日期和未知类型测试通过;
|
|
|
|
|
|
- 实时失败回退历史、历史失败返回降级结果的测试通过;
|
|
|
|
|
|
- Service 不直接解析 HTTP 响应细节。
|
|
|
|
|
|
|
|
|
|
|
|
### F3:缓存和健康状态
|
|
|
|
|
|
|
|
|
|
|
|
**目标**:降低外部接口压力,并让组员得到可识别的降级状态。
|
|
|
|
|
|
|
|
|
|
|
|
**任务**:
|
|
|
|
|
|
|
|
|
|
|
|
- 基金名称缓存建议 1 天;
|
|
|
|
|
|
- 收盘净值缓存建议 5-30 分钟;
|
|
|
|
|
|
- 实时行情缓存建议 30-60 秒;
|
|
|
|
|
|
- Redis 可用时使用 Redis,不可用时回退无缓存请求或结构化降级;
|
|
|
|
|
|
- 不将进程级字典作为多 Worker 唯一缓存;
|
|
|
|
|
|
- 增加行情数据源健康状态,不把供应商故障伪装成正常行情;
|
|
|
|
|
|
- 记录数据源成功率、延迟、降级次数和最后成功时间。
|
|
|
|
|
|
|
|
|
|
|
|
**验收**:
|
|
|
|
|
|
|
|
|
|
|
|
- Redis 正常、Redis 不可用、缓存过期和缓存击穿测试通过;
|
|
|
|
|
|
- 缓存故障不阻塞主流程;
|
|
|
|
|
|
- 返回结果能够区分实时、缓存、收盘和降级来源。
|
|
|
|
|
|
|
|
|
|
|
|
### F4:注册公共只读工具
|
|
|
|
|
|
|
|
|
|
|
|
**目标**:让所有业务 Agent 通过统一工具使用行情。
|
|
|
|
|
|
|
|
|
|
|
|
**工具契约**:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
工具名:query_fund_quote
|
|
|
|
|
|
权限码:fund:quote:read
|
|
|
|
|
|
只读:是
|
|
|
|
|
|
允许角色:customer、advisor、operator、risk_operator、admin
|
|
|
|
|
|
超时:5 秒
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**任务**:
|
|
|
|
|
|
|
|
|
|
|
|
- 定义严格的 Pydantic 输入模型;
|
|
|
|
|
|
- 在 `bootstrap` 中注册 `ToolDefinition`;
|
|
|
|
|
|
- 由 `ToolExecutor` 统一检查权限、角色、意图白名单和超时;
|
|
|
|
|
|
- 结果自动生成工具来源引用和审计记录;
|
|
|
|
|
|
- `AgentDefinition.allowed_tools` 只能缩小权限;
|
|
|
|
|
|
- 业务 Agent 不能绕过工具调用 Adapter。
|
|
|
|
|
|
|
|
|
|
|
|
**验收**:
|
|
|
|
|
|
|
|
|
|
|
|
- 正常调用、未授权、未列入意图白名单、参数错误、超时和外部失败均有测试;
|
|
|
|
|
|
- 工具结果不含供应商原始异常和敏感配置;
|
|
|
|
|
|
- 来源引用能通过公共合规审查。
|
|
|
|
|
|
|
|
|
|
|
|
### F5:配置中心和管理规则
|
|
|
|
|
|
|
|
|
|
|
|
**目标**:把基金白名单、数据源开关、缓存时间和限流参数逐步纳入配置治理。
|
|
|
|
|
|
|
|
|
|
|
|
**第一阶段**:
|
|
|
|
|
|
|
|
|
|
|
|
- 外部接口地址、超时和密钥引用写入 `.env` 或运行配置;
|
|
|
|
|
|
- 基金白名单保留在代码常量,避免未经审核扩大产品范围。
|
|
|
|
|
|
|
|
|
|
|
|
**后续阶段**:
|
|
|
|
|
|
|
|
|
|
|
|
- 使用 `platform_config_item` 增加行情配置命名空间;
|
|
|
|
|
|
- 配置发布、审核、激活、回滚必须经过已有配置中心;
|
|
|
|
|
|
- 配置变更写审计并通过 Outbox 通知缓存失效;
|
|
|
|
|
|
- 不通过配置中心扩大 Agent 的工具权限上限。
|
|
|
|
|
|
|
|
|
|
|
|
**验收**:
|
|
|
|
|
|
|
|
|
|
|
|
- 配置缺失时失败关闭或使用明确的安全默认值;
|
|
|
|
|
|
- 密钥只使用 `secret_ref`,不返回明文;
|
|
|
|
|
|
- 发布版本可追溯、可回滚。
|
|
|
|
|
|
|
|
|
|
|
|
**当前实现**:`RuntimeConfigService.fund_quote()` 读取当前激活版本的
|
|
|
|
|
|
`namespace=fund_market、config_key=default`;`query_fund_quote` 工具加载该配置,
|
|
|
|
|
|
`FundQuoteRuntimeConfig` 校验基金白名单和缓存 TTL。配置缺失、类型错误或配置中心异常时使用代码
|
|
|
|
|
|
默认值,不会因配置中心异常导致行情功能整体不可用。
|
|
|
|
|
|
|
|
|
|
|
|
### F6:业务组员接入
|
|
|
|
|
|
|
|
|
|
|
|
**目标**:客服、投顾和风控 Agent 只消费统一行情工具。
|
|
|
|
|
|
|
|
|
|
|
|
**组员需要做**:
|
|
|
|
|
|
|
|
|
|
|
|
- 在 `AgentDefinition.allowed_tools` 声明 `query_fund_quote`;
|
|
|
|
|
|
- 在对应意图白名单中声明 `fund_quote`;
|
|
|
|
|
|
- 在 `handle()` 中调用 `self.call_tool(...)`;
|
|
|
|
|
|
- 对行情缺失、降级和非交易时段给出业务解释;
|
|
|
|
|
|
- 补充本业务域的权限和边界测试。
|
|
|
|
|
|
|
|
|
|
|
|
**组员禁止做**:
|
|
|
|
|
|
|
|
|
|
|
|
- 直接导入 `hq.py`;
|
|
|
|
|
|
- 直接调用东方财富 URL;
|
|
|
|
|
|
- 自己读取行情缓存或解析外部 JSON;
|
|
|
|
|
|
- 根据行情直接下单或修改交易数据;
|
|
|
|
|
|
- 把行情数据写入交易表。
|
|
|
|
|
|
|
|
|
|
|
|
### F7:是否落行情快照表
|
|
|
|
|
|
|
|
|
|
|
|
**判断条件**:只有在需要历史回测、监管留痕、行情对账或跨进程长期查询时才进入本阶段。
|
|
|
|
|
|
|
|
|
|
|
|
**候选新增表**:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
fund_quote_snapshot
|
|
|
|
|
|
fund_quote_source_record
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**前置要求**:
|
|
|
|
|
|
|
|
|
|
|
|
- 先更新 00、02 数据库文档;
|
|
|
|
|
|
- 证明没有修改任何既有表名和字段定义;
|
|
|
|
|
|
- 设计唯一键、来源、采集时间、有效时间和幂等策略;
|
|
|
|
|
|
- 增加 Alembic 迁移、schema fingerprint 和真实 MySQL 验收;
|
|
|
|
|
|
- 只新增表,不改写场内交易表。
|
|
|
|
|
|
|
|
|
|
|
|
## 五、建议开发顺序和完成标准
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
F0 契约冻结
|
|
|
|
|
|
→ F1 Adapter
|
|
|
|
|
|
→ F2 Service
|
|
|
|
|
|
→ F3 缓存/健康
|
|
|
|
|
|
→ F4 公共工具
|
|
|
|
|
|
→ F5 配置治理
|
|
|
|
|
|
→ F6 业务接入
|
|
|
|
|
|
→ F7 可选落库
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
MVP 完成标准:F0-F4 全部通过;不要求新增数据库表。长期版本再评估 F5-F7。
|
|
|
|
|
|
|
|
|
|
|
|
## 六、测试命令
|
|
|
|
|
|
|
|
|
|
|
|
```powershell
|
|
|
|
|
|
python -m pytest tests/unit/infrastructure/test_fund_market_adapter.py tests/unit/service/test_fund_quote_service.py -q -p no:cacheprovider
|
|
|
|
|
|
python -m pytest -q -p no:cacheprovider
|
|
|
|
|
|
python -m ruff check app tests tools alembic
|
|
|
|
|
|
python -m mypy app
|
|
|
|
|
|
python tools/audit_schema.py
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
任何数据库变更都必须额外执行 schema fingerprint,并在 TODO 中记录基线对比结果。
|