feat(data): 510300 回填真实值并在产品页披露;511810 口径查清
按项目方决定处理三件事。 ## 510300(沪深300ETF) 决定:保留 fund_manager = "南方基金" 作演示口径,**不**改成"华泰柏瑞" (改了它会退出 MarketQuoteSyncService 的同步范围,投顾与知识条目也不该再算自家产品), 但净值与费率改成真实值,并在产品页显式披露它不是本公司产品。 - 回填(tools/backfill_product_snapshot.py --codes 510300 --apply --overwrite): current_nav 4.500000 → 4.5794、current_nav_at 同步为 2026-09-11 15:00:00、 management_fee_rate 0.5000 → 0.15、custodian_fee_rate 0.1000 → 0.05 - 产品页披露:mock-data.js 给这只加 product_note,详情页新增提示块渲染它 ("本产品为同指数参考产品,非本公司发行的基金,仅用于功能演示")。 同时把 mock 里这只的净值/费率也对齐真实值,避免出现"页面显示 mock 值、 库里是真实值"两套数字。 - 产品详情页的 css/js 版本参数提到 20260913-5,避免旧缓存。 ## 511810(货币ETF南方) 之前说它"三个数量级不一致"是**我比错了接口**:QUOTE_API 返的是**场内交易价格** (货币 ETF 约 100 元/份),而 current_nav 的口径是 DWJZ 单位净值。 查证结果:库里 0.2661 正是 09-11 的真实 DWJZ,快照时间也对得上 —— 不是脏数据,是旧值。 口径由其余 19 只的一致性确定(current_nav == DWJZ)。 要不要刷新到最新(0.2332)属运维节奏问题,**本次未执行**。 ## 最小交易金额 按项目方口径:一律**以产品字段为准**(100.00 元),不用「1 手 × 净值」推算金额 —— 后者会与字段值不一致,客户会看到两套数字。docs/42 的 1.1 节据此重写。 ## 工具改名 tools/backfill_product_fees.py → tools/backfill_product_snapshot.py:扩展为 净值 + 费率统一入口,新增 --codes 限定范围(避免顺手把全部快照刷成最新, 全量刷新是 ProductHistorySyncService / MarketQuoteSyncService 的职责)。 安全设计不变:默认 dry-run、默认只补 NULL(净值永不为空,所以刷净值需显式 --overwrite)、写入走单事务。 门禁:ruff 通过 / mypy 249 文件 0 错 / 单元+契约 1377 passed 2 skipped。
This commit is contained in:
@@ -15,6 +15,8 @@ const PRODUCT_BASE = {
|
||||
single_investor_max_holding_ratio: '10.0000',
|
||||
management_fee_rate: '0.5000',
|
||||
custodian_fee_rate: '0.1000',
|
||||
//: 产品级说明,默认无。只有需要额外披露的条目才填,例如「同指数参考产品」。
|
||||
product_note: null,
|
||||
risk_disclosure_required: 1,
|
||||
second_confirmation_required: 0,
|
||||
recording_required: 0,
|
||||
@@ -24,7 +26,15 @@ const PRODUCT_BASE = {
|
||||
};
|
||||
|
||||
export const MOCK_PRODUCTS = Object.freeze([
|
||||
{ ...PRODUCT_BASE, id: 1, product_code: '510300', product_name: '南方沪深300ETF', current_nav: '4.218000', current_nav_at: '2026-09-11T15:00:00', product_category: '宽基ETF', risk_level: 'R3' },
|
||||
{
|
||||
...PRODUCT_BASE, id: 1, product_code: '510300', product_name: '南方沪深300ETF',
|
||||
current_nav: '4.579400', current_nav_at: '2026-09-11T15:00:00',
|
||||
product_category: '宽基ETF', risk_level: 'R3',
|
||||
management_fee_rate: '0.1500', custodian_fee_rate: '0.0500',
|
||||
// 这只的代码 510300 在真实市场上对应的是**另一家管理人**的沪深300ETF,
|
||||
// 所以必须显式披露:否则客户会以为它是本公司发行的产品。
|
||||
product_note: '本产品为同指数参考产品,非本公司发行的基金,仅用于功能演示;净值与费率取自公开数据源。',
|
||||
},
|
||||
{ ...PRODUCT_BASE, id: 2, product_code: '510500', product_name: '南方中证500ETF', current_nav: '6.742000', current_nav_at: '2026-09-11T15:00:00', product_category: '宽基ETF', risk_level: 'R4' },
|
||||
{ ...PRODUCT_BASE, id: 3, product_code: '159915', product_name: '南方创业板ETF', current_nav: '2.184000', current_nav_at: '2026-09-11T15:00:00', product_category: '成长ETF', risk_level: 'R4', exchange_code: 'SZSE' },
|
||||
{ ...PRODUCT_BASE, id: 4, product_code: '512100', product_name: '南方中证1000ETF', current_nav: '2.367000', current_nav_at: '2026-09-11T15:00:00', product_category: '宽基ETF', risk_level: 'R4' },
|
||||
|
||||
@@ -5,18 +5,19 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>基金详情 · 南方财富</title>
|
||||
<link rel="stylesheet" href="/static/portal/common/base.css">
|
||||
<link rel="stylesheet" href="/static/portal/guest/product-detail/product-detail.css?v=20260913-4">
|
||||
<link rel="stylesheet" href="/static/portal/guest/product-detail/product-detail.css?v=20260913-5">
|
||||
</head>
|
||||
<body>
|
||||
<main id="main-content" class="page-shell" data-detail-root>
|
||||
<div class="page-heading"><div><a class="detail-back" href="/portal/guest/products/">返回基金列表</a><h1 class="page-heading__title" data-product-name>基金详情</h1><p class="page-heading__description" data-product-meta></p></div><div class="page-heading__actions"><a class="button" href="/portal/customer/login/">登录后交易</a></div></div>
|
||||
<div class="source-notice" role="note" data-source-notice></div>
|
||||
<div class="product-note" role="note" data-product-note hidden></div>
|
||||
<section class="detail-grid">
|
||||
<article class="panel detail-chart"><div class="panel__header"><h2 class="panel__title">历史净值</h2><span class="section-heading__meta">近十二期</span></div><div class="panel__body"><div class="detail-chart__quote"><strong data-current-nav>--</strong><span data-change>--</span></div><svg class="detail-chart__svg" viewBox="0 0 760 280" role="img" aria-label="基金历史净值走势" data-chart></svg><div class="detail-chart__axis"><span data-start-date></span><span data-end-date></span></div></div></article>
|
||||
<aside class="panel"><div class="panel__header"><h2 class="panel__title">产品要素</h2></div><div class="panel__body detail-facts" data-product-facts></div></aside>
|
||||
</section>
|
||||
<section class="panel risk-disclosure"><div class="panel__header"><h2 class="panel__title">风险与交易说明</h2></div><div class="panel__body"><div class="risk-disclosure__grid"><div><strong>风险等级</strong><span data-risk-level></span></div><div><strong>最小金额</strong><span data-min-amount></span></div><div><strong>交易方式</strong><span>场内市价模拟成交</span></div></div><p>基金净值会随市场变化。历史数据不代表未来表现,交易前应结合自身风险承受能力判断。</p></div></section>
|
||||
</main>
|
||||
<script type="module" src="/static/portal/guest/product-detail/product-detail.js?v=20260913-4"></script>
|
||||
<script type="module" src="/static/portal/guest/product-detail/product-detail.js?v=20260913-5"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.detail-back { display: inline-block; margin-bottom: var(--space-3); color: var(--brand-dark); font-size: var(--fs-small); }
|
||||
.source-notice { margin-bottom: var(--space-4); padding: var(--space-3) var(--space-4); color: var(--ink-soft); background: var(--accent-soft); border-left: 3px solid var(--accent); font-size: var(--fs-small); line-height: 1.6; }
|
||||
.product-note { margin-bottom: var(--space-4); padding: var(--space-3) var(--space-4); color: var(--brand-dark); background: var(--surface); border: 1px solid var(--line); border-left: 3px solid var(--brand); border-radius: var(--radius-sm); font-size: var(--fs-small); line-height: 1.6; }
|
||||
.detail-grid { display: grid; grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); gap: var(--space-4); }
|
||||
.detail-chart__quote { display: flex; align-items: baseline; gap: var(--space-3); }
|
||||
.detail-chart__quote strong { font-size: var(--fs-metric); }
|
||||
|
||||
@@ -13,6 +13,12 @@ document.querySelector('[data-product-name]').textContent = product.product_name
|
||||
document.querySelector('[data-product-meta]').textContent = `${product.product_code} · ${product.exchange_code} · ${product.product_category}`;
|
||||
// 详情页含历史净值曲线,全部来自 common/mock-data.js(公开产品 HTTP 接口尚未实现)。
|
||||
document.querySelector('[data-source-notice]').textContent = MOCK_SOURCE_NOTICE;
|
||||
// 产品级披露:只有带 product_note 的条目才显示(如"同指数参考产品")。
|
||||
const noteNode = document.querySelector('[data-product-note]');
|
||||
if (product.product_note) {
|
||||
noteNode.textContent = product.product_note;
|
||||
noteNode.hidden = false;
|
||||
}
|
||||
document.querySelector('[data-current-nav]').textContent = product.current_nav;
|
||||
const changeNode = document.querySelector('[data-change]');
|
||||
changeNode.textContent = formatPercent(change);
|
||||
|
||||
Reference in New Issue
Block a user