迭代16: 交易关联驱动持仓份额动态调整(R-018)

This commit is contained in:
2026-09-08 11:09:05 +08:00
parent 22ec732607
commit 1223e2e74b
25 changed files with 1659 additions and 405 deletions
+74
View File
@@ -0,0 +1,74 @@
# 需求:R-018 交易关联驱动持仓份额动态调整(归属 = 账本写操作)
> 登记:2026-09-08 来源:老师指令(2026-09-08 新优化需求讨论)| 需求状态:**已完成**
> 归档日期:2026-09-08 | 实现迭代:16-交易关联驱动持仓份额动态调整(验收通过 2026-09-08
> 讨论记录索引:R-018.md 正文(Q1-Q5/R1-R7 + 数据域分界 + T2/T3);实现细节见 04-迭代记录/16-交易关联驱动持仓份额动态调整/
> 归档路径:本文件已移入 05-需求池/已完成/
> 迭代状态标记:已定稿(2026-09-08)→ 已实现(迭代 16)→ **已归档(验收通过 2026-09-08,口径=自动化为主体 + 只读走查 + 真实使用顺带确认)**
## 需求描述(一句话)
从**交易记录出发**给委托关联策略/持仓时,**自动同步调整该策略下持仓的份额**(关联即账本写操作),支持一笔委托拆多段分配给不同策略;取消/改归属时做逆操作回滚份额;账本生命周期只由关联交易 + 手动份额操作驱动,同步机制不写账本。
## 〇、数据域分界(最根本前提,2026-09-08 老师拍板)
**「全部持仓」= 对账单域**:唯一数据源 = PositionSyncQMT 快照,10s 内存同步,整体替换)。**包括幽灵清仓在内的一切同步机制只作用于对账单域**,QMT 说了算(老师原话:幽灵清仓本就是一个同步机制,不可以让幽灵把爪子伸太长)。
**「策略持仓」= 账本域(strategy_holdings**:写操作只来自两条通道——
1. **交易关联分配器**(本需求新增):建仓 / 加仓 / 减仓 / 清仓 / 作废;
2. **手动份额操作**(+ 添加持仓 / 移出 / 全部移入):无交易来源时的兜底通道(Q3:保留现在的建仓逻辑;份额以 QMT 持仓信息为基础创建,受「已分配 ≤ 总持仓」校验)。
**既有行为变更**:R-014 定稿的「幽灵自动清仓」(连续 3 轮消失 → closeHolding 本地全部策略当前持仓)**不再写 strategy_holdings**;账本行转历史只由「卖出单关联 → reduceShares 归 0 → closeHolding」产生。影响既有定稿:技术约束-017(幽灵清仓条款)、产品约束-011、R-014、迭代 12(已实现待验收)需修订;PositionSync 删减幽灵清仓逻辑(变更理由 = 本次讨论,写入变更记录)。
## 背景与现状
1. **QMT 实盘持仓**(对账单):PositionSync 10s 内存快照,真实 volume/price(技术约束-017);
2. **本地策略账本** strategy_holdingsshares + 生命周期(open/add/reduce/close),此前靠手动份额操作维护;账本 ≠ 对账单(R-014:holding_id 是交易锚点,不掺易变快照);
3. **交易归属** trade_orders.strategy_id/holding_id:手动设置(R-009/R-017),只做过滤/展示/追溯,**不改数量**——本需求改变这一点。
**展示行关系(防误解)**:策略持仓 tab 当前持仓行 = **QMT 快照 ∩ 账本 shares>0**(行由对账单驱动、份额由账本驱动)。卖出全清 → QMT 无此 code → 展示行随快照消失(≤10s,与幽灵清仓无关);此时账本行仍为当前持仓(closed_at NULL),作为**待关联锚点**保留,直到卖出单被关联。
## 讨论记录(2026-09-08
| # | 决策点 | 结论(老师拍板) |
|---|---|---|
| 1 | 归属候选按什么给?(Q1) | **策略级下拉**,不做预先判断(有无持仓/历史持仓),指向动作发生后按目标策略当时的活动持仓状态现场判定 |
| 2 | 驱动量基准?(Q4 | **已成交量**(撤单/废单不动账) |
| 3 | 交互模型?(R4 统一分配器) | 不分买卖两套流程:一笔委托已成交量可拆 0..N 段(策略×量);待分配余额 = 成交量 − 已分配段和;默认输入 = 余额全量可改小;允许部分关联(剩余保留「未关联」醒目提示);买入卖出均可拆多段 |
| 4 | 买入关联动作?(原始第 2 条) | 该策略有活动持仓 → `addShares` 加仓;无活动持仓(含曾有历史/作废行)→ `openHolding` 建**新仓**(不复活旧行;Q5) |
| 5 | 卖出关联动作?(原始第 3 条) | 活动仓 ≥ 段量 → `reduceShares`,归 0 → `closeHolding`;活动仓 < 段量 → **自动截断**段量=可容纳量归零清仓,余额保留继续分配;该策略无活动持仓 → 关联失败提示(R1) |
| 6 | 撤段逆操作?(R3,粒度=段) | 撤建仓买入段 → 持仓**作废**(新增第三态,非 closeHolding,不产生假清仓历史);撤加仓买入段 → `reduceShares` 减回,归 0 且无真实卖出 → 作废;撤卖出段 → **恢复活动仓 + 份额加回**(撤销语义,区别于 Q5 新买入不接旧仓) |
| 7 | 改归属语义? | 撤旧段 + 加新段,**原子**;撤卖出段若该 holding 已被真实卖出(非本单所致)无法恢复 → 提示不可撤 |
| 8 | 数据域分界?(本轮核心) | 幽灵清仓等同步机制只作用于对账单域;策略持仓写操作只来自交易关联 + 手动份额操作 |
| 9 | T1 卖出关联 vs 幽灵清仓时序竞态 | **关闭**:幽灵不写账本 → 卖出后账本行保留为待关联锚点,无 30s 竞态 |
| 10 | T2 R-0177 天清仓候选) | **退役确认**:新时序下清仓发生在卖出单关联之后,不存在「系统先清、用户后补」场景,候选回归「当前持仓 + 未关联」;R-017 的 holdingId 键控/占位安全逻辑保留 |
| 11 | T3 漏关联兜底 | **软提示确认**:检测「账本有活动行 + QMT 快照无此 code」时提示老师去关联/移出;不自动写账本(不违背分界) |
## 核心规则汇总
| 触发 | 判定 | 动作 |
|---|---|---|
| 买入段 | 该策略该 code 有活动持仓 | `addShares`(加仓,量=段量) |
| 买入段 | 无活动持仓(含曾有历史/作废行) | `openHolding` 建新仓(不复活旧行) |
| 卖出段 | 活动仓 ≥ 段量 | `reduceShares`;归 0 → `closeHolding`(账本转历史唯一途径) |
| 卖出段 | 活动仓 < 段量 | 自动截断:段量=可容纳量 → 归零清仓,余额保留继续分配 |
| 卖出段 | 该策略无活动持仓 | 关联失败提示 |
撤段逆操作:撤建仓买入 → 持仓作废(第三态);撤加仓买入 → 减回归 0 作废;撤卖出 → 恢复活动仓 + 份额加回。改归属 = 撤旧段 + 加新段(原子)。
## 存储 / 模型调整方向
1. **holding 第三态**strategy_holdings 增加作废标记(如 `void_at`);活动 = `closed_at IS NULL AND void_at IS NULL`;作废行不进 R-016 历史「已清仓」层,保留数据可追溯;活动唯一索引(同策略同 code)收窄至两态皆空;
2. **归属单列 → 分段表**:新表(如 `trade_order_attributions`order_id → strategy_id/holding_id/volume/direction/created_at),一笔委托 N 段;R-010 by-holding 与 R-009 history 策略过滤改经段表 join(同一单可出现在多个 holding 展开里);trade_orders 原两列退役或降级为「主归属」快照,已有单组归属迁移为第一段;
3. **软提示(T3**:账本有活动行 + QMT 快照无此 code 时的可见性提示(不动账本)。
## 边界
**做**:幽灵清仓账本侧退役(PositionSync 删幽灵清仓逻辑)+ 策略级候选 + 统一份额分配器(拆段/余额)+ 买卖份额联动 + 撤段逆操作(含作废第三态)+ 归属分段存储 + 查询/展示适配 + R-017 7 天候选退役(holdingId 键控/占位保留)+ 漏关联软提示 + 回归。
**不做**:自动归属推导仍不暴露(可另议);全部持仓 tab / 未分配 tab 份额联动 UI;跨策略「未分配余额」自动兜底;账本写操作自动化兜底(软提示不自动改账本)。
## 验收
见规划后的迭代验收标准(本需求涉及既有已实现功能变更:迭代 12 幽灵清仓语义、迭代 15 R-017 退役,验收需含存量回归)。