fix(demo): 把未上市的 160129 换成真实挂牌的 515450,并修掉种子假行情盖住真行情

## 1. 160129 本就不该在场内清单里

`160129`(南方金利定开债券C)是 `160128`(金利定开债券 **A** 类)的 C 类份额,
而 C 类份额只在场外销售、**不在交易所挂牌** —— 行情源对它永远返回空,下单只能走
净值降级(`source=eastmoney_nav_fallback`),既掩盖了"该产品并不交易"这一事实,
又让成交价带上折溢价偏差。

选型依据:把南方基金全部 **882 个代码**逐个问过腾讯行情源(该源只对交易所上市证券
返回数据),确认真实上市交易的只有 **109 个**;其中 R1/R2 的场内产品
(159700、160128、511070、511810)**原先已在清单内**,所以替换品只能来自 R3 及以上。
选定 **`515450` 红利低波50ETF南方**:仍是南方基金旗下、成交额约 1.3 亿元流动性充足、
红利低波定位偏稳健,与它替换掉的债券 LOF 定位最接近;客户风险等级已覆盖 R3
(原有 `510300` 即 R3)。

改动:`hq.py` 的 `FUND_TYPE_GROUPS`、`tools/import_hq_test_products.py` 的场内映射,
两处都留了注释防止被加回来;`docs/42` / `docs/43` 的产品表与费率表同步
(净值 1.4027、管理费 0.50%、托管费 0.10%);`docs/42` 另加了一条决策记录。

库内用**复用 `fin_product.id = 9100005`** 的方式替换,使 `fin_holding` /
`fin_sim_order` / `fin_transaction` / `fin_market_price` 的既有引用自动跟随,
不产生孤儿数据。那笔历史成交保留 `quote_source='eastmoney_nav_fallback'` ——
它确实是当时的事实,不该被粉饰。同时清掉了 `fin_market_price` 里那条净值降级行,
全库净值降级行情行数归零。

## 2. 种子假行情会盖住真实行情(更严重,且会反复出现)

`seed_sim_account_demo.py` 原来按"今天"upsert 一条假行情(510300=4.50、
510500=6.20,`source='eastmoney_demo_seed'`),而真实行情来自行情源、日期是
**最近交易日**。下单按 `trade_date DESC` 取行情,这条种子行**永远排在真实行情前面**;
它的 `source_updated_at` 只是 seed 运行时刻,过了 `MAX_QUOTE_AGE`(15 分钟)就让整个
产品变成 `503 行情已过期` —— 而库里明明躺着一条刚同步好的真实行情。周末尤其明显:
`trade_date` 落在**非交易日**,价格还是编的。

实测表现:`tools/seed_demo_data.py` 跑完第 4 步刚同步过真实行情,冒烟脚本的下单仍报
`503`,且本该 4.579 的成交价被查到 4.50。

改为:**已有任何行情行就绝不插手**(真实行情优先,种子不参与竞争);
一条都没有时才补,且 `trade_date` 退到最近交易日。

