按项目方决定处理三件事。 ## 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。
76 lines
4.2 KiB
JavaScript
76 lines
4.2 KiB
JavaScript
const PRODUCT_BASE = {
|
|
exchange_code: 'SSE',
|
|
product_category: 'ETF',
|
|
risk_level: 'R3',
|
|
fund_manager: '南方基金管理股份有限公司',
|
|
currency: 'CNY',
|
|
lot_size: '100.0000',
|
|
price_tick: '0.001000',
|
|
min_amount: '100.00',
|
|
open_start_at: null,
|
|
open_end_at: null,
|
|
open_period_start: null,
|
|
open_period_end: null,
|
|
transaction_fee_rate: '0.000500',
|
|
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,
|
|
status: '正常',
|
|
created_at: '2026-01-02T09:00:00',
|
|
updated_at: '2026-09-11T15:00:00',
|
|
};
|
|
|
|
export const MOCK_PRODUCTS = Object.freeze([
|
|
{
|
|
...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' },
|
|
{ ...PRODUCT_BASE, id: 5, product_code: '513100', product_name: '南方纳指ETF', current_nav: '1.728000', current_nav_at: '2026-09-11T15:00:00', product_category: '跨境ETF', risk_level: 'R5' },
|
|
{ ...PRODUCT_BASE, id: 6, product_code: '511360', product_name: '南方短融ETF', current_nav: '110.126000', current_nav_at: '2026-09-11T15:00:00', product_category: '债券ETF', risk_level: 'R2' },
|
|
{ ...PRODUCT_BASE, id: 7, product_code: '159645', product_name: '南方新能源ETF', current_nav: '0.864000', current_nav_at: '2026-09-11T15:00:00', product_category: '行业ETF', risk_level: 'R5', exchange_code: 'SZSE' },
|
|
{ ...PRODUCT_BASE, id: 8, product_code: '159925', product_name: '南方沪深300成长ETF', current_nav: '1.462000', current_nav_at: '2026-09-11T15:00:00', product_category: '策略ETF', risk_level: 'R4', exchange_code: 'SZSE' },
|
|
]);
|
|
|
|
const CLOSE_SERIES = [4.02, 4.08, 4.05, 4.12, 4.16, 4.11, 4.19, 4.23, 4.18, 4.26, 4.21, 4.218];
|
|
|
|
export function getMockNavHistory(productId) {
|
|
const product = MOCK_PRODUCTS.find((item) => item.id === Number(productId)) || MOCK_PRODUCTS[0];
|
|
const ratio = Number(product.current_nav) / CLOSE_SERIES.at(-1);
|
|
return CLOSE_SERIES.map((close, index) => ({
|
|
id: product.id * 100 + index,
|
|
product_id: product.id,
|
|
nav_date: `2026-${String(index + 1).padStart(2, '0')}-11`,
|
|
nav: (close * ratio).toFixed(6),
|
|
created_at: `2026-${String(index + 1).padStart(2, '0')}-11T15:00:00`,
|
|
}));
|
|
}
|
|
|
|
export const MOCK_RANKING_CHANGE = Object.freeze({
|
|
'510300': 1.26,
|
|
'510500': 2.18,
|
|
'159915': -0.42,
|
|
'512100': 1.88,
|
|
'513100': 0.76,
|
|
'511360': 0.05,
|
|
'159645': -1.12,
|
|
'159925': 1.54,
|
|
});
|
|
|
|
//: 公开产品 HTTP 接口尚未实现,访客页面的产品、净值与涨跌**来自本文件而不是数据库**。
|
|
//: 这是接口缺位时的显式降级,不是真实行情 —— 凡展示这些数据的页面都必须原样呈现这句话。
|
|
//: 删掉它并不会让数据变真,只会让客户以为看到的是真实净值。
|
|
export const MOCK_SOURCE_NOTICE = '公开产品接口尚未提供,本页使用与 fin_product、fin_nav_history 模型同字段的演示数据,非真实行情。';
|