迭代22+23: 策略tab静态化 + 计算字段(数字/判定型)+ 网格超市信号字段

- R-027 静态化:策略恒为内置两项(不可增删改名,保留显隐/排序);设置页移除「策略分组」;
  字段配置入口迁到策略 tab 内「列设置」旁(FieldConfigDialog + schema-update 单策略写入)
- R-026 计算字段:自写公式引擎(中文变量、四则/括号/round·abs·min·max、缺值短路→—)+
  变量目录(行情/合约/持仓/自定义字段)+ strategy-positions 逐行现算(只读内存缓存,不落库、列只读)
- R-028 判定型:比较运算 + and/or + inferResultKind + 结果类型一致性校验 + 判定列 ✓/— 渲染;
  网格超市落地 可下空单=涨停价>基准值+网格大小、可下多单=跌停价<基准值-网格大小
- 变量选择改标签平铺(老师反馈);公式手册 docs/99-其他材料/计算字段公式说明.md
- 约束同步:产品约束-002/003/004/009/013/014、技术约束-014/015/022/023/024、UI约束-002/003/005/008
- 回归:新增 test-r026/test-r027,更新 r011/r013/r017,17 个脚本全绿;typecheck/build 通过
This commit is contained in:
2026-09-10 14:05:06 +08:00
parent 3dfa39b8bf
commit ec02a791ee
40 changed files with 3369 additions and 659 deletions
+42 -66
View File
@@ -1,18 +1,16 @@
/**
* R-011 回归测试:tabs 统一管理数据层
* R-011 回归测试:tabs 统一管理数据层R-027 策略 tab 静态化)
* 独立内存 mock scopesettings 读写),验证:
* 1. 归一化迁移:旧布尔对象 tabs → 有序数组(内置保留原显隐,策略接续追加)
* 2. 迁移:旧隐藏策略 → visible=trueQ3
* 3. getTabs:策略行 name join strategiesQ4 自动跟随改名
* 3. getTabs:策略行 name 由内置常量派生(改名场景已退役
* 4. updateTabs:整表更新(顺序 + 显隐)
* 5. addStrategy:联动追加 tab 条目(末尾,Q2)
* 6. removeStrategy:联动删除对应 tab 条目(D3)
* 5. 策略 tab 静态化(R-027):getStrategies 恒 2 项;normalizeTabs 恒 5 条;
* 存量自建策略 tab 条目被丢弃;addStrategy/removeStrategy/renameStrategy 等退役导出不存在
* 纯内存,不触真实数据(技术约束-011)
*/
import {
getTabs, updateTabs, normalizeTabs,
addStrategy, removeStrategy, renameStrategy, getStrategies,
} from '../src/settings.js';
import { getTabs, updateTabs, normalizeTabs, getStrategies } from '../src/settings.js';
import * as settingsMod from '../src/settings.js';
let pass = 0, fail = 0;
function assert(cond, msg) {
@@ -46,93 +44,71 @@ assert(oldTabs[1].refKey === 'tradeRecords' && oldTabs[1].visible === false, '
assert(oldTabs[2].refKey === 'watchlist' && oldTabs[2].visible === true, '内置 watchlist 保留旧显隐=true');
const manualTab = oldTabs.find((t) => t.kind === 'strategy' && t.refId === 'manual-t');
assert(manualTab && manualTab.visible === true, "旧隐藏策略 manual-t 迁移后 visible=trueQ3");
assert(manualTab && manualTab.name === '手动做T', '策略行 name 来自 strategies');
assert(manualTab && manualTab.name === '手动做T', '策略行 name 来自内置常量');
assert(oldTabs[3].order === 3 && oldTabs[4].order === 4, '策略按 order 接续(order 连续)');
// ---------- 2. 已归一化数组直接返回 ----------
console.log("\n[2] 已归一化数组直接返回");
const arrScope = makeScope({ tabs: [
{ id: 'tab-a', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 1 },
{ id: 'tab-b', kind: 'builtin', refKey: 'watchlist', name: '关注列表', visible: true, order: 0 },
{ id: 'tab-all-positions', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 1 },
{ id: 'tab-watchlist', kind: 'builtin', refKey: 'watchlist', name: '关注列表', visible: true, order: 0 },
] });
const arrTabs = normalizeTabs(arrScope);
assert(arrTabs[0].id === 'tab-b' && arrTabs[1].id === 'tab-a', '数组按 order 排序');
assert(arrTabs[0].id === 'tab-watchlist' && arrTabs[1].id === 'tab-all-positions', '数组按 order 排序');
assert(arrTabs.length === 5, '缺失条目补齐(恒 5 条)');
// ---------- 3. 策略行 name join(改名自动跟随,Q4 ----------
console.log("\n[3] getTabs 策略名 join");
// ---------- 3. getTabs策略行 name 由常量派生(改名已退役 ----------
console.log("\n[3] getTabs 策略行 name");
const joinScope = makeScope({
strategies: [{ id: 'manual-t', name: '手动做T新名' }],
strategies: [{ id: 'manual-t', name: '手动做T新名' }], // 存量改名残留:不再参与
tabs: [
{ id: 'tab-all', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 0 },
{ id: 'tab-s', kind: 'strategy', refId: 'manual-t', name: '旧名', visible: true, order: 1 },
{ id: 'tab-all-positions', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 0 },
{ id: 'tab-strategy-manual-t', kind: 'strategy', refId: 'manual-t', name: '旧名', visible: true, order: 1 },
],
});
const joined = getTabs(joinScope);
assert(joined.find((t) => t.id === 'tab-s').name === '手动做T新名', '策略改名后 tabs 行 name 自动跟随(Q4');
assert(joined.find((t) => t.id === 'tab-strategy-manual-t').name === '手动做T', '策略行 name 恒为内置常量(存量行 name 被覆盖');
// ---------- 4. updateTabs 整表更新 ----------
console.log("\n[4] updateTabs 整表更新");
const updScope = makeScope({ tabs: [
{ id: 'tab-a', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 0 },
{ id: 'tab-b', kind: 'builtin', refKey: 'watchlist', name: '关注列表', visible: true, order: 1 },
{ id: 'tab-all-positions', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 0 },
{ id: 'tab-watchlist', kind: 'builtin', refKey: 'watchlist', name: '关注列表', visible: true, order: 1 },
] });
await updateTabs(updScope, [
{ id: 'tab-b', kind: 'builtin', refKey: 'watchlist', name: '关注列表', visible: false, order: 0 },
{ id: 'tab-a', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 1 },
{ id: 'tab-watchlist', kind: 'builtin', refKey: 'watchlist', name: '关注列表', visible: false, order: 0 },
{ id: 'tab-all-positions', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 1 },
]);
const updTabs = getTabs(updScope);
assert(updTabs[0].id === 'tab-b' && updTabs[0].visible === false, '整表更新:顺序与显隐生效');
assert(updTabs.length === 2, '整表更新后条目数不变');
assert(updTabs[0].id === 'tab-watchlist' && updTabs[0].visible === false, '整表更新:顺序与显隐生效');
assert(updTabs.length === 5, '归一化后恒 5 条(缺失策略行补齐)');
// ---------- 5. addStrategy 联动追加 tab(末尾,Q2 ----------
console.log("\n[5] addStrategy 联动追加 tab");
const addScope = makeScope({
strategies: [{ id: 'grid-supermarket', name: '网格超市' }],
tabs: [
{ id: 'tab-all', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 0 },
{ id: 'tab-s', kind: 'strategy', refId: 'grid-supermarket', name: '网格超市', visible: true, order: 1 },
],
});
const added = await addStrategy(addScope, '长线持有');
assert(getStrategies(addScope).length === 2, '策略定义表新增');
const addTabs = getTabs(addScope);
assert(addTabs.length === 3, 'tabs 联动新增一条');
assert(addTabs[2].kind === 'strategy' && addTabs[2].refId === added.id, '新策略 tab 追加到末尾(Q2');
assert(addTabs[2].name === '长线持有', '新策略 tab name 正确');
assert(addTabs[2].visible === true, '新策略 tab 默认显示');
assert(addTabs[2].order === 2, 'order 连续');
// ---------- 6. removeStrategy 联动删除 tabD3 ----------
console.log("\n[6] removeStrategy 联动删除 tab");
const rmScope = makeScope({
// ---------- 5. 策略 tab 静态化(R-027 ----------
console.log("\n[5] 策略 tab 静态化(R-027");
const statScope = makeScope({
strategies: [
{ id: 'grid-supermarket', name: '网格超市' },
{ id: 'manual-t', name: '手动做T' },
{ id: 'long-term', name: '长线持有' }, // R-027 前可增:存量自建策略
],
tabs: [
{ id: 'tab-all', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 0 },
{ id: 'tab-g', kind: 'strategy', refId: 'grid-supermarket', name: '网格超市', visible: true, order: 1 },
{ id: 'tab-m', kind: 'strategy', refId: 'manual-t', name: '手动做T', visible: true, order: 2 },
{ id: 'tab-all-positions', kind: 'builtin', refKey: 'allPositions', name: '全部持仓', visible: true, order: 1 },
{ id: 'tab-watchlist', kind: 'builtin', refKey: 'watchlist', name: '关注列表', visible: true, order: 2 },
{ id: 'tab-strategy-long-term', kind: 'strategy', refId: 'long-term', name: '长线持有', visible: true, order: 0 }, // 存量自建策略 tab
],
});
await removeStrategy(rmScope, 'grid-supermarket');
const rmTabs = getTabs(rmScope);
assert(rmTabs.length === 2, '删除策略后 tabs 联动删除对应条目');
assert(!rmTabs.some((t) => t.refId === 'grid-supermarket'), 'grid-supermarket tab 条目已删除');
assert(rmTabs.some((t) => t.refId === 'manual-t'), 'manual-t tab 条目保留');
assert(getStrategies(rmScope).length === 1, '策略定义表删除');
// ---------- 7. renameStrategy 只改名(tabs 行 join 跟随,不影响定义) ----------
console.log("\n[7] renameStrategy");
const rnScope = makeScope({
strategies: [{ id: 'manual-t', name: '手动做T' }],
tabs: [
{ id: 'tab-m', kind: 'strategy', refId: 'manual-t', name: '手动做T', visible: true, order: 0 },
],
});
await renameStrategy(rnScope, 'manual-t', '手动T+');
assert(getStrategies(rnScope)[0].name === '手动T+', '策略定义名更新');
assert(getTabs(rnScope)[0].name === '手动T+', 'tabs 行 name join 跟随(Q4');
const strat = getStrategies(statScope);
assert(strat.length === 2, 'getStrategies 恒 2 项(自建策略被忽略)');
assert(!strat.some((s) => s.id === 'long-term'), '自建策略不出现在读取结果');
const statTabs = normalizeTabs(statScope);
assert(statTabs.length === 5, 'normalizeTabs 恒 5 条(3 内置 + 2 策略)');
assert(!statTabs.some((t) => t.refId === 'long-term'), '存量自建策略 tab 条目被丢弃');
assert(statTabs.filter((t) => t.kind === 'builtin').length === 3 && statTabs.filter((t) => t.kind === 'strategy').length === 2, '条目构成 = 3 内置 + 2 策略');
assert(!('addStrategy' in settingsMod), '退役导出 addStrategy 不存在');
assert(!('removeStrategy' in settingsMod), '退役导出 removeStrategy 不存在');
assert(!('renameStrategy' in settingsMod), '退役导出 renameStrategy 不存在');
assert(!('updateStrategies' in settingsMod), '退役导出 updateStrategies 不存在');
assert(!('appendStrategyTab' in settingsMod) && !('removeStrategyTab' in settingsMod), '退役导出 append/removeStrategyTab 不存在');
// ---------- 汇总 ----------
console.log("\n结果: " + pass + " 通过 / " + fail + " 失败");