feat(tools): 新增 fetch_live_quotes 拉真实行情并核对库里快照

项目本来就对接了东方财富真实行情(app/infrastructure/fund_market_adapter.py:
push2.eastmoney.com 的行情接口 + api.fund.eastmoney.com 的历史净值接口),
下单链路取 quote_price/quote_source 走的也是它。之前做知识草稿时用的是
fin_product 表里的快照,没有去核对真实数据源,这次补上。

新增 tools/fetch_live_quotes.py:
- 用项目自带的 EastmoneyFundAdapter 取数,不自己拼 HTTP、不引第三方行情库
- 产品范围与 MarketQuoteSyncService.sync 一致(南方基金 / SSE,SZSE / 上市)
- 逐只对比真实净值与 fin_product.current_nav,只报告差异、**不写库**
  (刷新快照是 ProductHistorySyncService / MarketQuoteSyncService 的职责)

实测结果(2026-09-13,20 只全部取到真实数据):
- 18 只完全一致(库里 6 位小数、数据源 4 位,是同一笔数)
- 2 只不一致:
  · 510300 沪深300ETF:库里 4.500000,真实 4.5794。库里快照时间是今天 08:19:37,
    其余 19 只都停在 09-11 15:00:00 —— 说明这是被人为改过的演示值(4.5 凑"1 手 450 元")。
    需要定:知识用真实值还是演示值;若交易按库里的 4.5 成交而知识说 4.5794,两处口径会打架。
  · 511810 货币ETF南方:库里 0.266100,NAV_API 给 0.2332,而 QUOTE_API(场内实时价)
    给 100.012 —— 差三个数量级,说明两个接口对这只的含义不同,需人工核实口径。

