Files
one_divine_lot/docs/04-迭代记录/06-数据存储SQLite/UI交互调用分析.md
T
kyugao dd98b4d45b docs(迭代06): 数据存储SQLite迭代文档 + 需求归档(R-008)
- 新增 02-计划/计划-数据存储SQLite.md(PLAN-007)
- 新增 04-迭代记录/06-数据存储SQLite/ 五份文档(迭代目标/技术实现方案/验收标准/UI交互调用分析/迭代复盘)
- 归档 R-008 至 已完成/(含归档头 + 讨论记录索引),索引更新为已实现(已归档)
- 设计约束更新:技术方案约束-012 标注 SQLite 变更、数据存储设计.md 第 9 节变更预告
- 需求池说明.md 新增「归档与转正规范」;迭代记录说明.md 新增「实现经验沉淀」
- 验收标准修正 db 文件名笔误(one-divine-lot.db → store.db)
2026-09-01 18:04:28 +08:00

11 KiB
Raw Blame History

UI 交互调用分析:06-数据存储SQLite

分析日期:2026-09-01 | 依据:技术实现方案.md(接口定义) 目的:基于 SQLite 存储层接口(strategy_holdings / market_quotes_cache + 持仓生命周期方法集),梳理前端各操作 → 服务端 → 存储层的完整调用链,确认对外 API 不变的前提下,存储层升级对调用流程的影响。

调用链路总览(升级后不变的部分)

