迭代16: 交易关联驱动持仓份额动态调整(R-018)
This commit is contained in:
+66
-3
@@ -364,7 +364,7 @@ CREATE INDEX IF NOT EXISTS idx_trade_fills_date ON trade_fills (trade_date);
|
||||
| 失败策略 | 同步失败**保留上次快照**,不清空不报错 | 读方继续消费旧快照,QMT 抖动不再白屏(强于旧的穿透行为) |
|
||||
| 空快照 | 双重确认(/health 可用 + getAsset 账户身份可识别)才接受为「真清仓」,否则视为异常保留旧快照 | 防一次异常响应清空缓存 |
|
||||
| 读路径 | `getAllPositions()` 读内存快照;快照为空 → 读穿透当场拉一次并回填;QMT 也挂 → 抛错(前端 LoadState 重试) | 首启兜底;未注入 positionSync 时保持旧行为(兼容) |
|
||||
| 幽灵持仓自动清仓 | QMT 快照连续 3 轮(约 30s)消失的 code,本地全部策略当前持仓 closeHolding 转历史;**账户身份守卫**:accountId 未知当轮跳过、账户切换当轮重置跳过(防误清);部分减持不触发(负数未分配由 UI 暴露) | 老师选定;清仓转历史不物理删除,历史保留语义不变 |
|
||||
| ~~幽灵持仓自动清仓~~(**退役,2026-09-08 R-018 数据域分界**) | ~~QMT 快照连续 3 轮消失 → 本地全部策略当前持仓 closeHolding 转历史~~ → **不再写 strategy_holdings**:同步机制只作用于对账单域(老师拍板:幽灵清仓本就是一个同步机制,不可以让幽灵把爪子伸太长);账本转历史唯一途径 = 卖出单关联减至 0(R-018);对账单码消失 → 快照无此行 + 漏关联软提示(positions/orphan-hints,只读) | R-014 选定时防本地账本残留;R-018 改由交易关联驱动账本生命周期,幽灵自动归档与「账本只由关联交易改」冲突 → 退役(迭代 16 实施,见 §12) |
|
||||
| 前端 | 零改动 | 三个接口数据源自动切换 |
|
||||
|
||||
### 11.2 数据流(持仓部分,替代原「读取时组装」描述)
|
||||
@@ -383,8 +383,71 @@ CREATE INDEX IF NOT EXISTS idx_trade_fills_date ON trade_fills (trade_date);
|
||||
| PositionManager | getAllPositions 改读快照 + 读穿透兜底(未注入 sync 时兼容旧穿透) | src/position/PositionManager.js |
|
||||
| 回归测试 | 纯内存 mock 34 项(同步/失败保留/空快照/幽灵防抖/账户守卫/读穿透/组装回归) | scripts/test-position-sync.mjs |
|
||||
|
||||
## 12. 约束条目(引用)
|
||||
## 12. 归属分段存储设计(R-018 落实,2026-09-08 迭代 16 实施)
|
||||
|
||||
- 技术约束-012:数据存储遵循本文件(SQLite 存储设计,含交易记录表 §10);
|
||||
> **变更理由**(R-018 老师拍板):交易归属从「纯标签」(trade_orders 单组 strategy_id/holding_id,不改数量)升级为**账本写操作**——关联即自动调整 strategy_holdings 份额;支持一笔委托拆多段分配多策略;撤段做逆操作。归属真相源从 trade_orders 两列迁移到**分段表**。
|
||||
|
||||
### 12.1 strategy_holdings 状态扩展(作废第三态 + 清仓份额快照)
|
||||
|
||||
```sql
|
||||
-- 幂等 ALTER(沿用 _ensureXxxColumn 模式)
|
||||
ALTER TABLE strategy_holdings ADD COLUMN void_at INTEGER; -- 作废时间(NULL=有效;非 NULL=建仓被撤=从未成立;不进历史层)
|
||||
ALTER TABLE strategy_holdings ADD COLUMN closed_shares REAL; -- 清仓前份额快照(closeHolding 写入,供撤卖出段恢复活动用)
|
||||
-- 活动唯一索引收窄(R-018:活动 = closed_at IS NULL AND void_at IS NULL)
|
||||
DROP INDEX IF EXISTS idx_active_holding;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_holding
|
||||
ON strategy_holdings (strategy_id, code) WHERE closed_at IS NULL AND void_at IS NULL;
|
||||
```
|
||||
|
||||
- 生命周期操作语义:closeHolding = shares 置 0 + closed_at + **closed_shares=清仓前份额**(shares 展示仍 0,R-016 Q2 语义不变);voidHolding(新增)= shares 置 0 + void_at(非清仓:建仓被撤 = 从未成立);
|
||||
- 作废行不进 R-016 历史「已清仓」层(getHoldingsHistory 过滤 void_at IS NULL),保留数据可追溯。
|
||||
|
||||
### 12.2 归属分段表 trade_order_attributions
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS trade_order_attributions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_id TEXT NOT NULL, -- → trade_orders.order_id
|
||||
strategy_id TEXT NOT NULL, -- 段归属策略
|
||||
holding_id INTEGER NOT NULL, -- 段锚点持仓(R-010 展开用)
|
||||
code TEXT NOT NULL, -- 冗余
|
||||
direction TEXT NOT NULL, -- buy/sell(冗余,撤段判向)
|
||||
volume REAL NOT NULL, -- 该段已成交量(>0;总额 ≤ order.traded_volume)
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_attr_order_strategy ON trade_order_attributions (order_id, strategy_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_attr_holding ON trade_order_attributions (holding_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_attr_strategy ON trade_order_attributions (strategy_id);
|
||||
```
|
||||
|
||||
- **真相源 = 段表**;trade_orders.strategy_id/holding_id 退役为冗余(保留列不删,防外部脚本 break;不再作为查询/过滤源);
|
||||
- 存量迁移:段表空且 trade_orders 有单组归属 → 迁为第一段(volume = 该单 traded_volume;幂等);
|
||||
- 查询适配:orders 附加归属、by-holding(R-010)、历史策略过滤(R-009)全部改经段表。
|
||||
|
||||
### 12.3 关键设计决策(R-018 迭代 16)
|
||||
|
||||
| 决策 | 结论 | 理由 |
|
||||
|---|---|---|
|
||||
| 归属真相源 | **分段表 trade_order_attributions**(order → 多段 strategyId/holdingId/volume) | 一笔委托可拆 N 段关联不同策略(R2/R4);单列两字段无法表达 |
|
||||
| 数据域分界 | PositionSync/幽灵清仓**只写对账单快照**,不写 strategy_holdings | R-018 老师拍板:同步机制不伸爪子到账本;账本只由交易关联 + 手动份额操作驱动 |
|
||||
| 关联驱动量 | 段 volume = 该段**已成交量** | Q4:撤单/废单不动账 |
|
||||
| 撤段逆操作 | 撤 buy 段 → 减回 / 归零作废(void);撤 sell 段 → 加回 / 恢复活动仓(closed_shares) | R3/R6/R7:作废 ≠ 清仓(不产生假清仓历史);恢复用 closed_shares 快照 |
|
||||
| 撤段约束 | 同 holding 内**逆序撤销**(乱序返回 segment-order-conflict);跨 holding 段独立可任意撤 | 账本正确性 > 操作便利(宁可拒绝不写错账) |
|
||||
| 事务 | setSegments 全量替换在**单事务**内(撤旧段 + 加新段) | 改归属原子性(R3 边界) |
|
||||
|
||||
### 12.4 对应实现(追加)
|
||||
|
||||
| 模块 | 职责 | 文件 |
|
||||
|---|---|---|
|
||||
| AttributionService | 归属服务:apply/revoke/setSegments(动作表判定 + 逆操作 + 事务) | src/trades/AttributionService.js(新增) |
|
||||
| SqliteStore | 增列/索引重建/voidHolding/close 快照/段表 CRUD/runInTransaction/查询改段表/存量迁移 | src/storage/SqliteStore.js |
|
||||
| api/trades.js | attribution-targets / attribution-set / attribution-segments;orders 附加段 | src/api/trades.js |
|
||||
| PositionSync | 删除幽灵清仓逻辑(只同步对账单快照) | src/position/PositionSync.js |
|
||||
| 回归测试 | 分界/动作表/撤段/迁移/软提示 | scripts/test-r018-attribution.mjs(新增) |
|
||||
|
||||
## 13. 约束条目(引用)
|
||||
|
||||
- 技术约束-012:数据存储遵循本文件(SQLite 存储设计,含交易记录表 §10、归属分段存储 §12);
|
||||
- 技术约束-011:测试/回归脚本禁止在真实数据上执行写操作(独立数据目录);
|
||||
- 技术约束-017(变更 1,2026-09-08):幽灵自动清仓退役,PositionSync 只同步对账单快照;
|
||||
- 本文件为新领域设计约束,后续数据存储变更以本文件为最终依据。
|
||||
Reference in New Issue
Block a user