/** * 图表辅助函数 * 用于处理异动标注等图表相关逻辑 */ /** * 异动类型配置 - 科技感配色方案 */ export const ALERT_TYPE_CONFIG = { surge_up: { label: '急涨', color: '#ff4d4f', gradient: ['#ff4d4f', '#ff7a45'], icon: 'TrendingUp', bgAlpha: 0.15, description: '概念板块出现快速上涨异动', }, surge: { label: '异动', color: '#ff7a45', gradient: ['#ff7a45', '#ffa940'], icon: 'Zap', bgAlpha: 0.12, description: '概念板块出现明显异动信号', }, surge_down: { label: '急跌', color: '#52c41a', gradient: ['#52c41a', '#73d13d'], icon: 'TrendingDown', bgAlpha: 0.15, description: '概念板块出现快速下跌异动', }, volume_surge_up: { label: '放量急涨', color: '#eb2f96', gradient: ['#eb2f96', '#f759ab'], icon: 'Activity', bgAlpha: 0.15, description: '成交量放大伴随价格急涨,资金关注度高', }, shrink_surge_up: { label: '缩量急涨', color: '#722ed1', gradient: ['#722ed1', '#9254de'], icon: 'Rocket', bgAlpha: 0.15, description: '成交量萎缩但价格急涨,筹码锁定良好', }, volume_oscillation: { label: '放量震荡', color: '#13c2c2', gradient: ['#13c2c2', '#36cfc9'], icon: 'Waves', bgAlpha: 0.12, description: '成交量放大但价格震荡,多空分歧加大', }, limit_up: { label: '涨停潮', color: '#fa541c', gradient: ['#fa541c', '#ff7a45'], icon: 'Flame', bgAlpha: 0.15, description: '板块内多只股票涨停,热度极高', }, rank_jump: { label: '排名跃升', color: '#1890ff', gradient: ['#1890ff', '#40a9ff'], icon: 'ArrowUpCircle', bgAlpha: 0.12, description: '概念板块排名快速上升,关注度提升', }, volume_spike: { label: '放量异动', color: '#faad14', gradient: ['#faad14', '#ffc53d'], icon: 'BarChart3', bgAlpha: 0.12, description: '成交量出现突发性放大', }, }; /** * 指标配置 - 包含详细提示说明 */ export const METRIC_CONFIG = { final_score: { label: '综合评分', unit: '分', tooltip: '综合规则评分和机器学习评分的最终得分,分数越高表示异动信号越强。60分以上值得关注,80分以上为强信号。', format: (v) => Math.round(v), getColor: (v) => { if (v >= 80) return '#ff4d4f'; if (v >= 60) return '#fa8c16'; if (v >= 40) return '#faad14'; return '#8c8c8c'; }, }, rule_score: { label: '规则评分', unit: '分', tooltip: '基于预设规则计算的评分,包括涨跌幅、成交量、涨停数等多维度指标。', format: (v) => Math.round(v), }, ml_score: { label: 'AI评分', unit: '分', tooltip: '机器学习模型预测的评分,基于历史数据训练,预测该异动后续表现概率。', format: (v) => Math.round(v), }, confirm_ratio: { label: '确认率', unit: '%', tooltip: '异动信号的确认程度。100%表示信号完全确认,数值越高表示异动越稳定、越可靠。低于60%的信号可能是噪音。', format: (v) => Math.round(v * 100), getColor: (v) => { if (v >= 0.8) return '#52c41a'; if (v >= 0.6) return '#faad14'; return '#ff4d4f'; }, }, alpha: { label: '超额收益', unit: '%', tooltip: '概念板块相对于大盘的超额涨跌幅(Alpha)。正值表示跑赢大盘,负值表示跑输大盘。该指标反映板块的相对强弱。', format: (v) => v?.toFixed(2), getColor: (v) => v >= 0 ? '#ff4d4f' : '#52c41a', showSign: true, }, alpha_zscore: { label: 'Alpha强度', unit: 'σ', tooltip: 'Alpha的Z-Score标准化值,衡量超额收益的统计显著性。|值|>2表示异常强,|值|>1.5表示较强。正值表示异常上涨,负值表示异常下跌。', format: (v) => v?.toFixed(2), getColor: (v) => v >= 0 ? '#ff4d4f' : '#52c41a', showSign: true, }, amt_zscore: { label: '成交额强度', unit: 'σ', tooltip: '成交额的Z-Score标准化值,衡量当前成交额相对于历史的异常程度。>2表示成交额异常放大,资金活跃度高。', format: (v) => v?.toFixed(2), getColor: (v) => { if (v >= 2) return '#eb2f96'; if (v >= 1) return '#faad14'; return '#8c8c8c'; }, }, rank_zscore: { label: '排名变化强度', unit: 'σ', tooltip: '板块排名变化的Z-Score值。正值表示排名上升速度异常,>2表示排名跃升显著。', format: (v) => v?.toFixed(2), showSign: true, }, momentum_3m: { label: '3分钟动量', unit: '', tooltip: '过去3分钟的价格动量指标,反映短期趋势强度。正值表示上涨动量,负值表示下跌动量。', format: (v) => v?.toFixed(3), getColor: (v) => v >= 0 ? '#ff4d4f' : '#52c41a', showSign: true, }, momentum_5m: { label: '5分钟动量', unit: '', tooltip: '过去5分钟的价格动量指标,比3分钟动量更稳定,过滤掉更多噪音。', format: (v) => v?.toFixed(3), getColor: (v) => v >= 0 ? '#ff4d4f' : '#52c41a', showSign: true, }, limit_up_ratio: { label: '涨停占比', unit: '%', tooltip: '板块内涨停股票数量占总股票数的比例。>10%表示板块热度高,>20%表示涨停潮。', format: (v) => Math.round(v * 100), getColor: (v) => { if (v >= 0.2) return '#ff4d4f'; if (v >= 0.1) return '#fa8c16'; if (v >= 0.05) return '#faad14'; return '#8c8c8c'; }, }, }; /** * 触发规则配置 */ export const TRIGGERED_RULES_CONFIG = { alpha_moderate: { label: 'Alpha中等', color: '#ff7a45', description: '超额收益达到中等水平' }, alpha_strong: { label: 'Alpha强', color: '#ff4d4f', description: '超额收益达到强势水平' }, amt_moderate: { label: '成交额中等', color: '#faad14', description: '成交额异常放大中等' }, amt_strong: { label: '成交额强', color: '#fa8c16', description: '成交额异常放大明显' }, limit_up_moderate: { label: '涨停中等', color: '#eb2f96', description: '涨停股票数量适中' }, limit_up_extreme: { label: '涨停极端', color: '#ff4d4f', description: '涨停股票数量很多' }, momentum_3m_moderate: { label: '3分钟动量', color: '#1890ff', description: '短期动量信号触发' }, momentum_3m_strong: { label: '3分钟强动量', color: '#096dd9', description: '短期强动量信号' }, combo_alpha_amt: { label: 'Alpha+成交额', color: '#722ed1', description: '超额收益和成交额双重确认' }, combo_alpha_limitup: { label: 'Alpha+涨停', color: '#eb2f96', description: '超额收益和涨停双重确认' }, early_session: { label: '早盘信号', color: '#13c2c2', description: '开盘30分钟内的异动' }, 'decay:accelerating': { label: '加速中', color: '#52c41a', description: '异动正在加速' }, 'decay:stable': { label: '稳定', color: '#1890ff', description: '异动保持稳定' }, 'decay:fading': { label: '衰减中', color: '#8c8c8c', description: '异动正在衰减' }, }; /** * 获取异动标注的配色和符号 * @param {string} alertType - 异动类型 * @param {number} importanceScore - 重要性得分 * @returns {Object} { color, symbol, symbolSize } */ export const getAlertStyle = (alertType, importanceScore = 0.5) => { const config = ALERT_TYPE_CONFIG[alertType] || ALERT_TYPE_CONFIG.surge; const baseSize = 30; const sizeBonus = Math.min(importanceScore * 20, 15); let symbol = 'pin'; switch (alertType) { case 'surge_up': case 'surge': case 'volume_surge_up': case 'shrink_surge_up': symbol = 'triangle'; break; case 'surge_down': symbol = 'path://M0,0 L10,0 L5,10 Z'; break; case 'limit_up': symbol = 'diamond'; break; case 'rank_jump': symbol = 'circle'; break; case 'volume_spike': case 'volume_oscillation': symbol = 'rect'; break; default: symbol = 'pin'; } return { color: config.color, gradient: config.gradient, symbol, symbolSize: baseSize + sizeBonus, }; }; /** * 获取异动类型的显示标签 * @param {string} alertType - 异动类型 * @returns {string} 显示标签 */ export const getAlertTypeLabel = (alertType) => { return ALERT_TYPE_CONFIG[alertType]?.label || alertType || '异动'; }; /** * 获取异动类型的详细描述 * @param {string} alertType - 异动类型 * @returns {string} 描述 */ export const getAlertTypeDescription = (alertType) => { return ALERT_TYPE_CONFIG[alertType]?.description || '概念板块出现异动信号'; }; /** * 获取异动类型的配色 * @param {string} alertType - 异动类型 * @returns {Object} { color, gradient, bgAlpha } */ export const getAlertTypeColor = (alertType) => { const config = ALERT_TYPE_CONFIG[alertType] || ALERT_TYPE_CONFIG.surge; return { color: config.color, gradient: config.gradient, bgAlpha: config.bgAlpha, }; }; /** * 生成图表标注点数据 * @param {Array} alerts - 异动数据数组 * @param {Array} times - 时间数组 * @param {Array} prices - 价格数组 * @param {number} priceMax - 最高价格(用于无法匹配时间时的默认位置) * @param {number} maxCount - 最大显示数量 * @returns {Array} ECharts markPoint data */ export const getAlertMarkPoints = (alerts, times, prices, priceMax, maxCount = 15) => { if (!alerts || alerts.length === 0) return []; // 按重要性排序,限制显示数量 const sortedAlerts = [...alerts] .sort((a, b) => (b.final_score || b.importance_score || 0) - (a.final_score || a.importance_score || 0)) .slice(0, maxCount); return sortedAlerts.map((alert) => { // 找到对应时间的价格 const timeIndex = times.indexOf(alert.time); const price = timeIndex >= 0 ? prices[timeIndex] : (alert.index_price || priceMax); const { color, gradient, symbol, symbolSize } = getAlertStyle( alert.alert_type, alert.final_score / 100 || alert.importance_score || 0.5 ); // 格式化标签 let label = alert.concept_name || ''; if (label.length > 6) { label = label.substring(0, 5) + '...'; } // 添加涨停数量(如果有) if (alert.limit_up_count > 0) { label += `\n涨停: ${alert.limit_up_count}`; } const isDown = alert.alert_type === 'surge_down'; return { name: alert.concept_name, coord: [alert.time, price], value: label, symbol, symbolSize, itemStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: gradient[0] }, { offset: 1, color: gradient[1] }, ], }, borderColor: 'rgba(255,255,255,0.8)', borderWidth: 2, shadowBlur: 8, shadowColor: `${color}66`, }, label: { show: true, position: isDown ? 'bottom' : 'top', formatter: '{b}', fontSize: 10, fontWeight: 500, color: isDown ? '#52c41a' : '#ff4d4f', backgroundColor: 'rgba(255,255,255,0.95)', padding: [3, 6], borderRadius: 4, borderColor: color, borderWidth: 1, shadowBlur: 4, shadowColor: 'rgba(0,0,0,0.1)', }, alertData: alert, // 存储原始数据 }; }); }; /** * 格式化分数显示 * @param {number} score - 分数 * @returns {string} 格式化后的分数 */ export const formatScore = (score) => { if (score === null || score === undefined) return '-'; return Math.round(score).toString(); }; /** * 获取分数对应的颜色 * @param {number} score - 分数 (0-100) * @returns {string} 颜色代码 */ export const getScoreColor = (score) => { const s = score || 0; if (s >= 80) return '#ff4d4f'; if (s >= 60) return '#fa8c16'; if (s >= 40) return '#faad14'; return '#8c8c8c'; }; /** * 获取分数等级标签 * @param {number} score - 分数 (0-100) * @returns {Object} { label, color } */ export const getScoreLevel = (score) => { const s = score || 0; if (s >= 80) return { label: '强信号', color: '#ff4d4f' }; if (s >= 60) return { label: '中等', color: '#fa8c16' }; if (s >= 40) return { label: '一般', color: '#faad14' }; return { label: '弱信号', color: '#8c8c8c' }; }; /** * 格式化指标值 * @param {string} metricKey - 指标键名 * @param {number} value - 值 * @returns {Object} { formatted, color, showSign } */ export const formatMetric = (metricKey, value) => { const config = METRIC_CONFIG[metricKey]; if (!config || value === null || value === undefined) { return { formatted: '-', color: '#8c8c8c', showSign: false }; } const formatted = config.format(value); const color = config.getColor ? config.getColor(value) : '#8c8c8c'; return { formatted: config.showSign && value > 0 ? `+${formatted}` : formatted, color, unit: config.unit, label: config.label, tooltip: config.tooltip, }; };