const normalizeSymbol = (symbol) => { if (typeof symbol !== "string") { return ""; } return symbol.trim().toUpperCase(); }; export const normalizeTickerSymbols = (symbols, previousTickers = []) => { if (!Array.isArray(symbols) || symbols.length === 0) { return previousTickers; } return symbols .map(normalizeSymbol) .filter(Boolean) .reduce((acc, symbol) => { const existing = acc.find((ticker) => ticker.symbol === symbol); if (existing) { return acc; } const prior = previousTickers.find((ticker) => ticker.symbol === symbol); acc.push( prior || { symbol, price: null, change: null } ); return acc; }, []); }; export const normalizeRuntimeWatchlistSymbols = (runtimeConfig, fallbackTickers = []) => { const runtimeSymbols = Array.isArray(runtimeConfig?.tickers) ? runtimeConfig.tickers.map(normalizeSymbol).filter(Boolean) : []; if (runtimeSymbols.length > 0) { return runtimeSymbols; } return fallbackTickers .map((ticker) => normalizeSymbol(ticker?.symbol)) .filter(Boolean); }; export const parseWatchlistInput = (value) => { if (typeof value !== "string") { return []; } return Array.from( new Set( value .split(/[\s,]+/) .map(normalizeSymbol) .filter(Boolean) ) ); }; export const buildRuntimeSummaryLabel = (runtimeConfig) => { if (!runtimeConfig) { return null; } const scheduleMode = String(runtimeConfig.schedule_mode || "daily"); const intervalMinutes = Number(runtimeConfig.interval_minutes || 60); const triggerTime = String(runtimeConfig.trigger_time || "now"); const maxCommCycles = Number(runtimeConfig.max_comm_cycles || 2); if (scheduleMode === "intraday") { return `调度 intraday / ${intervalMinutes}m / 讨论 ${maxCommCycles} 轮`; } if (triggerTime.toLowerCase() === "now") { return `调度 daily / 立即执行 / 讨论 ${maxCommCycles} 轮`; } return `调度 daily / ${triggerTime} ET / 讨论 ${maxCommCycles} 轮`; };