diff --git a/.trellis/tasks/08-28-sector-capital-radar/check.jsonl b/.trellis/tasks/08-28-sector-capital-radar/check.jsonl new file mode 100644 index 0000000..9646ffc --- /dev/null +++ b/.trellis/tasks/08-28-sector-capital-radar/check.jsonl @@ -0,0 +1,8 @@ +{"file": ".trellis/spec/backend/index.md", "reason": "检查后端模块边界与开发清单"} +{"file": ".trellis/spec/backend/quality-guidelines.md", "reason": "检查 Ruff、Pyright、pytest 与禁止模式"} +{"file": ".trellis/spec/frontend/index.md", "reason": "检查前端 feature 与质量清单"} +{"file": ".trellis/spec/frontend/quality-guidelines.md", "reason": "检查格式、lint、类型、测试、构建和可访问性"} +{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "检查后端响应、前端类型、query 与页面一致性"} +{"file": ".trellis/tasks/08-28-sector-capital-radar/research/implementation-evidence.md", "reason": "检查独立指标声明、单位、空值和凭据边界"} +{"file": "docs/research/onechartlab-sector-capital-radar.md", "reason": "禁止把未知公式伪装为原站公式"} +{"file": ".trellis/tasks/08-28-sector-capital-radar/research/tushare-radar-contract.md", "reason": "检查接口、PIT、单位、覆盖率和 last-good 质量语义"} diff --git a/.trellis/tasks/08-28-sector-capital-radar/design.md b/.trellis/tasks/08-28-sector-capital-radar/design.md new file mode 100644 index 0000000..f5fe45f --- /dev/null +++ b/.trellis/tasks/08-28-sector-capital-radar/design.md @@ -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 对账差异只记录为研究数据,不自动覆盖本地结果。 diff --git a/.trellis/tasks/08-28-sector-capital-radar/implement.jsonl b/.trellis/tasks/08-28-sector-capital-radar/implement.jsonl new file mode 100644 index 0000000..c039892 --- /dev/null +++ b/.trellis/tasks/08-28-sector-capital-radar/implement.jsonl @@ -0,0 +1,8 @@ +{"file": ".trellis/spec/backend/index.md", "reason": "后端 bounded context、HTTP、市场同步与质量规范入口"} +{"file": ".trellis/spec/backend/market-data-sync.md", "reason": "复用 Tushare、批次、幂等、单位和发布约束"} +{"file": ".trellis/spec/backend/http-api-contracts.md", "reason": "新增 sector radar 同源 HTTP 契约"} +{"file": ".trellis/spec/frontend/index.md", "reason": "前端 feature、类型、query 与页面规范入口"} +{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "保持 Pydantic 到前端页面的跨层字段一致"} +{"file": ".trellis/tasks/08-28-sector-capital-radar/research/implementation-evidence.md", "reason": "实现范围、仓库复用点、Tushare 客户端和独立指标契约"} +{"file": "docs/research/onechartlab-sector-capital-radar.md", "reason": "公开确认排名算法与未知公式边界"} +{"file": ".trellis/tasks/08-28-sector-capital-radar/research/tushare-radar-contract.md", "reason": "Radar MVP 所需 Tushare 接口、字段、单位、PIT 与质量规则"} diff --git a/.trellis/tasks/08-28-sector-capital-radar/implement.md b/.trellis/tasks/08-28-sector-capital-radar/implement.md new file mode 100644 index 0000000..af39553 --- /dev/null +++ b/.trellis/tasks/08-28-sector-capital-radar/implement.md @@ -0,0 +1,61 @@ +# 板块资金雷达执行计划 + +## 开始前门禁 + +- [ ] 用户审阅并明确批准 `prd.md`、`design.md` 和本计划后,运行 `task.py start`。 +- [ ] 从 `develop@ad9545e` 创建/切换 `codex/sector-capital-radar`,写入任务 branch/base-branch 元数据;保留旧调研任务不变。 +- [ ] 确认 `implement.jsonl` 与 `check.jsonl` 均含真实 spec/research 条目。 + +## 1. 纯领域安全里程碑 + +- [ ] 在新的 `sector_radar` bounded context 定义板块类型、成员快照、股票事实、指标观察、发布与排名模型。 +- [ ] 先写固定人工样本测试,再实现 `zhixing_amount_net_bn_v1`、`zhixing_ratio_turnover_v1`、`zhixing_swing_equal_3_10_v1`。 +- [ ] 实现概念/行业分池、稳定并列键、1 基排名、百分位、TOP/BOTTOM 和 1—5 日排名变化。 +- [ ] 覆盖乱序输入、NULL/0、非有限数、空池、单元素、并列、历史缺失、停牌和 point-in-time 成员变化。 +- [ ] 运行 `uv run --directory zhixing-server pytest tests/unit/sector_radar`、Ruff 与 Pyright。此步绿灯是第一个可回滚安全点。 + +## 2. Tushare 输入与持久化 + +- [ ] 把已有 RequestCoordinator 提升到 shared 基础设施,保持 market-data 适配器及测试行为不变。 +- [ ] 定义 `SectorRadarSource` 与 Tushare adapter,显式请求七类接口及 fields;token 仅由 `Settings` 注入。 +- [ ] 实现服务端错误分类、有限重试、行数上限检测、`dc_member` 分片和账号 capability probe;输出不得包含 token。 +- [ ] 新增 Alembic 表、约束、索引和 downgrade,保存原始 JSONB/hash、成员快照、股票事实、publication 与 ranking。 +- [ ] 实现 PostgreSQL staging/COPY、幂等重跑、同日多修订、advisory lock 和 last-good 查询。 +- [ ] 为 repository fake、Tushare fake、迁移和 PostgreSQL 集成补测试;仅在 `ZHIXING_TEST_DATABASE_URL` 存在时执行数据库集成测试。 +- [ ] 若运行环境存在 `ZHIXING_TUSHARE_TOKEN`,执行只读 capability probe 并记录接口成功、字段和行数,不打印原始凭据;否则明确记录 live 验证未执行。 + +## 3. 构建 Job + +- [ ] 实现 `BuildSectorRadar.execute` 的单日与日期区间编排、质量屏障、publication 状态和失败保留 last-good。 +- [ ] 新增 `sector-radar-build` CLI 及退出码;支持目标日、回填区间和失败 publication 重试。 +- [ ] 增加 Compose job service,但不启用生产定时;更新运行文档与无凭据示例。 +- [ ] 用 fake/golden 验证完整成功、部分数据、截断响应、重复运行、输入修订、并发锁和失败降级。 + +## 4. HTTP 读取链 + +- [ ] 实现 `ReadSectorRadar` 查询模块以及 `/dates`、`/rankings` Pydantic 契约。 +- [ ] 在路由目录挂载 `/api/v1/sector-radar`;实现筛选、分页、搜索、rank-change 参数和 `no_data`/503 行为。 +- [ ] 使用真实 `create_app()` 与 fake application dependency 写黑盒 HTTP 契约测试。 + +## 5. 前端 MVP + +- [ ] 新建 feature API types、adapter 与 React Query hooks;API 边界校验稳定枚举和关键字段。 +- [ ] 新增 `/sector-radar` 路由、导航、URL search 校验和活动路由映射。 +- [ ] 实现状态摘要、筛选工具栏、排名表和分页;显示单位、质量状态、数据日期、last-good/stale 和独立指标版本。 +- [ ] 页面测试覆盖成功、筛选、rank-change、loading、error、no-data、stale/partial;adapter 测试覆盖 URL、参数和 AbortSignal。 +- [ ] 不引入图表依赖,不实现成分详情、历史轨迹或导出。 + +## 6. 全量验证与审查 + +- [ ] 后端:`uv run ruff format --check .`、`uv run ruff check .`、`uv run pyright`、`uv run pytest`。 +- [ ] 前端:`pnpm format:check`、`pnpm lint`、`pnpm typecheck`、`pnpm test`、`pnpm build`。 +- [ ] 根级:`./dev.sh check`、`./dev.sh test`;验证开发和生产 Compose config。 +- [ ] 使用 `trellis-check` 做全范围规范、PRD、跨层字段、单位、空值、版本声明和凭据泄漏检查,并修复发现项。 +- [ ] 评估是否有经用户批准才应提升到 `.trellis/spec/` 的新知识;未经批准不写 Trellis spec。 + +## 风险与回滚点 + +- RequestCoordinator 提升后若现有 market-data 检查失败,先还原该重构,雷达 adapter 暂时内部组合相同行为,不改变现有同步。 +- 数据库迁移与 Job 在 HTTP/前端之前独立落地;迁移失败可 downgrade 新表,不能修改现有市场数据表。 +- live Tushare 调用只用于只读能力与数据质量验证;权限或到达时间不满足时,保留 fake/golden 里程碑并报告阻塞,不降低质量门或把缺失补 0。 +- 前端只读取 success/last-good;后端发布未稳定前不启用外部定时任务。 diff --git a/.trellis/tasks/08-28-sector-capital-radar/prd.md b/.trellis/tasks/08-28-sector-capital-radar/prd.md new file mode 100644 index 0000000..87a724f --- /dev/null +++ b/.trellis/tasks/08-28-sector-capital-radar/prd.md @@ -0,0 +1,74 @@ +# 板块资金雷达模块 + +## Goal + +在知行系统中提供一个收盘后可查询的板块资金雷达,使用户能够按交易日分别查看概念板块与行业板块的资金强弱、前后榜和排名变化,并能辨认数据新鲜度、覆盖率与指标来源。实现以公开证据可复现为首要目标;任何未公开公式都必须采用有独立名称、版本和说明的可替换策略,不能宣称为 OneChartLab 原站公式。 + +## Background + +- OneChartLab 当前产品是收盘后的板块横截面排名系统,不是盘中实时雷达。公开证据已确认概念与行业为两个独立排名池,排名键分别为 `Swing_Score`、`Ratio_Score` 与 `Amount_Raw_BN`,排名百分位、前后 10% 榜以及 1—5 日排名变化算法可确定性复现。依据:`docs/research/onechartlab-sector-capital-radar.md`。 +- `Ratio_Score`、`Amount_Score`、3—10 日权重、`Swing_Score` 及相关异常值处理没有公开公式。独立实现不得根据字段名、线性拟合或作者口述伪造等价公式。依据:`docs/research/onechartlab-sector-capital-radar.md`、`docs/research/onechartlab-tushare-data-requirements.md`。 +- 公开消费层与独立生产层是两个不同目标。前者可原样保存 OneChartLab manifest、日期分片、排名历史和成分详情以复现当前页面;后者需要 Tushare 的 point-in-time 板块成员和资金事实,并只能先生成明确标注的替代指标。依据:`docs/research/onechartlab-tushare-data-requirements.md`。 +- 当前仓库已有 FastAPI 模块化单体、PostgreSQL 批次审计、Tushare 请求协调、原子 CSV 发布、同源 `/api/v1`、React Query 垂直切片及前后端测试模式,但尚无板块身份、板块成员快照、资金事实、雷达发布、显式 `last_good` 或图表基础设施。 +- 当前 checkout 为 detached HEAD,提交 `ad9545e` 同时是本地与远端 `develop` 的头;新任务 `task.json` 尚未设置工作分支。进入实现前应创建或切换到任务分支并写入任务元数据,不改动现有 `08-27-onechartlab-research` 调研任务。 +- 用户已选择 Tushare 独立生产 MVP,并确认具备所需 Tushare 权限。OneChartLab 公开 payload 只作为对账证据和测试样本来源,不作为生产运行时事实源。 + +## Requirements + +### R1. 可追溯发布 + +每个可查询发布版本必须至少携带交易日、观测/发布时间、来源类型、来源版本、内容 hash、指标策略版本、universe 版本、数据状态和覆盖率。相同交易日允许存在修订版本,读取端必须能区分当前发布与最近一个有效发布。 + +### R2. 排名池与确定性算法 + +概念和行业必须独立排名,不能硬编码板块数量或成员关系。对公开证据已确认的算法必须提供确定性实现和回归测试: + +- `Swing` 按 `Swing_Score` 降序,`Ratio` 按 `Ratio_Score` 降序,`Amount` 按 `Amount_Raw_BN` 降序; +- `RankPct = 100 * (N - rank + 1) / N`,排名从 1 开始; +- 普通强榜使用 `RankPct >= 90`,弱榜使用 `RankPct <= 10`; +- 排名变化为 `PastRank - CurrentRank`,正数表示上升;1—5 日历史不足或缺失时按明确契约处理; +- 排序必须确定性且不依赖输入遍历顺序;由于原站并列规则未知,本项目稳定键必须以独立实现契约命名并测试,不能标注为原站规则。 + +### R3. 未公开指标隔离 + +`Ratio`、`Swing` 及任何自建 score 的计算必须位于可替换指标策略 seam 后,并返回明确的 `metric_version`、参数、质量状态和所需输入。公开 payload 的预计算 score 与本项目独立策略不得混写为同一来源或同一版本。 + +### R4. Point-in-time 与空值语义 + +板块成员必须按交易日保存;缺失成员快照应为 `membership_unknown`,不得用当前成员回填历史。供应商缺行、NULL、数值 0、停牌、生命周期无效与低流动性必须保持不同语义,缺失资金流不得转成 0。 + +### R5. 单位与质量门 + +原始层保留供应商单位,规范层显式换算。MVP 使用的 `moneyflow_dc.net_amount` 为万元,`daily.amount` 为千元,两者必须先统一为元再计算比例。发布至少输出有效样本数、成员覆盖率、资金覆盖率和质量状态;未达完整性门槛时不得覆盖最近有效发布。 + +### R6. 读取契约与页面 + +后端在独立 bounded context 中提供稳定 Pydantic 响应,并经 `/api/v1` 同源路由暴露。前端在独立 feature 中使用 `requestJson`、React Query 和 URL/局部状态,显式呈现加载、错误、无数据、stale/partial 与成功状态。页面最少支持交易日、板块类型、指标视角、强弱榜切换、搜索和排名表。首个 MVP 不包含成分股详情、历史轨迹图或数据导出,但应保存足以计算 1—5 日排名变化的历史发布结果。 + +### R7. 收盘后执行与降级 + +生产流程沿用外部调度的一次性 Job,不在 FastAPI 生命周期内启动定时器。发布过程必须幂等并使用锁避免同一目标日期并发构建;上游未到齐或校验失败时保留 `last_good`,并把当前状态标记为 stale/partial/failed,不能发布假完整结果。 + +### R8. 安全与凭据 + +Tushare token 只从 `Settings`/环境注入,不得写入源码、日志、响应、测试 fixture 或 Trellis 任务文档。公开站点输入必须进行 schema 与有限数校验,不能仅依靠 TypeScript 泛型断言。 + +## Acceptance Criteria + +- [ ] AC1:给定固定样本和乱序输入,概念/行业的三套排名、排名百分位、普通前后榜和 1—5 日排名变化结果可重复,且测试覆盖空池、单元素池、并列值、历史缺失和非有限数。 +- [ ] AC2:每条雷达结果可追溯到唯一发布版本、来源版本、universe 版本和指标策略版本;响应和页面明确标注“知行独立实现”,不暴露或暗示原站 `Ratio_Score`、`Swing_Score` 字段。 +- [ ] AC3:缺失资金流、成员未知、低流动性、部分覆盖与失败发布不会被展示成完整的零值结果;失败构建不覆盖 `last_good`。 +- [ ] AC4:后端 HTTP 契约测试锁定筛选、分页/榜单、数据状态和错误行为;前端类型、API adapter、query 与页面测试覆盖 loading/error/no-data/stale/partial/success。 +- [ ] AC5:页面可分别浏览概念与行业排名池,并按交易日、指标视角和强弱榜筛选;金额、比例、策略版本与排名变化的单位和方向符合本任务契约。 +- [ ] AC6:收盘后 Job 可幂等重复执行,重复内容不产生无意义修订;同一日期并发执行被锁阻止,失败时保留最近有效发布。 +- [ ] AC7:运行相关后端 Ruff、Pyright、pytest 与前端 format、lint、typecheck、Vitest、build;跨层链路通过根级检查。没有实际运行的检查不得标记为通过。 +- [ ] AC8:生产运行时不请求 OneChartLab;Tushare 原始响应、point-in-time 成员和规范化股票事实足以重放同一指标策略版本,且公开样本只用于对账,不覆盖本地事实。 + +## Out of Scope + +- 盘中实时资金流、WebSocket 推送或分钟级雷达。 +- 在没有第一方公式和 point-in-time 输入证据时宣称完全复刻 `Ratio_Score`、`Swing_Score`、`Amount_Score` 或 3—10 日权重。 +- 使用当前板块成员回填历史、把缺失值补 0,或把 OneChartLab 当前 universe 数量硬编码进实现。 +- 宏观择时模块、交易执行、收益承诺和投资建议。 +- 在首个安全里程碑中一次性实现研究报告列出的全部 17 个 Tushare 接口。 +- 首个 MVP 的成分股详情、排名轨迹图、导出、盘中刷新、`daily_basic` 流动性增强和 `moneyflow_ind_dc` 三方对账;这些作为后续增量,不阻塞主榜生产。 diff --git a/.trellis/tasks/08-28-sector-capital-radar/research/implementation-evidence.md b/.trellis/tasks/08-28-sector-capital-radar/research/implementation-evidence.md new file mode 100644 index 0000000..95ada05 --- /dev/null +++ b/.trellis/tasks/08-28-sector-capital-radar/research/implementation-evidence.md @@ -0,0 +1,32 @@ +# 板块资金雷达实现依据 + +## 已选择的产品边界 + +用户选择 Tushare 独立生产 MVP,并确认具备 Tushare 权限。生产运行时不依赖 OneChartLab;公开 payload 只用于验证公开契约、构造固定样本和对账。未公开公式必须使用知行系统自己的策略名称与版本。 + +## 仓库复用点 + +- 后端采用 `modules//{domain,application,infrastructure,presentation}`,依据 `docs/adr/0001-bounded-context-first-modular-monolith.md` 和 `.trellis/spec/backend/directory-structure.md`。雷达应创建独立 bounded context。 +- `market_data.infrastructure.tushare.RequestCoordinator` 已实现供应商请求冷却、退避和有限重试;`TushareAdapter` 已使用注入的 `pro_api(token)` client。相关实现位于 `zhixing-server/src/zhixing_server/modules/market_data/infrastructure/tushare.py:40-126,235-377`。 +- `SyncMarketData` 已实现 advisory lock、批次审计、部分成功和定向重试,位于 `zhixing-server/src/zhixing_server/modules/market_data/application/sync.py:150-165,255-261,408-532`。 +- PostgreSQL 适配器已有连接池、事务、staging + COPY 和幂等 upsert 模式,位于 `zhixing-server/src/zhixing_server/modules/market_data/infrastructure/postgres.py:26-57,296-401,769-925`。 +- FastAPI 业务路由由 `zhixing-server/src/zhixing_server/interfaces/http/router.py:10-18` 统一挂载;浏览器固定使用同源 `/api/v1`。 +- 前端垂直切片、路由和页面状态模式分别见 `zhixing-web/src/routes/route-tree.tsx:17-70`、`zhixing-web/src/features/home/api/`、`zhixing-web/src/features/home/pages/home-page.tsx`。当前没有图表依赖,因此首个 MVP 不加入轨迹图。 +- `.codegraph/` 不存在,跨文件影响分析只能使用源码、测试和 `rg`。 + +## 当前 Tushare 客户端核验 + +项目声明 `tushare>=1.4.24`,见 `zhixing-server/pyproject.toml:7-16`。2026-08-28 通过 Context7 解析 `/waditu/tushare` 与 `/websites/tushare_pro`,确认: + +- `ts.pro_api(token)` 创建 `DataApi`;客户端 `query(api_name, fields, **kwargs)` 把接口名、token、参数和字段列表发送到服务端。 +- 客户端本身不执行积分、权限、频率或行数限制;这些限制由 Tushare 服务端返回。因此采集 Job 必须记录安全错误类别、响应行数和覆盖率,并在返回数接近单次上限时分片重拉。 +- 不采用 `set_token()` 的用户目录持久化方式;项目继续通过 `Settings` 注入 token,避免凭据落盘或进入任务文档。 + +Context7 对具体板块接口的字段覆盖有限,接口字段、单位、历史边界和 2026-08-28 权限快照继续以 `docs/research/onechartlab-tushare-data-requirements.md` 所列 Tushare 第一方页面为实现依据。上线前由目标账号执行能力探测,不能把文档积分视为账号实测结果。 + +## 独立指标契约 + +- `zhixing_amount_net_bn_v1`:对当日有效成员的 `moneyflow_dc.net_amount` 求和,由万元除以 10,000 转为亿元;它是待公开样本对账的独立聚合,不宣称原站等价。 +- `zhixing_ratio_turnover_v1`:先统一为元,再计算板块 `sum(net_amount) / sum(daily.amount)`;分母为零或输入缺失时返回 NULL。 +- `zhixing_swing_equal_3_10_v1`:对窗口 3—10 个交易日分别计算 `sum(net_amount) / sum(turnover)`,再对八个完整窗口等权平均。任何窗口不完整时该指标不可用。该公式是透明、可替换的知行独立实现,不是 OneChartLab 的 3—10 日权重或 `Swing_Score`。 +- 横截面排名按指标值降序、板块代码升序稳定打破并列;概念与行业独立成池。`RankPct`、TOP/BOTTOM 阈值和 `PastRank - CurrentRank` 复用公开确认算法。 diff --git a/.trellis/tasks/08-28-sector-capital-radar/research/tushare-radar-contract.md b/.trellis/tasks/08-28-sector-capital-radar/research/tushare-radar-contract.md new file mode 100644 index 0000000..f405113 --- /dev/null +++ b/.trellis/tasks/08-28-sector-capital-radar/research/tushare-radar-contract.md @@ -0,0 +1,35 @@ +# Tushare 板块雷达最小契约 + +本文件从 `docs/research/onechartlab-tushare-data-requirements.md` 提炼首个 Radar MVP 实际需要的接口,避免实现上下文被宏观择时和后续增强接口稀释。上线前仍以目标账号的只读 capability probe 为准。 + +## 最小接口 + +| 接口 | 作用 | 调用与必要字段 | 业务键与边界 | +| --- | --- | --- | --- | +| `trade_cal` | 确认开市日和窗口 | `exchange,start_date,end_date`;`exchange,cal_date,is_open,pretrade_date` | `(exchange,cal_date)`;初始化后按年刷新 | +| `dc_index` | 当日概念/行业 universe | `trade_date,idx_type`;`ts_code,trade_date,name,idx_type,level,pct_change,leading_code` | `(trade_date,ts_code)`;概念与行业分别拉取,单次上限公开页为 5,000 | +| `dc_member` | 当日 point-in-time 成员 | 优先 `trade_date`,必要时按 `ts_code` 分片;`trade_date,ts_code,con_code,name` | `(trade_date,ts_code,con_code)`;单次上限 5,000,命中上限或覆盖不足必须分片,不得用当前成员补历史 | +| `stock_basic` | 生命周期与市场过滤 | 分别拉 `list_status=L,D,P,G,UN`;`ts_code,symbol,name,market,exchange,list_status,list_date,delist_date` | `ts_code + observed_at`;默认只返回 L,不能漏掉其他状态 | +| `suspend_d` | 区分停牌与缺数 | `trade_date`;`ts_code,trade_date,suspend_timing,suspend_type` | `(ts_code,trade_date,suspend_type,suspend_timing)`;官方称不定期修订,需重叠回拉 | +| `daily` | 成交额、涨跌幅和行情可用性 | `trade_date`;`ts_code,trade_date,close,pre_close,pct_chg,vol,amount` | `(ts_code,trade_date)`;单次上限 6,000,停牌期间不返回;`amount` 单位千元 | +| `moneyflow_dc` | 个股东财口径主力净额 | `trade_date`;`trade_date,ts_code,name,net_amount,net_amount_rate,pct_change,close` | `(ts_code,trade_date)`;单次上限 6,000,历史始于 2023-09-11;`net_amount` 单位万元 | + +## 单位与规范化 + +- `moneyflow_dc.net_amount` 万元转元时乘 `10_000`,转亿元时除 `10_000`。 +- `daily.amount` 千元转元时乘 `1_000`,转亿元时除 `100_000`。 +- `daily.pct_chg=1.5` 表示 1.5%;不与 OneChartLab 小数比例字段直接混算。 +- 空字符串、`None` 和 `NaN` 规范为 NULL;`inf`、`-inf`、重复业务键和错误交易日属于硬错误。 +- 缺失资金流不是 0。只有生命周期有效、非停牌且源接口应有记录的股票进入缺失率分母。 + +## Universe 与质量 + +- 只纳入沪深 A 股,排除北交所与沪深 B 股;OneChartLab Radar 契约未声明排除 ST,因此不能复用现有选股股票池的 ST 过滤。 +- `dc_member` 某日缺失时标记 `membership_unknown`,不能向前或向后填充。 +- 全局分别计算成员、`daily` 和 `moneyflow_dc` 覆盖率;publication 只有在全部必需接口通过、成员完整且事实覆盖率达到配置门槛时才为 success。 +- 板块结果输出成员数、有效样本数、成交额、成员覆盖率与资金覆盖率;有效样本少于 5 时标记 `available_limited_sample`。 +- 初始回填必须按交易日顺序完成至少 10 日,才能生成完整 3—10 日独立波段指标;`moneyflow_dc` 的最早日期是硬边界。 + +## 客户端与安全 + +项目使用 `ts.pro_api(token)` 返回的 `DataApi`,动态调用最终进入 `query(api_name, fields, **params)`。客户端不执行权限、积分、频率或行数保护,采集 adapter 必须处理服务端错误、退避、返回行数和覆盖率。token 只从 `Settings` 注入,不调用 `set_token()` 写用户目录,不写入日志、原始请求清单或错误摘要。 diff --git a/.trellis/tasks/08-28-sector-capital-radar/task.json b/.trellis/tasks/08-28-sector-capital-radar/task.json new file mode 100644 index 0000000..b7b6a52 --- /dev/null +++ b/.trellis/tasks/08-28-sector-capital-radar/task.json @@ -0,0 +1,26 @@ +{ + "id": "sector-capital-radar", + "name": "sector-capital-radar", + "title": "板块资金雷达模块", + "description": "基于 Tushare point-in-time 事实独立生产收盘后板块资金排名、版本化指标、last-good API 与前端页面。", + "status": "planning", + "dev_type": null, + "scope": "fullstack", + "package": null, + "priority": "P2", + "creator": "yuxuanhui", + "assignee": "yuxuanhui", + "createdAt": "2026-08-28", + "completedAt": null, + "branch": "codex/sector-capital-radar", + "base_branch": "develop", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [], + "notes": "", + "meta": {} +} diff --git a/CONTEXT.md b/CONTEXT.md index 185c62e..6f1ac9f 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -50,3 +50,17 @@ _Avoid_: 用当前市值回填历史、把当前目标股票池当作无幸存 **股票更新失败**:一只股票未能在目标交易日同时具备日线行情和估值快照时的状态,并应保留可读的失败原因。 **数据覆盖率**:目标交易日内,有效选股股票数占当前目标股票池目标数的比例,用于判断选股结果是否具备足够完整性。 + +## 板块资金雷达 + +**板块资金雷达**:在交易日收盘后,分别对概念板块和行业板块的资金指标进行横截面比较、排名与历史变化分析;它不是盘中实时信号,也不构成交易指令。 + +**板块排名池**:同一交易日、同一板块类型中参加同一指标排名的板块集合;概念板块与行业板块属于不同排名池,成员数量随交易日变化。 + +**板块成员快照**:数据源在指定交易日给出的板块与股票成员关系。历史分析只使用对应交易日的快照,缺失时标记成员未知,不用当前成员替代。 + +**独立指标策略**:知行系统根据 Tushare 原始事实自行定义的资金指标算法;它必须有稳定名称和版本,不能称为外部站点未公开公式的复刻。 + +**雷达发布批次**:针对一个目标交易日完成的输入采集、质量校验、指标计算和排名发布;同一交易日可以因上游修订产生多个批次。 + +**最近有效发布**:最近一个通过完整性与质量门的雷达发布批次。新批次失败或只完成部分数据时,读取端继续使用该批次并明确显示数据已过期。