前端组件(React
  │  fetch POST /odl/api/<method>  body={ args }
  ▼
connection.jsx useRpc()  ── 标准化剥离 one-divine-lot/ 前缀
  ▼
服务端 src/api/*.js 领域分发(positions / strategies / qmt-connections / market / trades
  ▼
PositionManager / MarketDataHub / Settings(业务逻辑)
  ▼
DataStore(改造)── 委托 ──▶ SqliteStorenode:sqlite,新增)
  │                        ├─ strategy_holdings 表(持仓生命周期)
  │                        └─ market_quotes_cache 表(行情快照)

关键结论:前端组件与 /odl/api/* 端点零改动;改动集中在服务端业务层(PositionManager 适配单票生命周期)+ 存储层(DataStore→SqliteStore)。以下逐操作分析。


操作 1:策略持仓 tab 加载(读)

触发:进入策略 tab → StrategyTab.load()

前端调用StrategyTab.jsx):

Promise.all([
  call('one-divine-lot/strategy-positions', { args: { strategyId } }),  // 策略持仓
  call('one-divine-lot/unallocated', {}),                                 // 未分配(添加下拉候选)
]);

服务端api/strategies.js)→ PositionManager.getStrategyPositions(strategyId)

  • 升级前:getAllPositions()QMT REST+ storage.getDataset(strategyId)(整策略读 store.json
  • 升级后:getAllPositions() + SqliteStore.getCurrentHoldings(strategyId)SELECT ... WHERE strategy_id=? AND closed_at IS NULL

存储层

SELECT code, shares, created_at FROM strategy_holdings
WHERE strategy_id = ? AND closed_at IS NULL

返回[{ code, name, volume, shares, ... }](对外结构不变,上层 API 无感知)


操作 2:添加持仓(建仓/加仓)

触发:策略 tab 添加表单 → 选标的 + 填份额 → 确认添加 → handleAdd()

前端调用StrategyTab.jsx):

call('one-divine-lot/add-shares', { args: { code, strategyId, shares: Number(addShares) } });

服务端PositionManager.addToStrategy(code, strategyId, shares)

  • 升级前:读 code 份额 map → 校验超限 → _setShares(code, next)整策略 dataset 重写
  • 升级后:校验逻辑不变(total / 已分配 + 新增 ≤ 总持仓),写入改为单票生命周期
    • 该策略该 code 无当前持仓SqliteStore.openHolding(strategyId, code, shares)INSERTcreated_at=nowclosed_at=NULL
    • 已有当前持仓SqliteStore.addShares(strategyId, code, shares)UPDATE shares += ? WHERE closed_at IS NULL

存储层

-- openHolding(建仓)
INSERT INTO strategy_holdings (strategy_id, code, shares, created_at, closed_at)
VALUES (?, ?, ?, ?, NULL);

-- addShares(加仓)
UPDATE strategy_holdings SET shares = shares + ?
WHERE strategy_id = ? AND code = ? AND closed_at IS NULL;

返回getShares(code){ strategyId: shares },结构不变)→ 前端 toast.success + load() 刷新


操作 3:移出份额(减仓/清仓)

触发:策略 tab 行内「移出」→ 填数量 → 确认 → handleRemove()

前端调用StrategyTab.jsx):

call('one-divine-lot/remove-shares', { args: { code, strategyId, shares: Number(removeTarget.shares) } });

服务端PositionManager.removeFromStrategy(code, strategyId, shares)

  • 升级前:读份额 → 校验不超移 → _setShares(整策略重写;减到 0 则删除该 code 条目)
  • 升级后:校验不变,写入分两种:
    • 减后 > 0SqliteStore.reduceShares(strategyId, code, shares)UPDATE shares -= ? WHERE closed_at IS NULL
    • 减到 = 0SqliteStore.closeHolding(strategyId, code)UPDATE closed_at=now, shares=0转历史,不物理删除

存储层

-- reduceShares(减仓,仍持仓)
UPDATE strategy_holdings SET shares = shares - ?
WHERE strategy_id = ? AND code = ? AND closed_at IS NULL;

-- closeHolding(清仓,转历史)
UPDATE strategy_holdings SET shares = 0, closed_at = ?
WHERE strategy_id = ? AND code = ? AND closed_at IS NULL;

行为变化:清仓数据保留为历史closed_at 非 NULL),不再物理删除 —— 供未来交易记录/复盘关联(R-007)。


操作 4:全部移入(单票级)

触发:策略 tab 行内「全部移入」→ handleMoveAllIn()

前端调用StrategyTab.jsx):

call('one-divine-lot/move-all-shares', { args: { code, strategyId } });

服务端PositionManager.moveAllUnallocatedToStrategy(code, strategyId)

  • 升级前:计算未分配份额 → _setShares(整策略重写,current[strategyId] + unallocated
  • 升级后:等价于「把未分配份额并入该策略当前持仓」→ 走生命周期:
    • 无当前持仓 → openHolding(strategyId, code, unallocated)
    • 有当前持仓 → addShares(strategyId, code, unallocated)

存储层:同操作 2 的 openHolding / addSharesshares = 原份额 + 未分配份额)

返回getShares(code)toast.success + load()


操作 5:一键清零(策略级)

触发:策略 tab 顶部「一键清零」→ 弹窗确认 → handleClearAll()

前端调用StrategyTab.jsx):

call('one-divine-lot/remove-all-shares', { args: { strategyId } });

服务端PositionManager.clearStrategyShares(strategyId)

  • 升级前:storage.getDataset(strategyId)(读整策略)→ storage.removeDataset(strategyId)物理删除整策略 dataset
  • 升级后:批量 closeHolding —— 该策略所有当前持仓转历史(不物理删除):
    • getCurrentHoldings(strategyId) 得到受影响 code 列表
    • 逐笔 closeHolding(strategyId, code)
    • 返回受影响 code 列表(对外结构不变)

存储层

UPDATE strategy_holdings SET shares = 0, closed_at = ?
WHERE strategy_id = ? AND closed_at IS NULL;

返回string[](受影响标的 code 列表)→ 前端提示「已清空策略份额(N 只标的回到未分配)」+ load()


操作 6:删除策略(设置页联动清份额)

触发:设置页「策略分组」→ 删除策略 → 弹窗确认 → handleDelete()

前端调用SettingsSection.jsx):

call('one-divine-lot/strategies/remove', { args: { strategyId: id } });

服务端api/strategies.js):

await removeStrategy(settings, args.strategyId);   // settings 删除策略定义
await manager.clearStrategyShares(args.strategyId); // 联动清份额
return getStrategies(settings);

升级后clearStrategyShares 同样走批量 closeHolding —— 该策略持仓全部转历史(closed_at=now),历史保留(供交易记录关联),settings 中策略定义删除(策略 id 从配置消失,但 SQLite 中历史行仍带 strategy_id,可追溯)。

存储层:同操作 5 的批量 closeHolding。


操作 7:行情现价展示(读缓存)

触发:任意持仓表格渲染现价列 → useMarket().getPrice(code)

前端调用MarketDataProvider 轮询):

// 表格上报 code → Provider 调行情接口(去重,不再自拉 positions)
registerCodes(codes);
// 周期轮询
call('one-divine-lot/market-snapshot', { args: { codes } });

服务端api/market.js)→ MarketDataHub.getByCodes()

  • 内存缓存 → 磁盘缓存 → REST 补拉(三级命中)
  • 升级后:磁盘缓存由 store.market.json 改为 SqliteStore.getMarketQuotes(codes)(读 market_quotes_cache 表)

存储层

SELECT code, last_price, last_close FROM market_quotes_cache WHERE code IN (...);

返回{ code: { lastPrice, lastClose } } → PriceCell 渲染现价 + 红涨绿跌着色


操作 8:行情写回缓存(服务端后台)

触发MarketDataHub.ingest() 防抖 3s 写回(非用户直接操作)

前端:无直接调用(服务端后台行为)

服务端MarketDataHub → 升级后 SqliteStore.setMarketQuotes(quotes)

INSERT INTO market_quotes_cache (code, last_price, last_close, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(code) DO UPDATE SET
  last_price = excluded.last_price,
  last_close = excluded.last_close,
  updated_at = excluded.updated_at;

说明:只落盘 lastPrice/lastClose 两列(UI 消费的现价 + 涨跌基准);盘口五档等仅内存缓存,不落盘(方案明确)。


存储层接口映射总表

前端操作 /odl/api 端点 PositionManager 方法 SqliteStore 方法(升级) 原 DataStore 方法(废弃)
策略 tab 加载 strategy-positions getStrategyPositions getCurrentHoldings getDataset
添加(无持仓) add-shares addToStrategy openHolding setDataset(整策略重写)
添加(有持仓) add-shares addToStrategy addShares setDataset
移出(>0 remove-shares removeFromStrategy reduceShares setDataset
移出(=0 remove-shares removeFromStrategy closeHolding setDataset(删条目)
全部移入 move-all-shares moveAllUnallocatedToStrategy openHolding / addShares setDataset
一键清零 remove-all-shares clearStrategyShares 批量 closeHolding removeDataset(物理删)
删除策略 strategies/remove removeStrategy + clearStrategyShares 批量 closeHolding removeDataset
行情读取 market-snapshot MarketDataHub.getByCodes getMarketQuotes store.market.json 读
行情写回 (后台) MarketDataHub.ingest setMarketQuotes store.market.json 写

对前端的结论

  1. 前端零改动:所有操作仍走 /odl/api/* 端点,useRpc 封装、请求/响应结构不变;
  2. 行为增强(前端无感):清仓/一键清零/删除策略后数据转历史保留(closed_at),而非物理删除 —— 为 R-007 交易记录关联与复盘铺路;
  3. 行情落盘降维:只落 lastPrice/lastClose 两列(响应无需关心,服务端投影);
  4. 唯一可见差异:历史持仓数据存在(未来若做「持仓历史」UI,getHoldingHistory 可直接支撑)。