Files
one_divine_lot/scripts/test-r017-candidates.mjs
T
kyugao ec02a791ee 迭代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 通过
2026-09-10 14:05:06 +08:00

69 lines
3.8 KiB
JavaScript
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-017 / 迭代 15 回归 → R-018 / 迭代 16 语义修订 → R-027 迭代 22:策略 id 改用内置(manual-t / grid-supermarket):归属候选退役 7 天清仓窗口
* 新语义(R-018 T2 老师拍板):候选回归「当前活动持仓 + 未关联」;正式候选为策略级 attribution-targets。
* 覆盖:当前活动持仓候选 / 清仓/作废行不进候选 / 多策略 / 空 code / targets 端点。
*/
import { DataStore } from '../src/storage/DataStore.js';
import { handleTrade } from '../src/api/trades.js';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
let pass = 0, fail = 0;
function assert(cond, msg) {
if (cond) { pass++; console.log(' ✅ ' + msg); }
else { fail++; console.log(' ❌ ' + msg); }
}
const DAY = 86400000;
const tmp = mkdtempSync(join(tmpdir(), 'odl-r017-'));
const storage = new DataStore({ dataDir: tmp });
const runtime = {
storage,
settings: { get: () => ({}) }, // R-027:策略恒为内置两项(不再从 settings 读取)
};
try {
console.log('\n[1] 当前活动持仓候选(closed=false');
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('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('manual-t', '600000.SH', 300);
storage.sqlite.voidHolding('manual-t', '600000.SH');
cands = await storage.getTradeAttributionCandidates('600000.SH');
assert(cands.length === 0, '作废行不进候选');
console.log('\n[3] 空 code / 无持仓 code');
assert((await storage.getTradeAttributionCandidates('')).length === 0, '空 code → []');
assert((await storage.getTradeAttributionCandidates('999999.SH')).length === 0, '无持仓 code → []');
console.log('\n[4] R-018 attribution-targets(策略级候选端点)');
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 === 'manual-t');
assert(rowT && rowT.activityShares === 500 && rowT.hasActiveHolding === true, '有仓策略 activityShares=500 / hasActiveHolding=true');
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('manual-t', '000002.SZ');
const tgt2 = await handleTrade('orders/attribution-targets', { code: '000002.SZ' }, runtime);
assert(tgt2.find((x) => x.strategyId === 'manual-t').hasActiveHolding === false, '清仓后该策略 hasActiveHolding=false');
} finally {
storage.close();
try { rmSync(tmp, { recursive: true, force: true }); } catch { /* ignore */ }
}
console.log('\n=== 结果: ' + pass + ' 通过 / ' + fail + ' 失败 ===');
process.exitCode = fail > 0 ? 1 : 0;