/** * 因子目录在界面上的共用小工具(参数化之后才需要)。 * * 为什么单独一个文件:因子的「名字」在参数化之后变成了带参数的引擎键 * (`momentum(window=90,direction=lower_is_better)`)。它**不能**直接当界面文字用 —— * 太长、挤爆布局、也没人想读 `direction=higher_is_better`。而「哪些因子能被选」的规则 * (停用的不出现、算不出来的不出现)必须各处一致,否则「停用」在某个页面就成了假开关。 */ import type { FactorMeta } from "@/lib/types"; /** * 可以被**选中/引用**的因子。 * * - `enabled === false`:在目录里停了用 —— 只是不出现在选择列表里,既有策略/归档仍按名字解析。 * - `resolvable === false`:引擎算不出来(历史手工登记行),选了也只会报错,不该摆给人点。 * * 注意:这**不是**「能不能解析」的判断(那是引擎的事),只是界面候选集的过滤。 */ export function pickableFactors(list: FactorMeta[]): FactorMeta[] { return list.filter((f) => f.enabled !== false && f.resolvable !== false); } /** 界面显示名:优先后端给的中文名(含参数),没有才退回引擎键。 */ export function factorLabel(f: FactorMeta | undefined, fallback = ""): string { return f?.label || f?.name || fallback; } /** * 参数化因子的「短键」:只用于展示。 * * 参数化因子的名字把参数写全了,直接放进下拉会很长;而 direction 在别处(提示里)已经 * 说成「越高越好 / 越低越好」,展示层去掉它不丢信息。**下拉的 value 始终是完整键** * (引擎身份),只有显示文字被缩短。非参数化名(`momentum_60`)原样返回。 */ export function shortFactorKey(name: string): string { const m = /^([A-Za-z_][A-Za-z0-9_]*)\(([^()]*)\)$/.exec(name); if (!m) return name; const kept = m[2] .split(",") .filter((part) => !part.trim().startsWith("direction=")) .join(","); return kept ? `${m[1]}(${kept})` : m[1]; } /** 下拉/列表里的一行文字:中文名(含参数)· 引擎键。藏着名字就等于藏着身份。 */ export function factorOptionLabel(f: FactorMeta): string { const d = (f.label || f.description).trim(); const head = d.length > 30 ? `${d.slice(0, 30)}…` : d || f.name; return `${head} · ${shortFactorKey(f.name)}`; }