Files
vf_react/src/views/StockOverview/components/HotspotOverview/utils/chartHelpers.js
2025-12-11 07:32:30 +08:00

422 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 图表辅助函数
* 用于处理异动标注等图表相关逻辑
*/
/**
* 异动类型配置 - 科技感配色方案
*/
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,
};
};