验证:`tools/e2e_smoke_test.py` → **40/40**(下单成交价 4.579 = 真实行情);
ruff 通过;mypy 250 文件 0 错;unit+contract 1381 passed / 0 failed。
This commit is contained in:
2026-09-13 22:28:42 +08:00
parent 4629e7d5dd
commit f6bfcc26b3
6 changed files with 62 additions and 31 deletions
+26 -2
View File
@@ -88,11 +88,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 |
| 515450 | 红利低波50ETF南方 | SSE | ETF | R3 | 1.4027 | -0.43% | 2026-09-11 |
| 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 |
@@ -106,6 +106,30 @@
> (库里存 6 位小数,如 `0.916700`,数据源给 4 位 `0.9167`,是同一笔数),
> **2 只不一致** —— `510300` 与 `511810`,见下方 3.1 与第四节。
> **2026-09-13 变更:`160129` → `515450`**
>
> 原 `160129`(南方金利定开债券C)已从清单移除,替换为 **`515450` 红利低波50ETF南方**
> (SSE / ETF / R3,管理费 0.50%、托管费 0.10%,净值 1.4027)。
>
> **为什么必须换**:`160129` 是 `160128`(金利定开债券 **A** 类)的 **C 类份额**,
> 而 C 类份额只在场外销售、**不在交易所挂牌** —— 行情源对它永远返回空,下单只能走
> 净值降级(`source=eastmoney_nav_fallback`)。这既掩盖了"该产品并不交易"这一事实,
> 又让成交价带上折溢价偏差。**它本就不该出现在场内清单里。**
>
> **选型依据**:把南方基金全部 **882 个代码**逐个问过腾讯行情源(该源只对交易所上市
> 证券返回数据),确认真实上市交易的只有 **109 个**;其中 R1/R2 的场内产品
> (159700、160128、511070、511810)**原先已在清单内**,所以替换品只能来自 R3 及以上。
> 选 `515450` 是因为它**仍属南方基金**、成交额约 1.3 亿元流动性充足、红利低波定位偏
> 稳健,与它替换掉的债券 LOF 定位最接近;客户风险等级已覆盖 R3(原有 `510300` 即 R3)。
>
> **处理方式**:复用 `fin_product.id = 9100005` 而非删除重建,使 `fin_holding` /
> `fin_sim_order` / `fin_transaction` / `fin_market_price` 的既有引用自动跟随,
> 不产生孤儿数据。那笔历史成交保留 `quote_source='eastmoney_nav_fallback'` ——
> 它确实是当时的事实,不该被粉饰。
>
> 同时移出的还有 `hq.py` 的 `FUND_TYPE_GROUPS`(`160129` 原在"债券型"组)
> 与 `tools/import_hq_test_products.py` 的场内映射,两处都留了注释防止被加回来。
问:平台上有哪些场内基金?产品代码是什么?
答:本平台模拟交易范围内共有 20 只场内基金,包含 13 只 ETF 和 7 只 LOF,
@@ -241,7 +265,6 @@ python tools/backfill_product_snapshot.py --codes 511810 --apply --overwrite
| 160105 | 南方积极配置混合(LOF) | 1.20 | 0.20 | — |
| 160127 | 南方新兴消费增长股票(LOF)A | 1.20 | 0.20 | — |
| 160128 | 南方金利定开债券A | 0.50 | 0.15 | — |
| 160129 | 南方金利定开债券C | 0.50 | 0.15 | — |
| 160142 | 南方优势产业(LOF) | 1.20 | 0.20 | — |
| 160143 | 南方创业板2年定期开放混合 | 1.20 | 0.20 | — |
| 501018 | 南方原油A | 1.00 | 0.20 | — |
@@ -250,6 +273,7 @@ python tools/backfill_product_snapshot.py --codes 511810 --apply --overwrite
| 510500 | 中证500ETF南方 | 0.15 | 0.05 | — |
| 511070 | 公司债ETF南方 | 0.15 | 0.05 | — |
| 511810 | 货币ETF南方 | 0.30 | 0.05 | **0.25** |
| 515450 | 红利低波50ETF南方 | 0.50 | 0.10 | — |
| 588890 | 科创芯片ETF南方 | 0.50 | 0.10 | — |
**规律**:宽基/债券 ETF 多为 0.15% 管理费,行业主题 ETF 多为 0.50%,
@@ -86,11 +86,11 @@ LOF 与主动管理型较高(如南方积极配置混合 1.20%/年、托管费
| 510500 | 中证500ETF南方 | 上交所 | ETF | R4 | 7.6027 | 2026-09-11 |
| 511070 | 公司债ETF南方 | 上交所 | ETF | R2 | 103.0145 | 2026-09-11 |
| 511810 | 货币ETF南方 | 上交所 | ETF | R1 | 0.2661 | 2026-09-11 |
| 515450 | 红利低波50ETF南方 | 上交所 | ETF | R3 | 1.4027 | 2026-09-11 |
| 588890 | 科创芯片ETF南方 | 上交所 | ETF | R4 | 1.2327 | 2026-09-11 |
| 160105 | 南方积极配置混合(LOF) | 深交所 | LOF | R3 | 1.2514 | 2026-09-11 |
| 160127 | 南方新兴消费增长股票(LOF)A | 深交所 | LOF | R4 | 0.8364 | 2026-09-11 |
| 160128 | 南方金利定开债券A | 深交所 | LOF | R2 | 1.0260 | 2026-09-11 |
| 160129 | 南方金利定开债券C | 深交所 | LOF | R2 | 1.0240 | 2026-09-11 |
| 160142 | 南方优势产业(LOF) | 深交所 | LOF | R3 | 1.0623 | 2026-09-11 |
| 160143 | 南方创业板2年定期开放混合 | 深交所 | LOF | R3 | 1.6105 | 2026-09-11 |
| 501018 | 南方原油A | 上交所 | LOF | R5 | 2.0750 | 2026-09-10 |
@@ -139,11 +139,11 @@ LOF 与主动管理型较高(如南方积极配置混合 1.20%/年、托管费
| 510500 | 中证500ETF南方 | 0.15 | 0.05 | — |
| 511070 | 公司债ETF南方 | 0.15 | 0.05 | — |
| 511810 | 货币ETF南方 | 0.30 | 0.05 | 0.25 |
| 515450 | 红利低波50ETF南方 | 0.50 | 0.10 | — |
| 588890 | 科创芯片ETF南方 | 0.50 | 0.10 | — |
| 160105 | 南方积极配置混合(LOF) | 1.20 | 0.20 | — |
| 160127 | 南方新兴消费增长股票(LOF)A | 1.20 | 0.20 | — |
| 160128 | 南方金利定开债券A | 0.50 | 0.15 | — |
| 160129 | 南方金利定开债券C | 0.50 | 0.15 | — |
| 160142 | 南方优势产业(LOF) | 1.20 | 0.20 | — |
| 160143 | 南方创业板2年定期开放混合 | 1.20 | 0.20 | — |
| 501018 | 南方原油A | 1.00 | 0.20 | — |