docs(迭代11+归档): R-013 归档完成 + 迭代 11 复盘

- 迭代 11 复盘:实施事实(R-013 自定义字段 + 列显隐 + 单元格编辑 + unit)+
  经验教训(JSX 未导入白屏 / z.dict dts / SQLite 关键字列 / map this 丢失 / 列宽稳定)
- R-013 归档至 已完成/(归档头:日期/状态/实现迭代/讨论记录)
- 索引 R-013 → 已实现(已归档);PLAN-012 → 已完成
This commit is contained in:
2026-09-02 17:34:51 +08:00
parent aac16b904c
commit 329ac67a71
4 changed files with 56 additions and 3 deletions
+198
View File
@@ -0,0 +1,198 @@
# R-013 策略自定义字段配置(定义随策略,值落库) · 已完成
> 归档日期:2026-09-02 需求状态:**已完成**
> 原索引:docs/05-需求池/需求池索引.md(主索引保留 R-013 条目,指向本归档)
> 实现迭代:11-策略自定义字段配置(验收通过,迭代复盘见 docs/04-迭代记录/11-策略自定义字段配置/迭代复盘.md)
> 讨论记录:2026-09-02 定稿(Q1-Q4 + D6);迭代 11 实施中扩展列显隐/单元格编辑/unit(老师确认);实现见迭代 11 技术实现方案/程序结构设计/复盘
> 状态:**已定稿**2026-09-02,老师确认 Q1-Q4)| 登记日期:2026-09-02
> 来源:老师提出(2026-09-02,策略可自定义 / 可扩展讨论;T-009 转正)
> 优先级:P1(策略管理能力扩展,无数据风险)
> 关联:T-003(策略配置文件骨架,起草 —— 本需求吸收其「策略数据结构」部分)、T-006(数据池 + 策略表格动态字段配置,起草 —— 边界:本需求=策略自定义字段,T-006=表格列配置)
## 需求描述
策略要可**自定义、可扩展**:每个策略可定义自己需要的一组**自定义字段**(带类型:文本 / 数字 / 布尔 / 枚举),该策略下的每个持仓(strategy_holdings 行)按策略定义存一份**键值对值**(JSON,可任意扩展)。字段**定义随策略走**(不落库),**值落库**strategy_holdings 新增 values JSON 列)。
## 现状(代码审查 2026-09-02
| 项 | 现状 |
|---|---|
| 策略定义 | settings.strategies = [{ id, name }]R-011 收窄),存 DSH settings |
| 持仓存储 | strategy_holdingsholding_id / strategy_id / code / shares / created_at / closed_at),无自定义字段列 |
| 定义位置 | 设置页「策略分组」子 tab:策略行内 CRUD(新增 / 重命名 / 删除),无字段配置能力 |
| 持仓渲染 | 策略持仓 tab 固定列(代码/名称/份额/现价/盈亏/操作),R-010 已支持持仓行展开看交易记录 |
## 讨论结论(2026-09-02 老师确认 Q1-Q4
- **D1**(第二轮)字段**定义不存数据库**:随策略定义走,扩展 settings.strategiesconfigSchema 数组);数据库只存值。
- **D2**(第三轮 + Q1)值 = **持仓级 JSON 键值对**strategy_holdings 新增一列 values TEXT(JSON);每行(一个策略 × 一只股票)一套值;某持仓需要哪些字段 = 看所属策略的 configSchema。
- **D3**Q2/Q3)字段**类型化**:文本 / 数字 / 布尔 / **枚举**(预设选项下拉,有用,采纳)。定义含:key / label / type / enum 选项 / 默认值。
- **D4**(第三轮)配置入口 = **设置页「策略分组」子 tab**:策略行做成**可展开**,展开区添加 / 配置字段(名称 + 类型 + 枚举选项 + 默认值)。
- **D5**Q4)兼容:旧策略无 configSchema(缺省空数组)→ 持仓行不展示自定义字段区,行为与现在完全一致;旧行 values 列缺省 NULL(空);无迁移历史值。
- **D6**2026-09-02 老师确认,2026-09-02 迭代 11 演进为列化)值编辑入口原定为**持仓行展开区**;随「自定义字段列化 + 单元格点击编辑」演进(老师选 A):字段直接作为策略持仓表**列**展示,点击字段单元格进入内联编辑(text/number 输入框、boolean 开关、enum 下拉),编辑即调 API 写回该行 values。展开区(交易明细)不再显示自定义字段。
## 目标数据模型(定稿)
```js
// settings.strategies(扩展 configSchema;不落库)
strategies: [{
id: 'grid-supermarket',
name: '网格超市',
configSchema: [
{ key: 'gridGap', label: '网格间距', type: 'text', def: '3%' },
{ key: 'gridAmount', label: '单格金额', type: 'number', def: 10000 },
{ key: 'hasStop', label: '设置止损', type: 'boolean', def: false },
{ key: 'riskLevel', label: '风险等级', type: 'enum', enum: ['低','中','高'], def: '中' },
],
}]
```
```sql
-- strategy_holdings 新增列(幂等 ALTER,沿用 _ensureTradeAttributionColumns 模式)
ALTER TABLE strategy_holdings ADD COLUMN values TEXT; -- JSON 键值对
```
```js
// 持仓行 values 示例(key 与 configSchema.key 对齐;未定义的额外键允许存在=可扩展)
{ "gridGap": "3%", "gridAmount": 10000, "hasStop": true, "riskLevel": "中" }
```
**语义要点**
- key 为稳健标识(英文 slug),label 为 UI 展示名(可中文);值 JSON 以 key 存;
- 枚举字段值校验 ∈ enum 选项;数字字段 UI 数字输入;布尔渲染开关;文本自由输入——校验在服务端写回时执行;
- 定义变更对已有值数据无破坏(旧值保留,新增字段缺省用 def,未配置字段不渲染)。
## 端到端示例(Sample,供后续推进参考)
> 场景:现有三个策略 —— 网格超市(已配自定义字段)、手动做T(未配,旧策略)、长线持有(新增策略,配另一组字段)。沿用项目现有个股:601117.SH(中国交建)、300057.SZ(万顺新材)、600719.SH(大连热电)。
### 1. 策略定义(settings.strategies,不落库)
```js
// 完整值(新增/编辑后持久化于 DSH settings
strategies: [
{
id: 'grid-supermarket',
name: '网格超市',
configSchema: [
{ key: 'gridGap', label: '网格间距', type: 'text', def: '3%' },
{ key: 'gridAmount', label: '单格金额', type: 'number', def: 10000 },
{ key: 'hasStop', label: '设置止损', type: 'boolean', def: false },
{ key: 'riskLevel', label: '风险等级', type: 'enum', enum: ['低','中','高'], def: '中' },
],
},
{ id: 'manual-t', name: '手动做T' }, // 旧策略:无 configSchema(兼容,缺省 []
{
id: 'long-term-hold',
name: '长线持有',
configSchema: [
{ key: 'targetPrice', label: '目标价', type: 'number', def: null },
{ key: 'notes', label: '持仓备注', type: 'text', def: '' },
],
},
]
```
### 2. strategy_holdings 表数据(含 values 列)
```sql
-- ALTER 幂等补列后,既有行 values 自动为 NULL(不迁移)
ALTER TABLE strategy_holdings ADD COLUMN values TEXT;
SELECT holding_id, strategy_id, code, shares, values FROM strategy_holdings WHERE closed_at IS NULL;
-- 结果示例:
-- 1 | grid-supermarket | 601117.SH | 600 | {"gridGap":"3%","gridAmount":10000,"hasStop":true,"riskLevel":"高"}
-- 2 | grid-supermarket | 300057.SZ | 1000 | {"gridGap":"5%","gridAmount":8000,"hasStop":false,"riskLevel":"低"}
-- 8 | manual-t | 600719.SH | 1000 | NULL(手动做T 未配字段 → 无值)
-- 9 | long-term-hold | 601117.SH | 200 | {"targetPrice":7.5,"notes":"周线突破再加仓"}
```
> 同策略多行各存各的值(600719 与 300057 的网格间距不同);不同策略可有完全不同的字段(long-term-hold 无 gridGap)。
### 3. API 交互示例(/odl/api 端点,RPC 通道)
```js
// ① 读策略(含 configSchema
call('one-divine-lot/strategies', {})
// 响应 value: 见 §1 完整数组(策略行自带 configSchema
// ② 配字段:整表更新 strategies(改 configSchema 随整表提交)
call('one-divine-lot/strategies/update', { strategies: [ /* §1 完整数组 */ ] })
// ③ 读某策略持仓(每行附 values)
call('one-divine-lot/strategy-positions', { args: { strategyId: 'grid-supermarket' } })
// 响应 value: [
// { holdingId: 1, code: '601117.SH', shares: 600, /* 既有列 */ values: { gridGap: '3%', gridAmount: 10000, hasStop: true, riskLevel: '高' } },
// { holdingId: 2, code: '300057.SZ', shares: 1000, /* ... */ values: { gridGap: '5%', gridAmount: 8000, hasStop: false, riskLevel: '低' } },
// ]
// ④ 写某持仓行的值(无 values → 传全量;已有 → 合并更新)
call('one-divine-lot/holdings/values-update', { args: { holdingId: 1, values: { gridGap: '4%', gridAmount: 10000, hasStop: true, riskLevel: '高' } } })
// 响应 value: { holdingId: 1, values: { gridGap: '4%', gridAmount: 10000, hasStop: true, riskLevel: '高' } }
```
### 4. UI 形态
**设置页 →「策略分组」子 tab(配置字段定义)**
```
[▸] 网格超市 [重命名] [删除]
自定义字段:
展示名 | key | 类型 | 选项(枚举) | 默认值 | 操作
网格间距 | gridGap | 文本 | - | 3% | [删除]
单格金额 | gridAmount | 数字 | - | 10000 | [删除]
设置止损 | hasStop | 布尔 | - | 关 | [删除]
风险等级 | riskLevel | 枚举 | 低/中/高 | 中 | [删除]
[+ 添加字段](名称/类型/选项/默认值 一行表单) [保存]
[▸] 手动做T ...(无字段区,保持现状)
[▸] 长线持有 ...(目标价 / 持仓备注)
```
**策略持仓 tab(迭代 11 演进:自定义字段直接为表格列,点击单元格内联编辑)**
```
代码 名称 现价 涨幅 网格间距 单格金额 设置止损 风险等级 策略份额 操作
601117.SH 中国交建 7.20 +0.56% 4% 10000 ✓ 高 600 [移出]
300057.SZ 万顺新材 5.10 -1.2% 5% 8000 ✗ 低 1000 [移出]
(点击「网格间距/单格金额/风险等级」单元格 → 输入框/下拉编辑,Enter 保存;
点击「设置止损」单元格 → 开关直接切换;空值显示 — / 默认值 def)
```
> 表格顶部另有「列设置」按钮:每策略独立配置列显隐与顺序(基础数据列 + 自定义字段列;代码/名称/操作固定)。
### 5. 渲染 / 校验逻辑参考
```js
// 渲染(策略持仓 tab):configSchema 决定字段集合,values 提供值,缺省回退 def
const schema = strategy.configSchema ?? []; // 无定义 → 不渲染字段区
const merged = schema.map((f) => ({ ...f, value: (row.values ?? {})[f.key] ?? f.def }));
// 校验(服务端 holdings/values-update 写回时)
function validateField(f, v) {
if (v === undefined || v === null || v === '') return null; // 允许空
if (f.type === 'number' && !Number.isFinite(Number(v))) return f.label + ' 需为数字';
if (f.type === 'enum' && !f.enum.includes(v)) return f.label + ' 需在选项内: ' + f.enum.join('/');
if (f.type === 'boolean' && typeof v !== 'boolean') return f.label + ' 需为布尔';
return null;
}
```
## 涉及改动面(技术方案,定稿)
1. **src/settings.js**strategySchema 的 strategies 项扩展 configSchemaschemastery array of objecttype union text|number|boolean|enum);读取归一化(旧项缺省 []);strategies/update 沿用整表更新语义。
2. **src/storage/SqliteStore.js**init() 增加 _ensureHoldingValuesColumnPRAGMA table_info → ALTER,幂等,只读容忍同 trade_orders 先例);openHolding / addShares / reduceShares / closeHolding 保持既有列行为(values 不随份额操作变动);新增 readValues(holdingId) / writeValues(holdingId, values)。
3. **src/api/strategies.js**strategy-positions 返回项附加 values;新增端点 holdings/values-update {holdingId, values} 写回;strategies/update 已可携带 configSchema(整表更新天然支持)。
4. **src/client/views/SettingsSection.jsx**:「策略分组」子 tab 策略行加展开手柄:展开区 = 字段列表(label/key/type/enum/def+ 添加/删除/编辑字段表单;保存走 strategies/update。
5. **src/client/views/StrategyTab.jsx**:持仓行展开区(R-010 基础)增加「自定义字段」区块:按该策略 configSchema 渲染输入控件(文本/数字/布尔/枚举),值来自该行 values,编辑保存调 values-update;行内表格可选加字段列。
6. 回归脚本:设置字段定义 → 批量写/改持仓行 values → 校验存储与读取(独立数据目录,技术约束-011)。
## 定稿记录(Q1-Q42026-09-02 老师确认)
- **Q1** 字段定义 = 策略级(某策略下每笔持仓需要的字段由该策略定义决定);值 = 持仓级(每行一套键值对)→ 确认。
- **Q2** 字段加类型(文本 / 数字 / 布尔 / 枚举)→ 确认。
- **Q3** 支持枚举字段(预设选项下拉)→ 确认(有用)。
- **Q4** 旧策略无自定义字段:沿用现有字段定义(不迁移、不补默认),值列缺省空 → 确认。
- **D6(值编辑入口)**:持仓行展开区编辑(老师确认 2026-09-02)→ 迭代 11 演进为**表格列 + 单元格点击编辑**(老师选 A,2026-09-02)。
> 三要素满足(边界清楚 / 核心逻辑明确 / 老师确认 Q1-Q4),**已定稿**,可进入计划范围。