Files
one_divine_lot/docs/05-需求池/R-013.md
T
kyugao 6f74ed4a79 docs(结构约束同步): 活动文档路径对齐新目录结构
归类重构(技术约束-016)落地后同步活动文档旧路径:
- 数据存储设计.md: 三处对应实现表 12 处 src/component/* → storage/position/market/trades
- R-013 / PLAN-012 / 迭代11技术实现方案: SqliteStore/PositionManager 路径更新
- 迭代11程序结构设计: 补后续动作记录(历史档案按追加式原则不改写)
2026-09-02 15:43:26 +08:00

194 lines
11 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,老师确认 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 老师确认)值编辑入口 = **策略持仓 tab 持仓行展开区**(沿 R-010 展开能力),展开后按 configSchema 渲染各字段输入控件,编辑即调 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 → 持仓行展开区(编辑值,R-010 展开能力之上)**
```
601117.SH 中国交建 600股 现价 7.20 +0.56% [展开]
└ 交易记录(R-010 已有)
└ 自定义字段:
网格间距 [ 4% ]
单格金额 [ 10000 ]
设置止损 [ ✓ ]
风险等级 [ 高 ▼ ]
[保存]
```
### 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)。
> 三要素满足(边界清楚 / 核心逻辑明确 / 老师确认 Q1-Q4),**已定稿**,可进入计划范围。