Files
one_divine_lot/docs/05-需求池/已完成/R-013.md
T
kyugao 329ac67a71 docs(迭代11+归档): R-013 归档完成 + 迭代 11 复盘
- 迭代 11 复盘:实施事实(R-013 自定义字段 + 列显隐 + 单元格编辑 + unit)+
  经验教训(JSX 未导入白屏 / z.dict dts / SQLite 关键字列 / map this 丢失 / 列宽稳定)
- R-013 归档至 已完成/(归档头:日期/状态/实现迭代/讨论记录)
- 索引 R-013 → 已实现(已归档);PLAN-012 → 已完成
2026-09-02 17:34:51 +08:00

198 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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),**已定稿**,可进入计划范围。