|
|
|
@ -514,7 +514,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
#klineChart {
|
|
|
|
#klineChart {
|
|
|
|
width: 100%;
|
|
|
|
width: 100%;
|
|
|
|
height: 500px;
|
|
|
|
height: 650px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Log */
|
|
|
|
/* Log */
|
|
|
|
@ -1881,12 +1881,16 @@
|
|
|
|
const upColor = '#ef4444';
|
|
|
|
const upColor = '#ef4444';
|
|
|
|
const downColor = '#10b981';
|
|
|
|
const downColor = '#10b981';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算MACD指标
|
|
|
|
|
|
|
|
const macdData = calculateMACD(candles);
|
|
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
const option = {
|
|
|
|
animation: true,
|
|
|
|
animation: true,
|
|
|
|
legend: {
|
|
|
|
legend: {
|
|
|
|
bottom: 10,
|
|
|
|
top: 10,
|
|
|
|
left: 'center',
|
|
|
|
left: 'center',
|
|
|
|
data: ['K线', 'MA5', 'MA10', 'MA20', 'MA60']
|
|
|
|
data: ['K线', 'MA5', 'MA10', 'MA20', 'MA60', 'DIF', 'DEA', 'MACD'],
|
|
|
|
|
|
|
|
textStyle: { fontSize: 11 }
|
|
|
|
},
|
|
|
|
},
|
|
|
|
tooltip: {
|
|
|
|
tooltip: {
|
|
|
|
trigger: 'axis',
|
|
|
|
trigger: 'axis',
|
|
|
|
@ -1915,6 +1919,10 @@
|
|
|
|
result += `<div>${param.seriesName}: ${param.data}</div>`;
|
|
|
|
result += `<div>${param.seriesName}: ${param.data}</div>`;
|
|
|
|
} else if (param.seriesName === '成交量') {
|
|
|
|
} else if (param.seriesName === '成交量') {
|
|
|
|
result += `<div>成交量: ${param.data}</div>`;
|
|
|
|
result += `<div>成交量: ${param.data}</div>`;
|
|
|
|
|
|
|
|
} else if (param.seriesName === 'DIF' || param.seriesName === 'DEA') {
|
|
|
|
|
|
|
|
result += `<div>${param.seriesName}: ${param.data}</div>`;
|
|
|
|
|
|
|
|
} else if (param.seriesName === 'MACD') {
|
|
|
|
|
|
|
|
result += `<div>MACD: ${param.data}</div>`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
@ -1922,8 +1930,9 @@
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
grid: [
|
|
|
|
grid: [
|
|
|
|
{ left: 60, right: 40, height: '60%' },
|
|
|
|
{ left: 60, right: 60, top: 50, height: '48%' },
|
|
|
|
{ left: 60, right: 40, top: '75%', height: '15%' }
|
|
|
|
{ left: 60, right: 60, top: '56%', height: '13%' },
|
|
|
|
|
|
|
|
{ left: 60, right: 60, top: '74%', height: '14%' }
|
|
|
|
],
|
|
|
|
],
|
|
|
|
xAxis: [
|
|
|
|
xAxis: [
|
|
|
|
{
|
|
|
|
{
|
|
|
|
@ -1938,10 +1947,17 @@
|
|
|
|
data: dates,
|
|
|
|
data: dates,
|
|
|
|
gridIndex: 1,
|
|
|
|
gridIndex: 1,
|
|
|
|
axisLine: { lineStyle: { color: '#8392A5' } },
|
|
|
|
axisLine: { lineStyle: { color: '#8392A5' } },
|
|
|
|
|
|
|
|
axisLabel: { show: false }
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
|
|
data: dates,
|
|
|
|
|
|
|
|
gridIndex: 2,
|
|
|
|
|
|
|
|
axisLine: { lineStyle: { color: '#8392A5' } },
|
|
|
|
axisLabel: {
|
|
|
|
axisLabel: {
|
|
|
|
show: true,
|
|
|
|
show: true,
|
|
|
|
formatter: function(value) {
|
|
|
|
formatter: function(value) {
|
|
|
|
return value.substring(5);
|
|
|
|
return value.substring(11);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -1962,23 +1978,33 @@
|
|
|
|
axisTick: { show: false },
|
|
|
|
axisTick: { show: false },
|
|
|
|
axisLabel: { show: false },
|
|
|
|
axisLabel: { show: false },
|
|
|
|
splitLine: { show: false }
|
|
|
|
splitLine: { show: false }
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
scale: true,
|
|
|
|
|
|
|
|
gridIndex: 2,
|
|
|
|
|
|
|
|
splitNumber: 3,
|
|
|
|
|
|
|
|
axisLine: { lineStyle: { color: '#8392A5' } },
|
|
|
|
|
|
|
|
axisLabel: { show: true, fontSize: 11 },
|
|
|
|
|
|
|
|
splitLine: { show: true, lineStyle: { color: '#E8EEF4', type: 'dashed' } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
],
|
|
|
|
],
|
|
|
|
dataZoom: [
|
|
|
|
dataZoom: [
|
|
|
|
{
|
|
|
|
{
|
|
|
|
type: 'inside',
|
|
|
|
type: 'inside',
|
|
|
|
xAxisIndex: [0, 1],
|
|
|
|
xAxisIndex: [0, 1, 2],
|
|
|
|
start: Math.max(0, 100 - 100 * 100 / candles.length),
|
|
|
|
start: Math.max(0, 100 - 100 * 100 / candles.length),
|
|
|
|
end: 100
|
|
|
|
end: 100
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
show: true,
|
|
|
|
show: true,
|
|
|
|
xAxisIndex: [0, 1],
|
|
|
|
xAxisIndex: [0, 1, 2],
|
|
|
|
type: 'slider',
|
|
|
|
type: 'slider',
|
|
|
|
bottom: 10,
|
|
|
|
bottom: 5,
|
|
|
|
start: Math.max(0, 100 - 100 * 100 / candles.length),
|
|
|
|
start: Math.max(0, 100 - 100 * 100 / candles.length),
|
|
|
|
end: 100,
|
|
|
|
end: 100,
|
|
|
|
height: 20
|
|
|
|
height: 15,
|
|
|
|
|
|
|
|
borderColor: 'transparent',
|
|
|
|
|
|
|
|
backgroundColor: '#f1f5f9'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
],
|
|
|
|
],
|
|
|
|
series: [
|
|
|
|
series: [
|
|
|
|
@ -1998,28 +2024,28 @@
|
|
|
|
type: 'line',
|
|
|
|
type: 'line',
|
|
|
|
data: calculateMA(candles, 5),
|
|
|
|
data: calculateMA(candles, 5),
|
|
|
|
smooth: true,
|
|
|
|
smooth: true,
|
|
|
|
lineStyle: { width: 1 }
|
|
|
|
lineStyle: { width: 1, color: '#f59e0b' }
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: 'MA10',
|
|
|
|
name: 'MA10',
|
|
|
|
type: 'line',
|
|
|
|
type: 'line',
|
|
|
|
data: calculateMA(candles, 10),
|
|
|
|
data: calculateMA(candles, 10),
|
|
|
|
smooth: true,
|
|
|
|
smooth: true,
|
|
|
|
lineStyle: { width: 1 }
|
|
|
|
lineStyle: { width: 1, color: '#3b82f6' }
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: 'MA20',
|
|
|
|
name: 'MA20',
|
|
|
|
type: 'line',
|
|
|
|
type: 'line',
|
|
|
|
data: calculateMA(candles, 20),
|
|
|
|
data: calculateMA(candles, 20),
|
|
|
|
smooth: true,
|
|
|
|
smooth: true,
|
|
|
|
lineStyle: { width: 1 }
|
|
|
|
lineStyle: { width: 1, color: '#ef4444' }
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: 'MA60',
|
|
|
|
name: 'MA60',
|
|
|
|
type: 'line',
|
|
|
|
type: 'line',
|
|
|
|
data: calculateMA(candles, 60),
|
|
|
|
data: calculateMA(candles, 60),
|
|
|
|
smooth: true,
|
|
|
|
smooth: true,
|
|
|
|
lineStyle: { width: 1 }
|
|
|
|
lineStyle: { width: 1, color: '#8b5cf6' }
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: '成交量',
|
|
|
|
name: '成交量',
|
|
|
|
@ -2030,7 +2056,43 @@
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
value: v.value,
|
|
|
|
value: v.value,
|
|
|
|
itemStyle: {
|
|
|
|
itemStyle: {
|
|
|
|
color: v.close >= v.open ? upColor : downColor
|
|
|
|
color: v.close >= v.open ? upColor : downColor,
|
|
|
|
|
|
|
|
opacity: 0.6
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
name: 'DIF',
|
|
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
|
|
xAxisIndex: 2,
|
|
|
|
|
|
|
|
yAxisIndex: 2,
|
|
|
|
|
|
|
|
data: macdData.dif,
|
|
|
|
|
|
|
|
smooth: true,
|
|
|
|
|
|
|
|
lineStyle: { width: 1.5, color: '#3b82f6' },
|
|
|
|
|
|
|
|
symbol: 'none'
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
name: 'DEA',
|
|
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
|
|
xAxisIndex: 2,
|
|
|
|
|
|
|
|
yAxisIndex: 2,
|
|
|
|
|
|
|
|
data: macdData.dea,
|
|
|
|
|
|
|
|
smooth: true,
|
|
|
|
|
|
|
|
lineStyle: { width: 1.5, color: '#f59e0b' },
|
|
|
|
|
|
|
|
symbol: 'none'
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
name: 'MACD',
|
|
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
|
|
xAxisIndex: 2,
|
|
|
|
|
|
|
|
yAxisIndex: 2,
|
|
|
|
|
|
|
|
data: macdData.macd.map((val, idx) => {
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
value: val,
|
|
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
|
|
color: val >= 0 ? upColor : downColor,
|
|
|
|
|
|
|
|
opacity: 0.7
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
})
|
|
|
|
})
|
|
|
|
@ -2061,6 +2123,80 @@
|
|
|
|
return result;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function calculateMACD(candles) {
|
|
|
|
|
|
|
|
const closes = candles.map(c => c.close);
|
|
|
|
|
|
|
|
const ema12 = calculateEMA(closes, 12);
|
|
|
|
|
|
|
|
const ema26 = calculateEMA(closes, 26);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dif = [];
|
|
|
|
|
|
|
|
for (let i = 0; i < closes.length; i++) {
|
|
|
|
|
|
|
|
if (ema12[i] !== '-' && ema26[i] !== '-') {
|
|
|
|
|
|
|
|
dif.push(parseFloat(ema12[i]) - parseFloat(ema26[i]));
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
dif.push(0);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dea = calculateEMARaw(dif, 9);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const macd = dif.map((d, i) => 2 * (d - dea[i]));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return { dif, dea, macd };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function calculateEMA(data, period) {
|
|
|
|
|
|
|
|
const result = [];
|
|
|
|
|
|
|
|
const multiplier = 2 / (period + 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let sum = 0;
|
|
|
|
|
|
|
|
for (let i = 0; i < period - 1 && i < data.length; i++) {
|
|
|
|
|
|
|
|
result.push('-');
|
|
|
|
|
|
|
|
sum += data[i];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (data.length >= period) {
|
|
|
|
|
|
|
|
sum += data[period - 1];
|
|
|
|
|
|
|
|
let ema = sum / period;
|
|
|
|
|
|
|
|
result.push(ema.toFixed(2));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = period; i < data.length; i++) {
|
|
|
|
|
|
|
|
ema = (data[i] - ema) * multiplier + ema;
|
|
|
|
|
|
|
|
result.push(ema.toFixed(2));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function calculateEMARaw(data, period) {
|
|
|
|
|
|
|
|
const result = [];
|
|
|
|
|
|
|
|
const multiplier = 2 / (period + 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let sum = 0;
|
|
|
|
|
|
|
|
let count = 0;
|
|
|
|
|
|
|
|
for (let i = 0; i < data.length; i++) {
|
|
|
|
|
|
|
|
if (data[i] === 0 && count < period) {
|
|
|
|
|
|
|
|
result.push(0);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count < period) {
|
|
|
|
|
|
|
|
sum += data[i];
|
|
|
|
|
|
|
|
count++;
|
|
|
|
|
|
|
|
if (count === period) {
|
|
|
|
|
|
|
|
let ema = sum / period;
|
|
|
|
|
|
|
|
result.push(ema);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
result.push(0);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
const ema = (data[i] - result[result.length - 1]) * multiplier + result[result.length - 1];
|
|
|
|
|
|
|
|
result.push(ema);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getPeriodLabel(period) {
|
|
|
|
function getPeriodLabel(period) {
|
|
|
|
const map = {
|
|
|
|
const map = {
|
|
|
|
'5min': '5分钟',
|
|
|
|
'5min': '5分钟',
|
|
|
|
|