迭代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
+13 -13
View File
@@ -1,5 +1,5 @@
/**
* R-017 / 迭代 15 回归 → R-018 / 迭代 16 语义修订:归属候选退役 7 天清仓窗口
* R-017 / 迭代 15 回归 → R-018 / 迭代 16 语义修订 → R-027 迭代 22:策略 id 改用内置(manual-t / grid-supermarket:归属候选退役 7 天清仓窗口
* 新语义(R-018 T2 老师拍板):候选回归「当前活动持仓 + 未关联」;正式候选为策略级 attribution-targets。
* 覆盖:当前活动持仓候选 / 清仓/作废行不进候选 / 多策略 / 空 code / targets 端点。
*/
@@ -20,25 +20,25 @@ const tmp = mkdtempSync(join(tmpdir(), 'odl-r017-'));
const storage = new DataStore({ dataDir: tmp });
const runtime = {
storage,
settings: { get: () => ({ strategies: [{ id: 's-t', name: '做T' }, { id: 's-g', name: '网格' }] }) },
settings: { get: () => ({}) }, // R-027:策略恒为内置两项(不再从 settings 读取)
};
try {
console.log('\n[1] 当前活动持仓候选(closed=false');
await storage.openHolding('s-t', '600719.SH', 1000);
await storage.openHolding('s-g', '600719.SH', 800); // 同码第二策略
await storage.openHolding('manual-t', '600719.SH', 1000);
await storage.openHolding('grid-supermarket', '600719.SH', 800); // 同码第二策略
let cands = await storage.getTradeAttributionCandidates('600719.SH');
assert(cands.length === 2, '同码两策略当前持仓都在候选');
assert(cands.every((c) => c.closed === false && c.closedAt === null), '当前持仓 closed=false / closedAt=null');
assert(cands[0].shares >= cands[1].shares, '当前持仓按 shares DESC');
console.log('\n[2] 清仓/作废行不进候选(R-018 退役 7 天窗口)');
const hc = await storage.openHolding('s-t', '000001.SZ', 500);
await storage.closeHolding('s-t', '000001.SZ'); // 刚清仓(R-017 时代会入选,R-018 起不再)
const hc = await storage.openHolding('manual-t', '000001.SZ', 500);
await storage.closeHolding('manual-t', '000001.SZ'); // 刚清仓(R-017 时代会入选,R-018 起不再)
cands = await storage.getTradeAttributionCandidates('000001.SZ');
assert(cands.length === 0, '刚清仓行不进候选(7 天窗口退役)');
const hv = await storage.openHolding('s-t', '600000.SH', 300);
storage.sqlite.voidHolding('s-t', '600000.SH');
const hv = await storage.openHolding('manual-t', '600000.SH', 300);
storage.sqlite.voidHolding('manual-t', '600000.SH');
cands = await storage.getTradeAttributionCandidates('600000.SH');
assert(cands.length === 0, '作废行不进候选');
@@ -47,18 +47,18 @@ try {
assert((await storage.getTradeAttributionCandidates('999999.SH')).length === 0, '无持仓 code → []');
console.log('\n[4] R-018 attribution-targets(策略级候选端点)');
const hc2 = await storage.openHolding('s-t', '000002.SZ', 500);
const hc2 = await storage.openHolding('manual-t', '000002.SZ', 500);
const tgt = await handleTrade('orders/attribution-targets', { code: '000002.SZ' }, runtime);
assert(Array.isArray(tgt), 'targets 端点返回数组');
assert(tgt.every((x) => x.strategyId && x.activityShares != null), 'targets 每项含 strategyId + activityShares');
const rowT = tgt.find((x) => x.strategyId === 's-t');
const rowT = tgt.find((x) => x.strategyId === 'manual-t');
assert(rowT && rowT.activityShares === 500 && rowT.hasActiveHolding === true, '有仓策略 activityShares=500 / hasActiveHolding=true');
const rowG = tgt.find((x) => x.strategyId === 's-g');
const rowG = tgt.find((x) => x.strategyId === 'grid-supermarket');
assert(rowG && rowG.activityShares === 0 && rowG.hasActiveHolding === false, '无仓策略 activityShares=0 / hasActiveHolding=false');
// 关闭的持仓不占 hasActiveHolding
await storage.closeHolding('s-t', '000002.SZ');
await storage.closeHolding('manual-t', '000002.SZ');
const tgt2 = await handleTrade('orders/attribution-targets', { code: '000002.SZ' }, runtime);
assert(tgt2.find((x) => x.strategyId === 's-t').hasActiveHolding === false, '清仓后该策略 hasActiveHolding=false');
assert(tgt2.find((x) => x.strategyId === 'manual-t').hasActiveHolding === false, '清仓后该策略 hasActiveHolding=false');
} finally {
storage.close();
try { rmSync(tmp, { recursive: true, force: true }); } catch { /* ignore */ }