fix(HEL-104): 选股持续有效/入选历史改从 recent_results 按当前策略组装
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
+51
-24
@@ -1653,33 +1653,60 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
// 持续有效 / 入选历史:对齐电脑端 selectedScreenerArchiveRows,按当前策略过滤 active_signals/candidate_history 组装行
|
||||
// 持续有效 / 入选历史:生产 /api/screener/setup 只返回 recent_results / latest_results,
|
||||
// 从 recent_results 里筛出当前策略(meta.strategy_name 匹配,与 screenerRunForCurrentStrategy 同口径)的运行,逐候选组装。
|
||||
function screenerArchiveRows(payload, view) {
|
||||
if (!payload) return [];
|
||||
const source = view === "active" ? (payload.active_signals || []) : (payload.candidate_history || []);
|
||||
const strategy = screenerCurrentStrategy(payload);
|
||||
const strategyName = strategy ? strategy.name : "";
|
||||
const mode = screenerLibraryMode(screenerCurrentLibrary(payload));
|
||||
return source.filter(function (row) {
|
||||
if ((row.mode || "smart") !== mode) return false;
|
||||
return (row.hits || []).some(function (hit) { return hit.strategy_name === strategyName; });
|
||||
}).map(function (row) {
|
||||
let hits = (row.hits || []).filter(function (hit) { return hit.strategy_name === strategyName; });
|
||||
if (view === "active") hits = hits.filter(function (hit) { return hit.active; });
|
||||
const latestHit = hits.slice().sort(function (a, b) {
|
||||
return String(b.selection_date || "").localeCompare(String(a.selection_date || ""));
|
||||
})[0] || {};
|
||||
return {
|
||||
code: row.code,
|
||||
name: row.name,
|
||||
sector: row.sector,
|
||||
pct_chg: row.pct_chg,
|
||||
return_5d: row.return_5d,
|
||||
score_display: latestHit.score_display != null ? latestHit.score_display : row.score_display,
|
||||
selection_date: displayCompactDate(latestHit.selection_date || row.selection_date || ""),
|
||||
status: view === "active" ? "持续有效" : "已入选"
|
||||
};
|
||||
if (!strategyName) return [];
|
||||
const rows = [];
|
||||
(payload.recent_results || []).forEach(function (run) {
|
||||
if (!run || !run.meta || run.meta.strategy_name !== strategyName) return;
|
||||
const meta = run.meta;
|
||||
const active = screenerSignalActive(run, payload);
|
||||
if (view === "active" && !active) return;
|
||||
(run.candidates || []).forEach(function (c) {
|
||||
const code = String(c.code || "");
|
||||
if (!code) return;
|
||||
rows.push({
|
||||
code: code,
|
||||
name: c.name || "",
|
||||
sector: c.sector || "",
|
||||
pct_chg: c.pct_chg,
|
||||
return_5d: c.return_5d,
|
||||
volume_ratio_5d: c.volume_ratio_5d,
|
||||
sector_strength: c.sector_strength,
|
||||
historical_probability: c.historical_probability,
|
||||
score_display: c.score_display,
|
||||
selection_date: displayCompactDate(String(meta.trade_date || ""))
|
||||
});
|
||||
});
|
||||
});
|
||||
rows.sort(function (a, b) {
|
||||
return String(b.selection_date || "").localeCompare(String(a.selection_date || ""));
|
||||
});
|
||||
return rows;
|
||||
}
|
||||
|
||||
// 信号是否仍在有效期:阶段/精选策略按「阶段不变即有效」(meta.regime == 当前阶段);
|
||||
// 量化策略按策略频率对应的交易日数,以 meta.trade_date 与数据日比较(无交易日历时按自然日近似)。
|
||||
function screenerSignalActive(run, payload) {
|
||||
const meta = (run && run.meta) || {};
|
||||
const mode = String(meta.mode || "smart");
|
||||
if (mode === "smart" || mode === "curated") {
|
||||
const regime = payload && payload.regime ? String(payload.regime.id || "") : "";
|
||||
return regime ? String(meta.regime || "") === regime : true;
|
||||
}
|
||||
const freq = String((((run && run.formula) || {}).meta || {}).frequency || "每日");
|
||||
const days = { "每周": 5, "双周": 10, "月度": 20, "事件驱动": 5 }[freq] || 1;
|
||||
const selected = String(meta.trade_date || "").replaceAll("-", "");
|
||||
const asOf = String((payload && payload.trade_date) || "").replaceAll("-", "");
|
||||
if (!selected || !asOf || selected === asOf) return true;
|
||||
const start = parseLocalDate(displayCompactDate(selected));
|
||||
const end = parseLocalDate(displayCompactDate(asOf));
|
||||
const diff = Math.round((end.getTime() - start.getTime()) / 86400000);
|
||||
return diff >= 0 && diff < days;
|
||||
}
|
||||
|
||||
function screenerViewKey(payload, view) {
|
||||
@@ -1861,12 +1888,12 @@
|
||||
}
|
||||
|
||||
function screenerEmptyText(payload) {
|
||||
if (state.screener.view === "active") return "该策略暂无持续有效信号";
|
||||
if (state.screener.view === "history") return "该策略暂无入选历史";
|
||||
const status = screenerStrategyStatusText(payload);
|
||||
if (status === "数据不足") return "该策略数据不足,暂无法出候选";
|
||||
if (status === "等待盘后") return "该策略等待盘后更新";
|
||||
if (status === "选择策略") return "请先选择一个策略查看结果";
|
||||
if (state.screener.view === "active") return "该策略暂无持续有效信号";
|
||||
if (state.screener.view === "history") return "该策略暂无入选历史";
|
||||
return "该策略当日暂无信号";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user