迭代14: 策略tab历史持仓展示(R-016)+ 今日已清仓默认层
- R-016 Q1-Q7: 两个策略 tab title 旁「历史持仓」开关 + 清仓范围筛选(近一周默认/1月/3月/半年/1年) - SqliteStore.getHoldingsHistory(strategy_id + closed_at 非空 + sinceMs,closed_at DESC,只读) - DataStore 门面透传 + strategy-holdings/history 端点(name 兜底 + 参数校验) - 前端: 开关/范围下拉/历史行灰显+「已清仓」徽标/展开复用 R-010(rowKey 唯一化) - R-016 二轮补充(Q8-Q9 老师拍板): 今日已清仓默认层 - range=today = 本地自然日 00:00 起(特判零点,不落回溯毫秒档) - 当天已清仓的持仓默认恒显示、不受「历史持仓」开关控制(开关只控今天之前) - rowsToRender 分层合成 + holdingId 去重;标题计数不含已清仓行;零写路径变更 - 回归: test-r016-history 24/24 + 存量回归全绿
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
* strategies/remove → 删除策略 { strategyId },联动清份额
|
||||
* strategies/move → 排序 { strategyId, dir }
|
||||
* strategy-positions → 某策略持仓 { strategyId }
|
||||
* strategy-holdings/history → 某策略已清仓历史持仓 { strategyId, range }(R-016)
|
||||
* unallocated → 未分配持仓
|
||||
* summary → 分仓摘要
|
||||
* add-shares → 添加份额 { code, strategyId, shares }
|
||||
|
||||
+53
-2
@@ -4,6 +4,7 @@
|
||||
* 端点:
|
||||
* strategies / strategies/update / strategies/add / strategies/remove
|
||||
* strategy-positions / unallocated / summary
|
||||
* strategy-holdings/history(R-016 迭代 14:已清仓历史持仓,范围筛选)
|
||||
* add-shares / move-all-shares / remove-shares
|
||||
* tabs / tabs/update
|
||||
*/
|
||||
@@ -14,6 +15,24 @@ import {
|
||||
getStrategyColumns, updateStrategyColumns,
|
||||
} from '../settings.js';
|
||||
|
||||
/** R-016 范围档 → 回溯毫秒(自然日近似,展示用途足够;技术约束-019)。
|
||||
* range='today' 不走回溯毫秒——二轮补充(2026-09-07 老师定):本地自然日 00:00 起清仓的持仓
|
||||
* 作为「今日已清仓」默认层(开关「历史持仓」不控制它,当天清仓默认显示),sinceMs 在 handler 内特判。 */
|
||||
const HISTORY_RANGE_MS = {
|
||||
week: 7 * 86400000,
|
||||
month: 30 * 86400000,
|
||||
quarter: 90 * 86400000,
|
||||
halfYear: 182 * 86400000,
|
||||
year: 365 * 86400000,
|
||||
};
|
||||
|
||||
/** 本地自然日零点毫秒(range='today' 用:今天 00:00 起 = 当日清仓的持仓) */
|
||||
function localDayStartMs() {
|
||||
const d = new Date();
|
||||
d.setHours(0, 0, 0, 0);
|
||||
return d.getTime();
|
||||
}
|
||||
|
||||
/** 策略/份额/tabs 端点方法表 */
|
||||
export const STRATEGY_METHODS = new Set([
|
||||
'strategies',
|
||||
@@ -31,15 +50,16 @@ export const STRATEGY_METHODS = new Set([
|
||||
'holdings/values-update', // R-013:写某持仓行的自定义字段值
|
||||
'strategy-columns', // 迭代11列显隐:读某策略列配置(归一化)
|
||||
'strategy-columns/update', // 迭代11列显隐:写某策略列配置
|
||||
'strategy-holdings/history', // R-016 迭代 14:某策略已清仓历史持仓(范围筛选,只读)
|
||||
]);
|
||||
|
||||
/**
|
||||
* 处理策略/份额/tabs 端点
|
||||
* @param {string} method
|
||||
* @param {object} args
|
||||
* @param {object} runtime { manager, settings }
|
||||
* @param {object} runtime { manager, settings, storage }
|
||||
*/
|
||||
export async function handleStrategy(method, args, { manager, settings }) {
|
||||
export async function handleStrategy(method, args, { manager, settings, storage }) {
|
||||
switch (method) {
|
||||
case 'strategies':
|
||||
return getStrategies(settings);
|
||||
@@ -78,6 +98,37 @@ export async function handleStrategy(method, args, { manager, settings }) {
|
||||
case 'strategy-columns/update':
|
||||
await updateStrategyColumns(settings, args.strategyId, args.columns);
|
||||
return getStrategyColumns(settings, args.strategyId);
|
||||
case 'strategy-holdings/history': {
|
||||
// R-016 迭代 14:某策略已清仓历史持仓(当前持仓快照路径零改动,技术约束-019)
|
||||
if (!args.strategyId) {
|
||||
throw Object.assign(new Error('缺少 strategyId 参数'), { code: 'bad-request' });
|
||||
}
|
||||
const range = args.range ?? 'week';
|
||||
// R-016 二轮补充(2026-09-07 老师定):range='today' = 本地自然日 00:00 起清仓的持仓
|
||||
// (前端默认层——当天清仓默认显示,与「历史持仓」开关解耦);其余 5 档维持回溯毫秒语义
|
||||
let sinceMs;
|
||||
if (range === 'today') {
|
||||
sinceMs = localDayStartMs();
|
||||
} else {
|
||||
const ms = HISTORY_RANGE_MS[range];
|
||||
if (!ms) {
|
||||
throw Object.assign(new Error('range 非法: ' + range + '(today|week|month|quarter|halfYear|year)'), { code: 'bad-request' });
|
||||
}
|
||||
sinceMs = Date.now() - ms;
|
||||
}
|
||||
const rows = await storage.getHoldingsHistory(args.strategyId, { sinceMs });
|
||||
// name 兜底:该 holding 最近一笔关联委托的 name(前端免二次请求)
|
||||
const ids = rows.map((r) => r.holdingId);
|
||||
const nameMap = new Map();
|
||||
if (ids.length > 0) {
|
||||
const ph = ids.map(() => '?').join(',');
|
||||
const orows = storage.sqlite.db.prepare(
|
||||
"SELECT holding_id, name FROM trade_orders WHERE holding_id IN (" + ph + ") AND name != '' ORDER BY insert_ts DESC"
|
||||
).all(...ids);
|
||||
for (const o of orows) if (!nameMap.has(o.holding_id)) nameMap.set(o.holding_id, o.name);
|
||||
}
|
||||
return rows.map((r) => ({ ...r, name: nameMap.get(r.holdingId) ?? null }));
|
||||
}
|
||||
default:
|
||||
throw Object.assign(new Error('unknown strategy method: ' + method), { code: 'not-found' });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user