Files
kyugao 51c70c48fb 迭代14: 策略tab历史持仓展示(R-016)+ 今日已清仓默认层
- R-016 Q1-Q7: 两个策略 tab title 旁「历史持仓」开关 + 清仓范围筛选(近一周默认/1月/3月/半年/1年)
  - SqliteStore.getHoldingsHistory(strategy_id + closed_at 非空 + sinceMs,closed_at DESC,只读)
  - DataStore 门面透传 + strategy-holdings/history 端点(name 兜底 + 参数校验)
  - 前端: 开关/范围下拉/历史行灰显+「已清仓」徽标/展开复用 R-010(rowKey 唯一化)
- R-016 二轮补充(Q8-Q9 老师拍板): 今日已清仓默认层
  - range=today = 本地自然日 00:00 起(特判零点,不落回溯毫秒档)
  - 当天已清仓的持仓默认恒显示、不受「历史持仓」开关控制(开关只控今天之前)
  - rowsToRender 分层合成 + holdingId 去重;标题计数不含已清仓行;零写路径变更
- 回归: test-r016-history 24/24 + 存量回归全绿
2026-09-07 18:15:07 +08:00

5.5 KiB
Raw Permalink Blame History

技术实现方案:14-策略tab历史持仓展示

迭代编号:14 依据:PLAN-015 + R-016(老师拍板:Q2 显示 0 / 其余按建议)

1. 存储层(SqliteStore.getHoldingsHistory

/** 某策略已清仓持仓(closed_at 非空),closed_at ≥ sinceMs,按 closed_at DESC */
getHoldingsHistory(strategyId, { sinceMs } = {}) {
  // SELECT holding_id, strategy_id, code, shares, created_at, closed_at, "values"
  // FROM strategy_holdings
  // WHERE strategy_id=? AND closed_at IS NOT NULL [AND closed_at >= ?]
  // ORDER BY closed_at DESC
  // 返回 _mapHolding 同构(holdingId/strategyId/code/shares/createdAt/closedAt/values
}
  • 只读新增;零写路径变更closeHolding 维持 shares=0 + closed_atQ2 定稿);
  • sinceMs 缺省 = 不过滤(内部能力预留;API 层始终传值);
  • 与 getHoldingHistory(code) 并存:后者是全表按 code 查(R-008 遗留),不满足本需求维度,不复用不改动。

2. 门面层(DataStore

async getHoldingsHistory(strategyId, opts) { await this._ensure(); return this.sqlite.getHoldingsHistory(strategyId, opts); }

3. APIsrc/api/strategies.js 新 method

  • methodstrategy-holdings/history
  • args{ strategyId: string, range: 'week'|'month'|'quarter'|'halfYear'|'year' }
  • range → sinceMs 换算(服务端):week=7d / month=30d / quarter=90d / halfYear=182d / year=365d(自然日近似,回溯窗口展示用途足够,精确日历边界无业务含义);
  • 缺 strategyId 或 range 非法 → 参数错误(code: 'bad-request');
  • 返回行附加 name 兜底:按 holdingId 查 trade_orders 取最新一笔的 nameSQL 单查,无则 null)——前端直接展示,免二次请求。

4. 前端(StrategyTab.jsx

  • 状态:showHistory(默认 false)、historyRange(默认 'week')、historyRowshistoryLoading
  • 头部按钮区:「历史持仓」toggle 按钮 + 开启时显示范围下拉(近一周/近1个月/近3个月/近半年/近1年);
  • 数据流:开启或切范围 → call('one-divine-lot/strategy-holdings/history', { strategyId, range });缓存按 range 键保留,关闭仅隐藏;
  • 渲染:rowsForRender = 当前持仓行 +showHistory ? 历史行 : []);历史行标识 isHistory: true
    • 行样式:opacity/灰色调(--dsw-alias-label-tertiary+ 徽标「已清仓 YYYY-MM-DD」;
    • 单元格:代码/名称/清仓时间(YYYY-MM-DD HH:mm/持有天数(ceil((closedAt-createdAt)/86400000),同日=1);shares 列显示 DB 原值(=0Q2);行情类列(lastPrice/pctChange/lastClose/avgPrice/lastTradePrice/upStop/downStop/open/high)一律 —(不注册行情、不取价);自定义字段列:p.values 只读展示,禁点击编辑(isHistory 不进入 FieldCellEditor 分支);
    • 展开:复用 toggleExpand(code, holdingId) → trades/by-holding(历史行同样有效);
  • 排序:当前行维持现状(涨幅排序仅作用于当前行集合);历史行固定清仓时间 DESC 追加在后;
  • 计数:标题(N 只)= 当前持仓数,不含历史行。

5. 回归脚本(scripts/test-r016-history.mjs

  • ODL_TEST_DATA_DIR 临时目录(技术约束-011,不碰真实数据);
  • 覆盖:建仓→清仓→history 可查 / 范围过滤边界(7d/30d…)/ 排序 / 策略隔离 / 未清仓行不出现 / API 参数校验 / name 兜底。

6. 验证链

typecheck → build → test-r016 → 存量回归(test-position-sync 34 / test-r013 21 / test-quote-sync)→ 人工验收清单。

7. 二轮补充实现:今日已清仓默认层(2026-09-07 老师拍板 Q8-Q9

在首轮(§1-6)基础上追加;首轮实现保持可追溯,新增改动如下。

7.1 服务端(src/api/strategies.js

  • range 新增 today特判本地自然日零点localDayStartMssetHours(0,0,0,0)),sinceMs = 本地今天 00:00;不落入 HISTORY_RANGE_MS 回溯毫秒档(那 5 档语义 = now − 范围,today 语义 = 自然日边界,两类不可混);
  • 参数校验错误信息扩为 today|week|month|quarter|halfYear|year;其余逻辑(strategy_id 过滤 + name 兜底 + DESC 排序)与 5 档完全复用——SqliteStore/DataStore 零改动sinceMs 本就通用)。

7.2 前端(src/client/views/StrategyTab.jsx

  • 状态:historyRows 缓存键新增 today(与 5 档并存互不干扰);
  • 数据流loadHistory 定义提前到 load 之前;load() 的 finally 里调 loadHistory('today')——进 tab 即拉 + 每次持仓重载(移出/加仓/编辑后 load())都刷新今日已清仓层,当天清仓转历史后 tab 内不因此少行;
  • **渲染合成(rowsToRender**分两层:
    • L1 = histRowsFor('today') —— 恒显示showHistory 关也显示);当前持仓行后追加,清仓时间 DESC;
    • L2 = showHistory 开时追加 histRowsFor(historyRange)按 holdingId 去重(剔除已含于 L1 的行,避免今天清仓 + 开启近一周出现两行);
  • 交互文案:开关 title 更新为「显示/隐藏今天之前已清仓的历史持仓(当天已清仓的持仓始终显示)」;文件头注释同步;
  • 标题计数(N 只)仍只算当前持仓,两层已清仓行不计入。

7.3 回归(scripts/test-r016-history.mjs

  • 新增 [4b] range=today:昨天 23:59:59 清仓不出现 / 今天 00:00:01 清仓出现 / 不含 40 天前行 / today 行 name 兜底同样生效 / range 非法校验不受 today 引入影响;
  • 验证链复跑:test-r016 24/24 + 存量回归全绿(见迭代复盘)。