chore(task): archive 08-28-sector-capital-radar
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# 板块资金雷达技术设计
|
||||
|
||||
## 设计结论
|
||||
|
||||
首个 MVP 建设 Tushare 独立生产链,生产运行时不读取 OneChartLab。后端新增 `sector_radar` bounded context,通过一次性 CLI 采集七类最小事实、保存 point-in-time 输入、计算三个带版本的知行独立指标、生成横截面排名并发布;FastAPI 只读取已发布结果。前端新增 `/sector-radar` feature,提供日期、板块类型、指标、强弱榜、排名变化、搜索和分页。
|
||||
|
||||
首个安全里程碑不实现成分股详情、历史轨迹图、导出、`moneyflow` 主买净额、`daily_basic` 流动性增强或 `moneyflow_ind_dc` 对账。架构为这些能力保留数据版本与指标策略 seam,但不创建空实现。
|
||||
|
||||
## Module 与 seam
|
||||
|
||||
### `sector_radar` bounded context
|
||||
|
||||
外部 interface 保持两个深模块:
|
||||
|
||||
1. `BuildSectorRadar.execute(command) -> BuildSummary` 隐藏目标交易日解析、采集、校验、持久化、指标计算、排名和发布切换。
|
||||
2. `ReadSectorRadar.list_dates()` 与 `ReadSectorRadar.query(query) -> RankingPage` 隐藏 last-good 选择、筛选、排序和分页。
|
||||
|
||||
内部保留三个真实 seam:
|
||||
|
||||
- `SectorRadarSource`:Tushare 生产 adapter 与测试 fake adapter;负责 `trade_cal`、`dc_index`、`dc_member`、`stock_basic`、`suspend_d`、`daily`、`moneyflow_dc`。
|
||||
- `SectorRadarRepository`:PostgreSQL 生产 adapter 与 application 测试 fake adapter;负责输入版本、发布批次和读取投影。
|
||||
- `MetricStrategy`:金额、单日资金率和波段资金率是三个实际可替换 adapter;每个结果必须带 `metric_version`、显示标签、排序值、单位和质量状态。
|
||||
|
||||
Tushare 请求协调能力已有第二个真实消费者后,将 `RequestCoordinator` 从 `market_data` infrastructure 提升为无业务所有权的小型 shared 基础能力;原市场同步与雷达 adapter 同时复用,且保留现有行为测试。
|
||||
|
||||
## 领域模型
|
||||
|
||||
- `SectorType`:`concept` 或 `industry`;地域板块不进入 MVP。
|
||||
- `SectorMembershipSnapshot`:`trade_date + sector_code + stock_code`,只表示该交易日的成员事实。
|
||||
- `StockDailyFact`:股票在交易日的生命周期、停牌、成交额和主力净额状态;缺行、NULL、0 和无效成员不同义。
|
||||
- `MetricObservation`:策略版本生成的值、单位、有效样本数、成员覆盖率和质量状态。
|
||||
- `RadarPublication`:针对一个目标交易日的不可变构建版本,状态为 `running|success|partial|failed`,包含输入 hash、universe 版本、指标版本集合、覆盖率、开始/完成时间和安全错误摘要。
|
||||
- `RadarRanking`:属于某个 publication 和排名池的指标值、1 基排名、排名百分位及 1—5 日变化。
|
||||
- `LastGoodPublication`:不使用可被失败构建覆盖的可变字段;读取时选择最近一个 `status=success` 的 publication。失败与 partial 批次仍保留审计。
|
||||
|
||||
## 数据采集与质量
|
||||
|
||||
每日 Job 的目标交易日由 `trade_cal` 确认。目标日采集 `dc_index` 的概念与行业、`dc_member`、全部上市状态的 `stock_basic`、`suspend_d`、`daily` 和 `moneyflow_dc`。首次初始化或补算按交易日顺序执行,至少准备 10 个交易日才能产生完整波段指标,至少保留 30 个已发布交易日供排名变化和后续轨迹使用。
|
||||
|
||||
原始响应按 `api_name + normalized_params + observed_at` 保存 JSONB、行数和 SHA-256;规范表保存 point-in-time 成员与股票事实。响应接近官方单次上限或覆盖率不足时必须分片重拉,不能接受可能截断的成功响应。
|
||||
|
||||
有效股票候选满足目标日生命周期并属于沪深 A 股,排除北交所与 B 股;不沿用选股模块的 ST 排除规则。停牌且无 `daily` 的股票标记为 suspended,不作为应有行情缺失;应有 `daily` 或 `moneyflow_dc` 却缺失的股票进入覆盖率缺口,不能补 0。
|
||||
|
||||
发布成功门使用可配置的全局事实覆盖率,默认沿用项目的 `0.99`。成员快照必须完整;任一必需接口硬失败、重复业务键、日期错误、非有限数、单位校验失败或覆盖率低于门槛,当前 publication 为 `partial` 或 `failed`,不得成为 last-good。每个板块仍输出有效样本数、成员覆盖率和质量状态;少于 5 个有效成员标记 `available_limited_sample`。
|
||||
|
||||
## 独立指标策略
|
||||
|
||||
对板块 `s`、交易日 `t`,使用当日 point-in-time 有效成员:
|
||||
|
||||
```text
|
||||
Net(s,t) = sum(moneyflow_dc.net_amount) × 10_000 元
|
||||
Turnover(s,t) = sum(daily.amount) × 1_000 元
|
||||
```
|
||||
|
||||
- `zhixing_amount_net_bn_v1 = Net(s,t) / 100_000_000`,排序值为亿元净额。
|
||||
- `zhixing_ratio_turnover_v1 = Net(s,t) / Turnover(s,t)`;分母为 0 或输入不完整时为 NULL。
|
||||
- 对每个 `w ∈ [3,10]`,`WindowRatio_w = sum(Net(s,d)) / sum(Turnover(s,d))`,其中每个 `d` 使用自己的成员快照;`zhixing_swing_equal_3_10_v1` 是八个完整 `WindowRatio_w` 的算术平均。任一窗口不完整时为 NULL。
|
||||
|
||||
这些名称和页面标签都明确写“知行独立实现”。接口不暴露原站的 `Ratio_Score` 或 `Swing_Score` 字段名,而统一返回 `metric_value`、`metric_version`、`unit` 和 `implementation_kind=independent`。
|
||||
|
||||
## 排名契约
|
||||
|
||||
概念与行业分别成池。NULL 指标不进入排名,但作为 unavailable 记录保留质量信息。非 NULL 值按 `metric_value DESC, sector_code ASC` 排序,后者是知行独立稳定键,不宣称原站并列规则。
|
||||
|
||||
```text
|
||||
RankPct = 100 * (N - RankPos + 1) / N
|
||||
Top = RankPct >= 90
|
||||
Bottom = RankPct <= 10
|
||||
RankChg = PastRank - CurrentRank
|
||||
```
|
||||
|
||||
排名变化按过去第 1—5 个已发布交易日计算;过去或当前排名缺失时返回 NULL,而不是伪造 0。前端明确显示暂无可比历史。
|
||||
|
||||
## 持久化
|
||||
|
||||
新增一条 Alembic 迁移,至少包含:
|
||||
|
||||
- `sector_radar_source_snapshot`:接口、参数、目标日、原始 JSONB、行数、hash、观测时间;
|
||||
- `sector_radar_membership`:来源快照、交易日、板块类型、板块代码、股票代码及展示名;
|
||||
- `sector_radar_stock_fact`:来源快照集合、交易日、股票代码、生命周期/停牌状态、成交额、主力净额和数据状态;
|
||||
- `sector_radar_publication`:构建身份、状态、版本、覆盖率、input hash、时间和错误摘要;
|
||||
- `sector_radar_ranking`:publication、板块身份、指标版本、数值、单位、质量、排名百分位和历史变化。
|
||||
|
||||
业务唯一键必须包含交易日和来源/发布版本,允许同一交易日修订共存。数值使用有限 `NUMERIC`/`Decimal`;批量写入沿用 staging + COPY + 幂等 upsert。原始 token、完整请求头和未经净化的异常不得持久化。
|
||||
|
||||
## CLI 与发布流程
|
||||
|
||||
新增 `sector-radar-build` 一次性 CLI:
|
||||
|
||||
- 默认构建最近一个已收盘交易日;
|
||||
- `--trade-date YYYY-MM-DD` 构建单日;
|
||||
- `--start-date/--end-date` 按交易日顺序初始化或回填;
|
||||
- `--retry-publication-id` 只重试失败来源分片。
|
||||
|
||||
Job 使用独立 advisory lock。顺序为准备 running publication、采集并保存原始版本、规范化与质量屏障、计算指标与排名、事务写入、标记 success。任何阶段失败都保留原 publication 审计,读取端继续选择 last-good。FastAPI 不启动定时器;生产 Compose 增加 job service,实际定时继续由外部调度器负责。
|
||||
|
||||
## HTTP 契约
|
||||
|
||||
挂载前缀 `/api/v1/sector-radar`:
|
||||
|
||||
- `GET /dates`:返回可用成功日期、当前尝试状态、last-good 日期和发布时间;
|
||||
- `GET /rankings`:参数为 `trade_date`(缺省 last-good)、`sector_type`、`view=amount|ratio|swing|rank_change`、`rank_change_metric`、`rank_change_days=1..5`、`side=top|bottom|all`、`search`、`page` 和 `page_size`。
|
||||
|
||||
响应包含 publication 元数据、指标定义与独立实现声明、分页信息和排名行。没有成功发布时返回稳定的 `no_data` 成功响应;非法参数由 Pydantic/FastAPI 返回 422;数据库不可用映射 503。外部源异常只发生在 Job,不从读取端点实时透传。
|
||||
|
||||
## 前端
|
||||
|
||||
新增 `features/sector-radar` 垂直切片以及 `/sector-radar` 路由和导航入口。URL 保存日期、类型、视角、榜侧、搜索和分页;服务器数据只进入 React Query。页面使用现有 `PageLayout`、`Card`、`Input`、`Select`、`Badge`、`Pagination` 和语义 table,显式显示 loading、error、no-data、stale/partial/success。
|
||||
|
||||
首个 MVP 使用排名表和状态摘要,不引入图表库。金额显示亿元,比率显示百分比,指标旁始终显示策略版本或“知行独立实现”。
|
||||
|
||||
## 兼容、回滚与风险
|
||||
|
||||
- 新表、新路由和新 feature 不改变现有 market-data/selection 契约;shared 请求协调器移动必须先保持现有测试通过。
|
||||
- 数据库迁移 downgrade 只删除新上下文表,不触碰现有市场数据。
|
||||
- 外部调度在 job 验证稳定前保持关闭;手工构建与读取验证通过后再启用。
|
||||
- 主要风险是目标账号实际权限/限流、`dc_member` 分片完整性和数据到达时间。首次实现必须提供不泄密的 capability probe 与覆盖率报告;没有 live token 时以 fake/golden 完成自动化验证,但不得声称生产采集通过。
|
||||
- OneChartLab 对账差异只记录为研究数据,不自动覆盖本地结果。
|
||||
Reference in New Issue
Block a user