更新Company页面的UI为FUI风格
This commit is contained in:
@@ -21,8 +21,8 @@ import {
|
||||
MenuDivider,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react';
|
||||
import { RepeatIcon, InfoIcon, ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import { BarChart2, Clock, TrendingUp, Calendar, LineChart, Activity } from 'lucide-react';
|
||||
import { RepeatIcon, InfoIcon, ChevronDownIcon, ViewIcon, ViewOffIcon } from '@chakra-ui/icons';
|
||||
import { BarChart2, Clock, TrendingUp, Calendar, LineChart, Activity, Pencil } from 'lucide-react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
|
||||
import { darkGoldTheme, PERIOD_OPTIONS } from '../../../constants';
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
getMinuteKLineDarkGoldOption,
|
||||
type IndicatorType,
|
||||
type MainIndicatorType,
|
||||
type DrawingType,
|
||||
} from '../../../utils/chartOptions';
|
||||
import type { KLineModuleProps } from '../../../types';
|
||||
|
||||
@@ -57,6 +58,9 @@ const SUB_INDICATOR_OPTIONS: { value: IndicatorType; label: string; description:
|
||||
{ value: 'MACD', label: 'MACD', description: '平滑异同移动平均线' },
|
||||
{ value: 'KDJ', label: 'KDJ', description: '随机指标' },
|
||||
{ value: 'RSI', label: 'RSI', description: '相对强弱指标' },
|
||||
{ value: 'WR', label: 'WR', description: '威廉指标(超买超卖)' },
|
||||
{ value: 'CCI', label: 'CCI', description: '商品通道指标' },
|
||||
{ value: 'BIAS', label: 'BIAS', description: '乖离率' },
|
||||
{ value: 'VOL', label: '仅成交量', description: '不显示副图指标' },
|
||||
];
|
||||
|
||||
@@ -67,6 +71,14 @@ const MAIN_INDICATOR_OPTIONS: { value: MainIndicatorType; label: string; descrip
|
||||
{ value: 'NONE', label: '无', description: '不显示主图指标' },
|
||||
];
|
||||
|
||||
// 绘图工具选项
|
||||
const DRAWING_OPTIONS: { value: DrawingType; label: string; description: string }[] = [
|
||||
{ value: 'NONE', label: '无', description: '不显示绘图工具' },
|
||||
{ value: 'SUPPORT_RESISTANCE', label: '支撑/阻力', description: '自动识别支撑位和阻力位' },
|
||||
{ value: 'TREND_LINE', label: '趋势线', description: '基于线性回归的趋势线' },
|
||||
{ value: 'ALL', label: '全部显示', description: '显示所有参考线' },
|
||||
];
|
||||
|
||||
const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
theme,
|
||||
tradeData,
|
||||
@@ -81,6 +93,8 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
const [mode, setMode] = useState<ChartMode>('daily');
|
||||
const [subIndicator, setSubIndicator] = useState<IndicatorType>('MACD');
|
||||
const [mainIndicator, setMainIndicator] = useState<MainIndicatorType>('MA');
|
||||
const [showAnalysis, setShowAnalysis] = useState<boolean>(true);
|
||||
const [drawingType, setDrawingType] = useState<DrawingType>('NONE');
|
||||
const hasMinuteData = minuteData && minuteData.data && minuteData.data.length > 0;
|
||||
|
||||
// 切换到分时模式时自动加载数据
|
||||
@@ -188,6 +202,20 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
</HStack>
|
||||
)}
|
||||
|
||||
{/* 隐藏/显示涨幅分析 */}
|
||||
<Tooltip label={showAnalysis ? '隐藏涨幅分析标记' : '显示涨幅分析标记'} placement="top" hasArrow>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
leftIcon={showAnalysis ? <ViewOffIcon /> : <ViewIcon />}
|
||||
onClick={() => setShowAnalysis(!showAnalysis)}
|
||||
{...(showAnalysis ? inactiveButtonStyle : activeButtonStyle)}
|
||||
minW="90px"
|
||||
>
|
||||
{showAnalysis ? '隐藏分析' : '显示分析'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
{/* 主图指标选择 */}
|
||||
<Menu>
|
||||
<Tooltip label="主图指标" placement="top" hasArrow>
|
||||
@@ -268,6 +296,47 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
{/* 绘图工具选择 */}
|
||||
<Menu>
|
||||
<Tooltip label="绘图工具" placement="top" hasArrow>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
leftIcon={<Pencil size={14} />}
|
||||
{...(drawingType !== 'NONE' ? activeButtonStyle : inactiveButtonStyle)}
|
||||
minW="90px"
|
||||
>
|
||||
{DRAWING_OPTIONS.find(o => o.value === drawingType)?.label || '绘图'}
|
||||
</MenuButton>
|
||||
</Tooltip>
|
||||
<MenuList
|
||||
bg="#1a1a2e"
|
||||
borderColor={darkGoldTheme.border}
|
||||
boxShadow="0 4px 20px rgba(0,0,0,0.5)"
|
||||
>
|
||||
{DRAWING_OPTIONS.map((option) => (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
onClick={() => setDrawingType(option.value)}
|
||||
bg={drawingType === option.value ? 'rgba(212, 175, 55, 0.2)' : 'transparent'}
|
||||
color={drawingType === option.value ? darkGoldTheme.gold : darkGoldTheme.textPrimary}
|
||||
_hover={{ bg: 'rgba(212, 175, 55, 0.1)' }}
|
||||
>
|
||||
<VStack align="start" spacing={0}>
|
||||
<Text fontSize="sm" fontWeight={drawingType === option.value ? 'bold' : 'normal'}>
|
||||
{option.label}
|
||||
</Text>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
{option.description}
|
||||
</Text>
|
||||
</VStack>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -314,7 +383,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
tradeData.length > 0 ? (
|
||||
<Box h="650px">
|
||||
<ReactECharts
|
||||
option={getKLineDarkGoldOption(tradeData, analysisMap, subIndicator, mainIndicator)}
|
||||
option={getKLineDarkGoldOption(tradeData, analysisMap, subIndicator, mainIndicator, showAnalysis, drawingType)}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
theme="dark"
|
||||
onEvents={{ click: onChartClick }}
|
||||
|
||||
@@ -250,6 +250,133 @@ export const calculateBOLL = (
|
||||
return { upper, middle, lower };
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算WR (威廉指标)
|
||||
* @param highPrices 最高价数组
|
||||
* @param lowPrices 最低价数组
|
||||
* @param closePrices 收盘价数组
|
||||
* @param period 周期 (默认14)
|
||||
*/
|
||||
export const calculateWR = (
|
||||
highPrices: number[],
|
||||
lowPrices: number[],
|
||||
closePrices: number[],
|
||||
period = 14
|
||||
): (number | null)[] => {
|
||||
const result: (number | null)[] = [];
|
||||
|
||||
for (let i = 0; i < closePrices.length; i++) {
|
||||
if (i < period - 1) {
|
||||
result.push(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
let highestHigh = -Infinity;
|
||||
let lowestLow = Infinity;
|
||||
for (let j = 0; j < period; j++) {
|
||||
highestHigh = Math.max(highestHigh, highPrices[i - j]);
|
||||
lowestLow = Math.min(lowestLow, lowPrices[i - j]);
|
||||
}
|
||||
|
||||
if (highestHigh === lowestLow) {
|
||||
result.push(0);
|
||||
} else {
|
||||
// WR = (最高价 - 收盘价) / (最高价 - 最低价) * -100
|
||||
result.push(((highestHigh - closePrices[i]) / (highestHigh - lowestLow)) * -100);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算CCI (商品通道指标)
|
||||
* @param highPrices 最高价数组
|
||||
* @param lowPrices 最低价数组
|
||||
* @param closePrices 收盘价数组
|
||||
* @param period 周期 (默认14)
|
||||
*/
|
||||
export const calculateCCI = (
|
||||
highPrices: number[],
|
||||
lowPrices: number[],
|
||||
closePrices: number[],
|
||||
period = 14
|
||||
): (number | null)[] => {
|
||||
const result: (number | null)[] = [];
|
||||
|
||||
// 计算典型价格 (TP = (High + Low + Close) / 3)
|
||||
const tp = closePrices.map((close, i) => (highPrices[i] + lowPrices[i] + close) / 3);
|
||||
|
||||
for (let i = 0; i < closePrices.length; i++) {
|
||||
if (i < period - 1) {
|
||||
result.push(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 计算TP的移动平均
|
||||
let tpSum = 0;
|
||||
for (let j = 0; j < period; j++) {
|
||||
tpSum += tp[i - j];
|
||||
}
|
||||
const tpMA = tpSum / period;
|
||||
|
||||
// 计算平均绝对偏差
|
||||
let madSum = 0;
|
||||
for (let j = 0; j < period; j++) {
|
||||
madSum += Math.abs(tp[i - j] - tpMA);
|
||||
}
|
||||
const mad = madSum / period;
|
||||
|
||||
// CCI = (TP - MA) / (0.015 * MAD)
|
||||
if (mad === 0) {
|
||||
result.push(0);
|
||||
} else {
|
||||
result.push((tp[i] - tpMA) / (0.015 * mad));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算BIAS (乖离率)
|
||||
* @param closePrices 收盘价数组
|
||||
* @param period 周期 (默认6)
|
||||
*/
|
||||
export const calculateBIAS = (closePrices: number[], period = 6): (number | null)[] => {
|
||||
const ma = calculateMA(closePrices, period);
|
||||
const result: (number | null)[] = [];
|
||||
|
||||
for (let i = 0; i < closePrices.length; i++) {
|
||||
if (ma[i] === null || ma[i] === 0) {
|
||||
result.push(null);
|
||||
} else {
|
||||
// BIAS = (收盘价 - MA) / MA * 100
|
||||
result.push(((closePrices[i] - ma[i]!) / ma[i]!) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化数字(最多2位小数,去除无意义的尾数)
|
||||
*/
|
||||
export const formatPrice = (value: number | null | undefined): string => {
|
||||
if (value === null || value === undefined || isNaN(value)) return '--';
|
||||
// 使用 toFixed(2) 然后去除尾部的0
|
||||
const fixed = value.toFixed(2);
|
||||
return parseFloat(fixed).toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化百分比(最多2位小数)
|
||||
*/
|
||||
export const formatPercent = (value: number | null | undefined): string => {
|
||||
if (value === null || value === undefined || isNaN(value)) return '--';
|
||||
return parseFloat(value.toFixed(2)).toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成日K线图配置
|
||||
*/
|
||||
@@ -647,23 +774,96 @@ export const getMinuteKLineOption = (theme: Theme, minuteData: MinuteData | null
|
||||
};
|
||||
|
||||
// 技术指标类型
|
||||
export type IndicatorType = 'VOL' | 'MACD' | 'KDJ' | 'RSI' | 'BOLL' | 'NONE';
|
||||
export type IndicatorType = 'VOL' | 'MACD' | 'KDJ' | 'RSI' | 'WR' | 'CCI' | 'BIAS' | 'NONE';
|
||||
|
||||
// 主图指标类型
|
||||
export type MainIndicatorType = 'MA' | 'BOLL' | 'NONE';
|
||||
|
||||
// 绘图工具类型
|
||||
export type DrawingType = 'NONE' | 'SUPPORT_RESISTANCE' | 'TREND_LINE' | 'ALL';
|
||||
|
||||
/**
|
||||
* 计算支撑位和阻力位
|
||||
* 基于近期高低点自动识别
|
||||
* @param highPrices 最高价数组
|
||||
* @param lowPrices 最低价数组
|
||||
* @param period 回看周期(默认20日)
|
||||
*/
|
||||
export const calculateSupportResistance = (
|
||||
highPrices: number[],
|
||||
lowPrices: number[],
|
||||
period = 20
|
||||
): { support: number | null; resistance: number | null } => {
|
||||
if (highPrices.length < period) {
|
||||
return { support: null, resistance: null };
|
||||
}
|
||||
|
||||
// 取最近 period 天的数据
|
||||
const recentHighs = highPrices.slice(-period);
|
||||
const recentLows = lowPrices.slice(-period);
|
||||
|
||||
// 阻力位:近期最高价
|
||||
const resistance = Math.max(...recentHighs);
|
||||
// 支撑位:近期最低价
|
||||
const support = Math.min(...recentLows);
|
||||
|
||||
return { support, resistance };
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算趋势线数据点
|
||||
* 使用线性回归拟合趋势
|
||||
* @param closePrices 收盘价数组
|
||||
* @param period 回看周期(默认60日)
|
||||
*/
|
||||
export const calculateTrendLine = (
|
||||
closePrices: number[],
|
||||
period = 60
|
||||
): { startPrice: number; endPrice: number; slope: number } | null => {
|
||||
if (closePrices.length < 5) return null;
|
||||
|
||||
// 取最近 period 天的数据(或全部数据如果不足)
|
||||
const len = Math.min(period, closePrices.length);
|
||||
const prices = closePrices.slice(-len);
|
||||
|
||||
// 使用简单线性回归计算趋势线
|
||||
// y = mx + b
|
||||
let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
|
||||
const n = prices.length;
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
sumX += i;
|
||||
sumY += prices[i];
|
||||
sumXY += i * prices[i];
|
||||
sumXX += i * i;
|
||||
}
|
||||
|
||||
const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
|
||||
const intercept = (sumY - slope * sumX) / n;
|
||||
|
||||
// 起点和终点价格
|
||||
const startPrice = intercept;
|
||||
const endPrice = intercept + slope * (n - 1);
|
||||
|
||||
return { startPrice, endPrice, slope };
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成日K线图配置 - 黑金主题(专业版 - 支持多技术指标)
|
||||
* @param tradeData K线数据
|
||||
* @param analysisMap 涨幅分析数据
|
||||
* @param subIndicator 副图指标 (默认MACD)
|
||||
* @param mainIndicator 主图指标 (默认MA)
|
||||
* @param showAnalysis 是否显示涨幅分析标记 (默认true)
|
||||
* @param drawingType 绘图工具类型 (默认NONE)
|
||||
*/
|
||||
export const getKLineDarkGoldOption = (
|
||||
tradeData: TradeDayData[],
|
||||
analysisMap: Record<number, RiseAnalysis>,
|
||||
subIndicator: IndicatorType = 'MACD',
|
||||
mainIndicator: MainIndicatorType = 'MA'
|
||||
mainIndicator: MainIndicatorType = 'MA',
|
||||
showAnalysis: boolean = true,
|
||||
drawingType: DrawingType = 'NONE'
|
||||
): EChartsOption => {
|
||||
if (!tradeData || tradeData.length === 0) return {};
|
||||
|
||||
@@ -700,9 +900,16 @@ export const getKLineDarkGoldOption = (
|
||||
const rsi6 = calculateRSI(closePrices, 6);
|
||||
const rsi12 = calculateRSI(closePrices, 12);
|
||||
const rsi24 = calculateRSI(closePrices, 24);
|
||||
const wr14 = calculateWR(highPrices, lowPrices, closePrices, 14);
|
||||
const wr6 = calculateWR(highPrices, lowPrices, closePrices, 6);
|
||||
const cci14 = calculateCCI(highPrices, lowPrices, closePrices, 14);
|
||||
const bias6 = calculateBIAS(closePrices, 6);
|
||||
const bias12 = calculateBIAS(closePrices, 12);
|
||||
const bias24 = calculateBIAS(closePrices, 24);
|
||||
|
||||
// 创建涨幅分析标记点
|
||||
// 创建涨幅分析标记点(仅当 showAnalysis 为 true 时)
|
||||
const scatterData: [number, number][] = [];
|
||||
if (showAnalysis) {
|
||||
Object.keys(analysisMap).forEach((dateIndex) => {
|
||||
const idx = parseInt(dateIndex);
|
||||
if (tradeData[idx]) {
|
||||
@@ -710,6 +917,7 @@ export const getKLineDarkGoldOption = (
|
||||
scatterData.push([idx, value]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 根据是否显示副图指标调整布局
|
||||
const hasSubIndicator = subIndicator !== 'VOL' && subIndicator !== 'NONE';
|
||||
@@ -834,6 +1042,45 @@ export const getKLineDarkGoldOption = (
|
||||
legendData.push('BOLL上轨', 'BOLL中轨', 'BOLL下轨');
|
||||
}
|
||||
|
||||
// 计算绘图工具数据
|
||||
const supportResistance = (drawingType === 'SUPPORT_RESISTANCE' || drawingType === 'ALL')
|
||||
? calculateSupportResistance(highPrices, lowPrices)
|
||||
: null;
|
||||
const trendLine = (drawingType === 'TREND_LINE' || drawingType === 'ALL')
|
||||
? calculateTrendLine(closePrices)
|
||||
: null;
|
||||
|
||||
// 构建 markLine 数据
|
||||
const markLineData: any[] = [];
|
||||
if (supportResistance) {
|
||||
if (supportResistance.resistance !== null) {
|
||||
markLineData.push({
|
||||
name: '阻力位',
|
||||
yAxis: supportResistance.resistance,
|
||||
lineStyle: { color: red, type: 'dashed', width: 1.5 },
|
||||
label: {
|
||||
formatter: `阻力 ${formatPrice(supportResistance.resistance)}`,
|
||||
position: 'end',
|
||||
color: red,
|
||||
fontSize: 10,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (supportResistance.support !== null) {
|
||||
markLineData.push({
|
||||
name: '支撑位',
|
||||
yAxis: supportResistance.support,
|
||||
lineStyle: { color: green, type: 'dashed', width: 1.5 },
|
||||
label: {
|
||||
formatter: `支撑 ${formatPrice(supportResistance.support)}`,
|
||||
position: 'end',
|
||||
color: green,
|
||||
fontSize: 10,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 构建系列数据
|
||||
const series: EChartsOption['series'] = [
|
||||
{
|
||||
@@ -846,9 +1093,47 @@ export const getKLineDarkGoldOption = (
|
||||
borderColor: red,
|
||||
borderColor0: green,
|
||||
},
|
||||
markLine: markLineData.length > 0 ? {
|
||||
symbol: ['none', 'none'],
|
||||
data: markLineData,
|
||||
silent: true,
|
||||
} : undefined,
|
||||
},
|
||||
];
|
||||
|
||||
// 添加趋势线(使用单独的 line series)
|
||||
if (trendLine && (drawingType === 'TREND_LINE' || drawingType === 'ALL')) {
|
||||
const trendLineLen = Math.min(60, closePrices.length);
|
||||
const trendLineStartIdx = closePrices.length - trendLineLen;
|
||||
const trendLineData: (number | null)[] = [];
|
||||
|
||||
// 前面填充 null
|
||||
for (let i = 0; i < trendLineStartIdx; i++) {
|
||||
trendLineData.push(null);
|
||||
}
|
||||
|
||||
// 计算趋势线上每个点的值
|
||||
const { startPrice, slope } = trendLine;
|
||||
for (let i = 0; i < trendLineLen; i++) {
|
||||
trendLineData.push(startPrice + slope * i);
|
||||
}
|
||||
|
||||
series.push({
|
||||
name: '趋势线',
|
||||
type: 'line',
|
||||
data: trendLineData,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: '#FF6B6B',
|
||||
width: 2,
|
||||
type: 'dashed',
|
||||
opacity: 0.8,
|
||||
},
|
||||
z: 50,
|
||||
});
|
||||
legendData.push('趋势线');
|
||||
}
|
||||
|
||||
// 添加主图指标
|
||||
if (mainIndicator === 'MA') {
|
||||
series.push(
|
||||
@@ -1067,6 +1352,110 @@ export const getKLineDarkGoldOption = (
|
||||
}
|
||||
);
|
||||
legendData.push('RSI6', 'RSI12', 'RSI24');
|
||||
} else if (subIndicator === 'WR') {
|
||||
series.push(
|
||||
{
|
||||
name: 'WR14',
|
||||
type: 'line',
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
data: wr14,
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { color: gold, width: 1 },
|
||||
},
|
||||
{
|
||||
name: 'WR6',
|
||||
type: 'line',
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
data: wr6,
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { color: cyan, width: 1 },
|
||||
}
|
||||
);
|
||||
legendData.push('WR14', 'WR6');
|
||||
} else if (subIndicator === 'CCI') {
|
||||
series.push({
|
||||
name: 'CCI',
|
||||
type: 'line',
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
data: cci14,
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { color: gold, width: 1.5 },
|
||||
});
|
||||
// 添加超买超卖参考线
|
||||
series.push(
|
||||
{
|
||||
name: '+100',
|
||||
type: 'line',
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
data: dates.map(() => 100),
|
||||
symbol: 'none',
|
||||
lineStyle: { color: red, width: 1, type: 'dashed', opacity: 0.5 },
|
||||
silent: true,
|
||||
},
|
||||
{
|
||||
name: '-100',
|
||||
type: 'line',
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
data: dates.map(() => -100),
|
||||
symbol: 'none',
|
||||
lineStyle: { color: green, width: 1, type: 'dashed', opacity: 0.5 },
|
||||
silent: true,
|
||||
}
|
||||
);
|
||||
legendData.push('CCI');
|
||||
} else if (subIndicator === 'BIAS') {
|
||||
series.push(
|
||||
{
|
||||
name: 'BIAS6',
|
||||
type: 'line',
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
data: bias6,
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { color: gold, width: 1 },
|
||||
},
|
||||
{
|
||||
name: 'BIAS12',
|
||||
type: 'line',
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
data: bias12,
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { color: cyan, width: 1 },
|
||||
},
|
||||
{
|
||||
name: 'BIAS24',
|
||||
type: 'line',
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
data: bias24,
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { color: purple, width: 1 },
|
||||
}
|
||||
);
|
||||
// 添加零轴参考线
|
||||
series.push({
|
||||
name: '零轴',
|
||||
type: 'line',
|
||||
xAxisIndex: 2,
|
||||
yAxisIndex: 2,
|
||||
data: dates.map(() => 0),
|
||||
symbol: 'none',
|
||||
lineStyle: { color: textMuted, width: 1, type: 'dashed', opacity: 0.5 },
|
||||
silent: true,
|
||||
});
|
||||
legendData.push('BIAS6', 'BIAS12', 'BIAS24');
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1091,6 +1480,32 @@ export const getKLineDarkGoldOption = (
|
||||
borderColor: gold,
|
||||
borderWidth: 1,
|
||||
textStyle: { color: textColor, fontSize: 11 },
|
||||
formatter: (params: unknown) => {
|
||||
const paramsArr = params as { name: string; marker: string; seriesName: string; data: number[] | number | null; value: number | null }[];
|
||||
if (!paramsArr || paramsArr.length === 0) return '';
|
||||
|
||||
const dateStr = paramsArr[0].name;
|
||||
let result = `<div style="font-weight: bold; color: ${gold}; margin-bottom: 6px;">${dateStr}</div>`;
|
||||
|
||||
paramsArr.forEach((param) => {
|
||||
const { seriesName, marker, data, value } = param;
|
||||
if (seriesName === 'K线' && Array.isArray(data)) {
|
||||
const [open, close, low, high] = data;
|
||||
result += `${marker} 开:${formatPrice(open)} 收:${formatPrice(close)} 低:${formatPrice(low)} 高:${formatPrice(high)}<br/>`;
|
||||
} else if (seriesName === '成交量' && typeof value === 'number') {
|
||||
const volStr = value >= 100000000 ? (value / 100000000).toFixed(2) + '亿' :
|
||||
value >= 10000 ? (value / 10000).toFixed(0) + '万' : value.toString();
|
||||
result += `${marker} 成交量:${volStr}<br/>`;
|
||||
} else if (seriesName === '涨幅分析') {
|
||||
// 跳过涨幅分析标记
|
||||
} else if (typeof value === 'number' || (value !== null && !isNaN(Number(value)))) {
|
||||
// MA、BOLL、MACD、KDJ、RSI 等指标
|
||||
result += `${marker} ${seriesName}:${formatPrice(Number(value))}<br/>`;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
},
|
||||
axisPointer: {
|
||||
link: [{ xAxisIndex: 'all' }],
|
||||
@@ -1218,20 +1633,20 @@ export const getMinuteKLineDarkGoldOption = (minuteData: MinuteData | null): ECh
|
||||
|
||||
let result = `<div style="font-weight: bold; color: ${gold}; margin-bottom: 6px;">${timeStr}</div>`;
|
||||
result += `<div style="display: flex; justify-content: space-between; margin-bottom: 3px;">`;
|
||||
result += `<span>现价:</span><span style="color: ${changeColor}; font-weight: bold;">${price.toFixed(2)}</span></div>`;
|
||||
result += `<span>现价:</span><span style="color: ${changeColor}; font-weight: bold;">${formatPrice(price)}</span></div>`;
|
||||
result += `<div style="display: flex; justify-content: space-between; margin-bottom: 3px;">`;
|
||||
result += `<span>涨跌:</span><span style="color: ${changeColor};">${changeSign}${change.toFixed(2)} (${changeSign}${changePercent.toFixed(2)}%)</span></div>`;
|
||||
result += `<span>涨跌:</span><span style="color: ${changeColor};">${changeSign}${formatPrice(change)} (${changeSign}${formatPercent(changePercent)}%)</span></div>`;
|
||||
|
||||
// 查找均价
|
||||
const avgPrice = avgPrices[dataIndex];
|
||||
if (avgPrice !== null) {
|
||||
result += `<div style="display: flex; justify-content: space-between; margin-bottom: 3px;">`;
|
||||
result += `<span>均价:</span><span style="color: ${orange};">${avgPrice.toFixed(2)}</span></div>`;
|
||||
result += `<span>均价:</span><span style="color: ${orange};">${formatPrice(avgPrice)}</span></div>`;
|
||||
}
|
||||
|
||||
// 成交量
|
||||
const volume = item.volume;
|
||||
const volumeStr = volume >= 10000 ? (volume / 10000).toFixed(1) + '万' : volume.toString();
|
||||
const volumeStr = volume >= 10000 ? formatPrice(volume / 10000) + '万' : volume.toString();
|
||||
result += `<div style="display: flex; justify-content: space-between;">`;
|
||||
result += `<span>成交:</span><span style="color: ${goldLight};">${volumeStr}手</span></div>`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user