docs/42 随之更新:
- 产品清单改用真实净值,并加上当日涨跌与净值日期,注明取数来源与复现命令
- 3.1 节的沪深300ETF 条目改用真实值 4.5794,并列出两处待确认口径
- 新增 4.2 节记录本次核对结果与两只不一致项的判断依据
This commit is contained in:
2026-09-13 19:27:58 +08:00
parent f72a545c39
commit 131effcfae
2 changed files with 230 additions and 28 deletions
+73 -28
View File
@@ -3,7 +3,10 @@
> **状态**:草稿。**尚未写入知识库** —— 审核通过后再走
> `POST /api/v1/knowledge/documents` 入库。
>
> **数据来源**:`fin_product` 表导出(2026-09-13),共 20 只(ETF 13 + LOF 7)。
> **数据来源**:**净值取自东方财富真实行情** —— 走项目自带的
> `app/infrastructure/fund_market_adapter.py`(与下单链路、`MarketQuoteSyncService` 同源),
> 取数时间 2026-09-13;产品属性(代码/名称/交易所/风险等级/交易单位)取自 `fin_product` 表。
> 共 20 只(ETF 13 + LOF 7)。复现命令:`python tools/fetch_live_quotes.py`。
> **原则**:只写表里**确实有值**的字段;缺失的字段一律写"以交易页面为准",
> **不补编数字**(编费率是最容易出事的一类错误)。
>
@@ -64,28 +67,36 @@
## 二、产品清单(20 只,数据来自 `fin_product`)
| 代码 | 名称 | 交易所 | 类型 | 风险等级 | 最新净值 | 净值日期 |
|---|---|---|---|---|---|---|
| 159329 | 沙特ETF南方 | SZSE | ETF | R5 | 0.9167 | 2026-09-10 |
| 159382 | 创业板人工智能ETF南方 | SZSE | ETF | R4 | 2.5841 | 2026-09-11 |
| 159511 | 通信ETF南方 | SZSE | ETF | R4 | 2.3755 | 2026-09-11 |
| 159615 | 恒生生物科技ETF南方 | SZSE | ETF | R5 | 1.1027 | 2026-09-11 |
| 159687 | 亚太精选ETF南方 | SZSE | ETF | R5 | 1.8891 | 2026-09-11 |
| 159700 | 科创债ETF南方 | SZSE | ETF | R2 | 101.8009 | 2026-09-11 |
| 159948 | 创业板ETF南方 | SZSE | ETF | R4 | 3.6895 | 2026-09-11 |
| **510300** | **沪深300ETF** | **SSE** | **ETF** | **R3** | **4.5000** | 2026-09-11 |
| 510500 | 中证500ETF南方 | SSE | ETF | R4 | 7.6027 | 2026-09-11 |
| 511070 | 公司债ETF南方 | SSE | ETF | R2 | 103.0145 | 2026-09-11 |
| 511810 | 货币ETF南方 | SSE | ETF | R1 | 0.2661 | 2026-09-11 |
| 588890 | 科创芯片ETF南方 | SSE | ETF | R4 | 1.2327 | 2026-09-11 |
| 160105 | 南方积极配置混合(LOF) | SZSE | LOF | R3 | 1.2514 | 2026-09-11 |
| 160127 | 南方新兴消费增长股票(LOF)A | SZSE | LOF | R4 | 0.8364 | 2026-09-11 |
| 160128 | 南方金利定开债券A | SZSE | LOF | R2 | 1.0260 | 2026-09-11 |
| 160129 | 南方金利定开债券C | SZSE | LOF | R2 | 1.0240 | 2026-09-11 |
| 160142 | 南方优势产业(LOF) | SZSE | LOF | R3 | 1.0623 | 2026-09-11 |
| 160143 | 南方创业板2年定期开放混合 | SZSE | LOF | R3 | 1.6105 | 2026-09-11 |
| 501018 | 南方原油A | SSE | LOF | R5 | 2.0750 | 2026-09-11 |
| 501062 | 南方瑞合定开混合(LOF) | SSE | LOF | R3 | 2.1048 | 2026-09-11 |
| 代码 | 名称 | 交易所 | 类型 | 风险等级 | **真实净值** | 当日涨跌 | 净值日期 |
|---|---|---|---|---|---|---|---|
| 159329 | 沙特ETF南方 | SZSE | ETF | R5 | 0.9167 | -0.01% | 2026-09-10 |
| 159382 | 创业板人工智能ETF南方 | SZSE | ETF | R4 | 2.5841 | -0.49% | 2026-09-11 |
| 159511 | 通信ETF南方 | SZSE | ETF | R4 | 2.3755 | +0.61% | 2026-09-11 |
| 159615 | 恒生生物科技ETF南方 | SZSE | ETF | R5 | 1.1027 | -1.39% | 2026-09-11 |
| 159687 | 亚太精选ETF南方 | SZSE | ETF | R5 | 1.8891 | -0.38% | 2026-09-10 |
| 159700 | 科创债ETF南方 | SZSE | ETF | R2 | 101.8009 | +0.01% | 2026-09-11 |
| 159948 | 创业板ETF南方 | SZSE | ETF | R4 | 3.6895 | -0.49% | 2026-09-11 |
| **510300** | **沪深300ETF** | **SSE** | **ETF** | **R3** | **4.5794** | **-0.83%** | 2026-09-11 |
| 510500 | 中证500ETF南方 | SSE | ETF | R4 | 7.6027 | -1.79% | 2026-09-11 |
| 511070 | 公司债ETF南方 | SSE | ETF | R2 | 103.0145 | +0.01% | 2026-09-11 |
| 511810 | 货币ETF南方 | SSE | ETF | R1 | 0.2332 ⚠️ | 0.00% | 2026-09-13 |
| 588890 | 科创芯片ETF南方 | SSE | ETF | R4 | 1.2327 | -1.36% | 2026-09-11 |
| 160105 | 南方积极配置混合(LOF) | SZSE | LOF | R3 | 1.2514 | -1.34% | 2026-09-11 |
| 160127 | 南方新兴消费增长股票(LOF)A | SZSE | LOF | R4 | 0.8364 | -0.64% | 2026-09-11 |
| 160128 | 南方金利定开债券A | SZSE | LOF | R2 | 1.0260 | 0.00% | 2026-09-11 |
| 160129 | 南方金利定开债券C | SZSE | LOF | R2 | 1.0240 | 0.00% | 2026-09-11 |
| 160142 | 南方优势产业(LOF) | SZSE | LOF | R3 | 1.0623 | -2.09% | 2026-09-11 |
| 160143 | 南方创业板2年定期开放混合 | SZSE | LOF | R3 | 1.6105 | -1.14% | 2026-09-11 |
| 501018 | 南方原油A | SSE | LOF | R5 | 2.0750 | +3.86% | 2026-09-10 |
| 501062 | 南方瑞合定开混合(LOF) | SSE | LOF | R3 | 2.1048 | -0.49% | 2026-09-11 |
> **本表净值是真实数据**,取数时间 2026-09-13,来源为项目自带的东方财富适配器
> (`app/infrastructure/fund_market_adapter.py`:`push2.eastmoney.com` 行情 +
> `api.fund.eastmoney.com` 历史净值)。复现命令:`python tools/fetch_live_quotes.py`。
>
> **核对结果**:20 只全部取到真实数据;其中 **18 只与库里 `fin_product.current_nav` 完全一致**
> (库里存 6 位小数,如 `0.916700`,数据源给 4 位 `0.9167`,是同一笔数),
> **2 只不一致** —— `510300` 与 `511810`,见下方 3.1 与第四节。
问:平台上有哪些场内基金?产品代码是什么?
@@ -105,15 +116,21 @@
答:沪深300ETF 的产品代码为 **510300**,在**上交所(SSE)**挂牌,属于 ETF,
风险等级 **R3**。它按"手"交易,**1 手 = 100 份**,
按最新净值 4.5000 元估算,买入 1 手约需 **450 元**;
按最新净值 **4.5794 元**(2026-09-11)估算,买入 1 手约需 **458 元**;
产品库中登记的最小交易金额为 **100.00 元**。
该产品的管理费率为 **0.50%/年**、托管费率为 **0.10%/年**(从基金资产中计提,不单独向您收取)。
实际成交金额、可买数量与费用请以交易页面的实时报价为准。
> ⚠️ **待你确认(重要)**:这条里同时出现了"1 手约 450 元"和"最小交易金额 100.00 元",
> 两者不一致 —— 前者是按`lot_size=100 × 净值 4.5`推的,后者是 `fin_product.min_amount` 字段的原值。
> 请确认该以哪个为准,或者说明 `min_amount` 对场内产品只是历史字段、不参与场内校验。
> **在你确认之前,我建议回答里只保留"1 手 = 100 份、金额随市价变动",不写具体数字。**
> ⚠️ **两处待你确认**:
>
> 1. **净值口径**:这条用的是数据源真实值 **4.5794**,而库里 `fin_product.current_nav` 是
> **4.500000**,且快照时间是 **2026-09-13 08:19:37**(就是今天)—— 其余 19 只的快照
> 都停在 `2026-09-11 15:00:00`。**说明这条是被人为改过的演示值**(整数好算"450 元")。
> 知识条目该用真实值还是演示值,请你定;如果交易链路读的是库里那个值,
> 那么"知识说 4.5794、下单按 4.5 成交"会不一致。
> 2. **"1 手约 458 元"与"最小交易金额 100.00 元"仍不相等** —— 前者是 `lot_size=100 × 真实净值`,
> 后者是 `fin_product.min_amount` 原值。请确认该以哪个为准,或说明 `min_amount`
> 对场内产品不参与校验。**未确认前建议回答里只保留"1 手 = 100 份、金额随市价变动"。**
### 3.2 其他产品(简版模板)
@@ -141,6 +158,34 @@
| `risk_disclosure_required` 等三项 | 全部为 `0` | 与"严格风险等级匹配"的业务口径是否需要区分产品,待确认 |
| 产品"起投金额" | **表里没有这个字段** | 场内按手交易,本就没有固定起投金额;建议统一按 1.1 的口径回答 |
### 4.2 真实数据核对结果(2026-09-13)
用 `python tools/fetch_live_quotes.py` 拉东方财富真实行情,与库里 `fin_product.current_nav` 逐只比对:
| 结果 | 只数 | 说明 |
|---|---|---|
| 完全一致 | **18** | 库里存 6 位小数(`0.916700`)、数据源给 4 位(`0.9167`),是同一笔数 |
| 不一致 | **2** | 见下 |
**`510300 沪深300ETF`:库里 `4.500000` vs 真实 `4.5794`**
- 库里快照时间是 `2026-09-13 08:19:37`(就是今天),其余 19 只都停在 `2026-09-11 15:00:00`
- ⇒ 这一条是**被人为改过的演示值**(4.5 是整数,正好凑"1 手 = 450 元")
- 要决定的:知识条目用真实值还是演示值。**若交易链路按库里的 4.5 成交,
而知识说 4.5794,两处口径会打架** —— 这比"知识库有没有这条"更要紧
**`511810 货币ETF南方`:库里 `0.266100` vs 真实 `0.2332`**
- 这只的口径**本身可疑**:货币 ETF 通常以 **100 元/份**为面值(如 511990 华宝添益),
而 `0.2661` 和 `0.2332` 都不是这个量级
- 我第一次用 `QUOTE_API`(场内实时价)取它拿到的是 **100.012**,与 `NAV_API` 的 `0.2332`
**差三个数量级** —— 两个接口对这只的含义不同,**需要人工核实该用哪个口径**
- 建议:核实之前,知识条目**不要写这只的具体净值**
> 复现:`python tools/fetch_live_quotes.py`(默认表格对比,`--json` 输出结构化结果)。
> 该脚本只报告差异、**不写库** —— `fin_product.current_nav` 的刷新属于
> `ProductHistorySyncService` / `MarketQuoteSyncService` 的职责。
---
## 五、入库方式(审核通过后我执行)