docs(design): 多策略共享账户×实例预算制定稿spec——A策略session契约(卖出只卖自己+context属性清单+联调) B前后端实施(账户监视器/全局快照/定寸虚拟化/预算硬限制+默认剩余值/前端三层/对账恒等式/清理) C全删重建runbook(用户定:删全部+明开盘清仓+部署后再重建,顺序硬约束) D远期触发式;业界调研背书见memory multi-strategy-shared-account-plan [nas]
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
# Spec:多策略共享账户 × 实例预算制(定稿 2026-08-19)
|
||||
|
||||
> 用户确认的最终方案。背景调研与决策过程见 memory `multi-strategy-shared-account-plan`。
|
||||
> 本文是**实施规格**:A 部分是策略 session 的契约,B 部分是前后端 session 的实施项,C 部分是全删重建 runbook。
|
||||
> 原则:模拟盘(影子+实走)**零改动**(每实例独立虚拟账户=参照系);实盘共享期上实例预算制;绑专属账号后本机制自然退化,不需要改回。
|
||||
|
||||
## 0. 架构一句话
|
||||
|
||||
```
|
||||
策略实例 ──看到──> 实例账本(预算N万,归因自己的成交) ──下单──> 共享QMT账户
|
||||
│
|
||||
supervisor 账户监视器 ──写──> 全局账户快照(现金/持仓,一条)
|
||||
│
|
||||
预算校验(新建/修改时): Σ预算 ≤ 账户现金
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## A. 策略 session 交付(按本节完成即可,不需读 B/C)
|
||||
|
||||
### A1. 卖出只卖自己(P0,最先做)
|
||||
|
||||
**规则(magic number 铁律)**:调仓/轮换/清仓时,卖出目标集**必须**来自本实例持仓视图,**禁止**从 `context.portfolio.positions`(共享账户全量)取卖出名单。channel_test 现行"卖掉所有不在目标里的持仓"是反模式,必须改。
|
||||
|
||||
**通道(已上线,dae56e2)**:
|
||||
|
||||
```python
|
||||
# broker = 注入的 BrokerFacade(回测/实盘同构)
|
||||
pos = getattr(self.broker, "get_instance_positions", None)
|
||||
if pos is not None:
|
||||
my_positions = pos() # 实盘:本实例账本视图
|
||||
else:
|
||||
my_positions = {s: ... for s in context.portfolio.positions} # 回测/测试回退原逻辑
|
||||
```
|
||||
|
||||
**返回契约**:`Dict[symbol, {"amount": int, # 总持仓
|
||||
"closeable_amount": int, # T+1 可卖(已扣当日买入)
|
||||
"avg_cost": float}]`
|
||||
- 需要现价用 provider 查(视图不含 price,避免快照时点错位)。
|
||||
- 视图为空 dict = 本实例无持仓 → 轮换语义退化为"只买不卖"(**这是正确行为,不是 bug**)。
|
||||
|
||||
**涉及策略**:channel_test(rotate/swap_one/t1_probe 的卖出腿)、momentum/_ex(熊市清仓段)、small_cap/_ex(_rebalance 清仓段)、all_weather/_ex(调仓卖出段)、value_selection/_ex。逐个过一遍卖出路径。
|
||||
|
||||
**验收**:影子日志次日 9:35 轮换只卖自己账本内的票;单测覆盖"实例视图为空→零卖出"。
|
||||
|
||||
### A2. context 属性清单(P0,给前后端做虚拟化的输入)
|
||||
|
||||
盘点所有策略从 `context` 上读的属性,逐个列出(尤其 `context.portfolio.*`):
|
||||
|
||||
| 属性 | 哪些策略用 | 用途 |
|
||||
|------|-----------|------|
|
||||
| `portfolio.positions` | … | 卖出名单/持仓判断 |
|
||||
| `portfolio.total_value` | … | **定寸**(每只买多少) |
|
||||
| `portfolio.available_cash` | … | 资金判断 |
|
||||
| `portfolio.positions_value` / `locked_cash` / 其他 | … | … |
|
||||
| `current_dt` / `previous_date` / 其他非 portfolio 属性 | … | … |
|
||||
|
||||
输出就是这张表(补全真实值)。前后端用它实现 `InstancePortfolio` 代理——**清单里没有的属性默认透传真 context,不会漏**;清单的意义是把要覆盖的 portfolio 子属性做全,以及确认除 portfolio 外还有没有需要实例化的东西。
|
||||
|
||||
**验收**:表格发给前后端 session(写入 Gitea issue 或记忆均可)。
|
||||
|
||||
### A3. 配合定寸虚拟化联调(P1,在 A1/A2 之后)
|
||||
|
||||
前后端完成 B2 后:策略代码**不改**(虚拟化对策略透明),但需配合验证——channel_test 实盘每只买入应回到 `预算/6`(虚拟化前是全账户/6)。异常情况(策略读到了没覆盖的属性)反馈前后端。
|
||||
|
||||
---
|
||||
|
||||
## B. 前后端 session 交付
|
||||
|
||||
### B1. supervisor 账户监视器 + 全局快照(B3/B4 的地基)
|
||||
|
||||
- supervisor(`sanguo_live`)起一个账户监视线程:独立 probe 连接(session id 专用的 int),60s 查一次 QMT 账户资金+持仓,**upsert 一条**全局快照(不再挂任何实例名下)。
|
||||
- 存储:新表 `qmt_account_snapshot`(按 QMT 账号一行):
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS qmt_account_snapshot (
|
||||
account TEXT PRIMARY KEY, cash REAL, market_value REAL, total REAL,
|
||||
positions TEXT, -- 全账户持仓 JSON [{symbol,volume,can_use,avg_price,mv}]
|
||||
updated_at TEXT
|
||||
);
|
||||
```
|
||||
|
||||
- 快照不可用/过期(>10 分钟)时,预算校验**fail-closed**(见 B3)。
|
||||
- 监视器与子进程解耦:零实盘实例时也照常运行(删光重建期间校验数据不能断)。
|
||||
|
||||
### B2. 定寸虚拟化(InstancePortfolio 代理)
|
||||
|
||||
- 位置:`live_strategy._setup`,包装经 facade 注册的回调——wrapper 收到真 context 后,把 `context` 换成代理再调策略函数;代理对除 `portfolio` 外的一切属性透传。
|
||||
- `InstancePortfolio`(每次调用现算):
|
||||
- `available_cash` = 实例账本 cash
|
||||
- `total_value` = 账本 equity(现价取真 portfolio 同标的现价,缺则加权成本)
|
||||
- `positions` = 账本视图的 jq 风格对象(属性名以 A2 清单为准,至少 `total_amount/closeable_amount/avg_cost`)
|
||||
- 清单里的其余 portfolio 子属性按语义映射,账本没有的(如 locked_cash)给安全值
|
||||
- 引擎内部(订单撮合/风控)仍看真实账户,代理只作用于策略决策层。
|
||||
- 只在 live 注入(`get_active()` 非空);回测/影子不变。
|
||||
- 回归测试:代理属性映射表逐项测;不覆盖属性透传测试。
|
||||
|
||||
### B3. 预算硬限制 + 默认值(用户定稿)
|
||||
|
||||
- 预算载体 = `live_accounts.initial_capital`(含义升级为资金额度,不新增字段)。
|
||||
- **新建/修改校验(后端,fail-closed)**:
|
||||
- `剩余 = snapshot.cash − Σ 其他实盘实例预算`(同共享池;绑专属账号的实例未来不参与)
|
||||
- `本次预算 > 剩余` → 400,报文带剩余数
|
||||
- 快照缺失/过期(>10min)→ 400 "账户快照不可用,稍后再试"(不猜数)
|
||||
- **前端**:创建/编辑表单初始资金默认值 = 剩余(新接口 `GET /live/budget-info` → `{account_cash, allocated, remaining}`);实时占用率条(allocated/cash);超限红条+禁提交。
|
||||
- 存量实例不追溯;总览页持续显示占用率,存量超限亮黄条。
|
||||
- 影子/模拟盘不受此限制(虚拟资金)。
|
||||
|
||||
### B4. 前端三层展示
|
||||
|
||||
实盘详情页三块(Tab 或分区):
|
||||
1. **本策略买卖**:`live_trades` 按 account(归因后只有本实例的成交)
|
||||
2. **本策略持仓+净值**:`live_positions`/`live_balance`(实例视图,已上线)
|
||||
3. **账户实况**:`qmt_account_snapshot`(现金/总资产/持仓列表 + `Σ实例市值 + 未归因` 的分解)
|
||||
|
||||
### B5. 对账改造
|
||||
|
||||
15:10 日终对账改为恒等式优先:
|
||||
- **恒等式**:`snapshot.market_value ≈ Σ 实例账本市值 + 未归因遗留仓`(容差 0.5%,价格时点差)
|
||||
- 恒等式通过后,逐对 live↔shadow 行为对比(配对 v2 不变,instance_id 精确配对)
|
||||
- 未归因仓单列在报表里(重建后应为 0 或接近 0——这是"从新建开始"的直接红利)
|
||||
|
||||
### B6. 收尾清理
|
||||
|
||||
- 删光重建后:`DELETE FROM live_balance/live_positions/live_trades WHERE account_id NOT IN (SELECT id FROM live_accounts)`(孤儿行清理,一次性)
|
||||
- QMT remark `bt:live_strateg:<hash>` ↔ 实例映射查清(对账交叉验证用,顺手项)
|
||||
|
||||
---
|
||||
|
||||
## C. 全删重建 Runbook(用户已定:一切从新建开始)
|
||||
|
||||
| 步骤 | 动作 | 谁 | 时机 |
|
||||
|------|------|-----|------|
|
||||
| 1 | 前端删除**全部**实盘实例 + **全部**影子实例(删除即自动停引擎,配对从新) | 用户(界面操作) | 随时(今晚即可) |
|
||||
| 2 | QMT 客户端手动**卖出所有持仓**释放资金 | 用户 | 明天开盘 |
|
||||
| 3 | B1→B5 开发、测试、push(CI→NAS 自动) | 前后端 | 与 1/2 并行 |
|
||||
| 4 | 策略 session 完成 A1/A2 | 策略 session | 与 3 并行 |
|
||||
| 5 | 合并部署 VPS(按口令闸门,用户说「推vps」) | 用户授权 | 3/4 完成后 |
|
||||
| 6 | **重建舰队**:每策略 1 实盘 + 1 影子(4+4);实盘预算按用户数字(表单默认=剩余,硬限制兜底);**必须在步骤 5 之后**——虚拟化没上线前建的新实例仍会按全账户定寸 | 用户(界面)+ 前后端盯 | 部署完当晚 |
|
||||
| 7 | 观察日:验证 A1/A3 验收标准 + 首次干净对账 | 前后端 | 重建次日 |
|
||||
|
||||
> ⚠️ 步骤 6 的顺序是硬约束:明天卖完资金后**不要先建实例**——新实例在虚拟化上线前依然按全账户定寸,995 万现金池会再次被竞赛。重建等部署完成。
|
||||
|
||||
---
|
||||
|
||||
## D. 明确不做(触发式远期)
|
||||
|
||||
净额合成引擎 / 策略输出目标 / 中央风控 / 执行层——等真有 2+ 个**不同**策略并行时再立项。绑专属账号(账号口子)随时可插:插入后该实例预算=专属账号真实资金、账本=真实账本,本 spec 机制对其自然退化。
|
||||
Reference in New Issue
Block a user