|
@@ -1,5 +1,5 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <echart-base :options="options" v-model:loading="loading"></echart-base>
|
|
|
|
|
|
|
+ <echart-base :options="[options]" v-model:loading="loading"></echart-base>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
<script lang="ts">
|
|
@@ -17,15 +17,20 @@ export default defineComponent({
|
|
|
EchartBase,
|
|
EchartBase,
|
|
|
},
|
|
},
|
|
|
props: {
|
|
props: {
|
|
|
|
|
+ // 实时行情数据
|
|
|
|
|
+ quoteData: {
|
|
|
|
|
+ type: Object as PropType<QueryQuoteDayRsp>,
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ },
|
|
|
// 周期类型
|
|
// 周期类型
|
|
|
cycleType: {
|
|
cycleType: {
|
|
|
type: Number as PropType<CycleType>,
|
|
type: Number as PropType<CycleType>,
|
|
|
required: true,
|
|
required: true,
|
|
|
},
|
|
},
|
|
|
- // 实时行情数据
|
|
|
|
|
- quoteData: {
|
|
|
|
|
- type: Object as PropType<QueryQuoteDayRsp>,
|
|
|
|
|
- required: true,
|
|
|
|
|
|
|
+ // 指标类型
|
|
|
|
|
+ seriesType: {
|
|
|
|
|
+ type: String,
|
|
|
|
|
+ default: 'MACD',
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
setup(props) {
|
|
setup(props) {
|
|
@@ -37,17 +42,20 @@ export default defineComponent({
|
|
|
const handleData = (rawData: QueryHistoryDatasRsp[]): void => {
|
|
const handleData = (rawData: QueryHistoryDatasRsp[]): void => {
|
|
|
historyIndexs.length = 0; // 清空数据
|
|
historyIndexs.length = 0; // 清空数据
|
|
|
const datas: number[][] = [],
|
|
const datas: number[][] = [],
|
|
|
- times: string[] = [];
|
|
|
|
|
|
|
+ times: string[] = [],
|
|
|
|
|
+ volume: number[] = [];
|
|
|
|
|
|
|
|
rawData.forEach((item, index) => {
|
|
rawData.forEach((item, index) => {
|
|
|
- const { o, c, h, l, ts } = item;
|
|
|
|
|
- datas.push([o, c, h, l]);
|
|
|
|
|
- times.push(moment(ts).format('MM-DD HH:mm'));
|
|
|
|
|
|
|
+ const { o, c, h, l, ts, tv } = item;
|
|
|
|
|
+ datas.push([o, c, l, h]);
|
|
|
|
|
+ times.push(moment(ts).format('YYYY/MM/DD HH:mm:ss'));
|
|
|
|
|
+ volume.push(tv);
|
|
|
if (!item.f) historyIndexs.push(index); // 排除补充数据
|
|
if (!item.f) historyIndexs.push(index); // 排除补充数据
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
chartData.datas = datas;
|
|
chartData.datas = datas;
|
|
|
chartData.times = times;
|
|
chartData.times = times;
|
|
|
|
|
+ chartData.volume = volume;
|
|
|
chartData.ma5 = calcMA(datas, 5);
|
|
chartData.ma5 = calcMA(datas, 5);
|
|
|
chartData.ma10 = calcMA(datas, 10);
|
|
chartData.ma10 = calcMA(datas, 10);
|
|
|
chartData.ma15 = calcMA(datas, 15);
|
|
chartData.ma15 = calcMA(datas, 15);
|
|
@@ -56,54 +64,56 @@ export default defineComponent({
|
|
|
chartData.macd = macd;
|
|
chartData.macd = macd;
|
|
|
chartData.dif = dif;
|
|
chartData.dif = dif;
|
|
|
chartData.dea = dea;
|
|
chartData.dea = dea;
|
|
|
|
|
+
|
|
|
|
|
+ chartData.kdj = calcKDJ(datas);
|
|
|
|
|
+ chartData.cci = clacCCI(datas);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// 计算平均线
|
|
// 计算平均线
|
|
|
const calcMA = (data: number[][], count: number) => {
|
|
const calcMA = (data: number[][], count: number) => {
|
|
|
- const result: string[] = [];
|
|
|
|
|
|
|
+ const ma: string[] = [];
|
|
|
if (data.length >= count) {
|
|
if (data.length >= count) {
|
|
|
// 均线起始位置
|
|
// 均线起始位置
|
|
|
const startIndex = historyIndexs[count - 1];
|
|
const startIndex = historyIndexs[count - 1];
|
|
|
- data.forEach((item, index) => {
|
|
|
|
|
- if (index < startIndex) {
|
|
|
|
|
- result.push('-');
|
|
|
|
|
|
|
+ for (let i = 0; i < data.length; i++) {
|
|
|
|
|
+ if (i < startIndex) {
|
|
|
|
|
+ ma.push('-');
|
|
|
} else {
|
|
} else {
|
|
|
- const j = historyIndexs.findIndex((val) => val === index);
|
|
|
|
|
|
|
+ const j = historyIndexs.findIndex((val) => val === i);
|
|
|
// 判断是否补充数据
|
|
// 判断是否补充数据
|
|
|
if (j === -1) {
|
|
if (j === -1) {
|
|
|
// 取上个平均值
|
|
// 取上个平均值
|
|
|
- result.push(result[result.length - 1]);
|
|
|
|
|
|
|
+ ma.push(ma[ma.length - 1]);
|
|
|
} else {
|
|
} else {
|
|
|
// 向后取MA数
|
|
// 向后取MA数
|
|
|
const maIndexs = historyIndexs.slice(j - (count - 1), j + 1);
|
|
const maIndexs = historyIndexs.slice(j - (count - 1), j + 1);
|
|
|
// 计算总价,data[val][1]=收盘价
|
|
// 计算总价,data[val][1]=收盘价
|
|
|
const total = maIndexs.reduce((sum, val) => sum + data[val][1], 0);
|
|
const total = maIndexs.reduce((sum, val) => sum + data[val][1], 0);
|
|
|
// 计算均线
|
|
// 计算均线
|
|
|
- result.push((total / count).toFixed(2));
|
|
|
|
|
|
|
+ ma.push((total / count).toFixed(2));
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- return result;
|
|
|
|
|
|
|
+ return ma;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// 计算EMA
|
|
// 计算EMA
|
|
|
const calcEMA = (close: number[], n: number) => {
|
|
const calcEMA = (close: number[], n: number) => {
|
|
|
- const result: number[] = [],
|
|
|
|
|
|
|
+ const ema: number[] = [],
|
|
|
a = 2 / (n + 1); // 平滑系数
|
|
a = 2 / (n + 1); // 平滑系数
|
|
|
-
|
|
|
|
|
for (let i = 0; i < close.length; i++) {
|
|
for (let i = 0; i < close.length; i++) {
|
|
|
if (i === 0) {
|
|
if (i === 0) {
|
|
|
//第一个EMA(n)是前n个收盘价代数平均
|
|
//第一个EMA(n)是前n个收盘价代数平均
|
|
|
- const ema = close.slice(0, n).reduce((sum, val) => sum + val, 0) / n;
|
|
|
|
|
- result.push(ema);
|
|
|
|
|
|
|
+ const result = close.slice(0, n).reduce((sum, val) => sum + val, 0) / n;
|
|
|
|
|
+ ema.push(result);
|
|
|
} else {
|
|
} else {
|
|
|
// EMA(n) = α × Close + (1 - α) × EMA(n - 1)
|
|
// EMA(n) = α × Close + (1 - α) × EMA(n - 1)
|
|
|
- const ema = a * close[i] + (1 - a) * result[i - 1];
|
|
|
|
|
- result.push(ema);
|
|
|
|
|
|
|
+ const result = a * close[i] + (1 - a) * ema[i - 1];
|
|
|
|
|
+ ema.push(result);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- return result;
|
|
|
|
|
|
|
+ return ema;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// 计算DEA
|
|
// 计算DEA
|
|
@@ -113,21 +123,21 @@ export default defineComponent({
|
|
|
|
|
|
|
|
// 计算DIF
|
|
// 计算DIF
|
|
|
const calcDIF = (close: number[]) => {
|
|
const calcDIF = (close: number[]) => {
|
|
|
- const result: number[] = [],
|
|
|
|
|
|
|
+ const dif: number[] = [],
|
|
|
emaShort = calcEMA(close, 12),
|
|
emaShort = calcEMA(close, 12),
|
|
|
emaLong = calcEMA(close, 26);
|
|
emaLong = calcEMA(close, 26);
|
|
|
|
|
|
|
|
for (let i = 0; i < close.length; i++) {
|
|
for (let i = 0; i < close.length; i++) {
|
|
|
- const dif = emaShort[i] - emaLong[i];
|
|
|
|
|
- result.push(dif);
|
|
|
|
|
|
|
+ const result = emaShort[i] - emaLong[i];
|
|
|
|
|
+ dif.push(result);
|
|
|
}
|
|
}
|
|
|
- return result;
|
|
|
|
|
|
|
+ return dif;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// 计算MACD
|
|
// 计算MACD
|
|
|
const calcMACD = (data: number[][]) => {
|
|
const calcMACD = (data: number[][]) => {
|
|
|
const macd = [],
|
|
const macd = [],
|
|
|
- close = data.map((item) => item[1]), // 收盘价数据
|
|
|
|
|
|
|
+ close = data.map((val) => val[1]), // 收盘价数据
|
|
|
dif = calcDIF(close),
|
|
dif = calcDIF(close),
|
|
|
dea = calcDEA(dif);
|
|
dea = calcDEA(dif);
|
|
|
|
|
|
|
@@ -141,9 +151,61 @@ export default defineComponent({
|
|
|
};
|
|
};
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ // 计算KDJ
|
|
|
|
|
+ const calcKDJ = (data: number[][]) => {
|
|
|
|
|
+ const kdj: string[][] = []; // [[k,d,j]]
|
|
|
|
|
+ for (let i = 0; i < data.length; i++) {
|
|
|
|
|
+ if (i < 8) {
|
|
|
|
|
+ kdj.push(['-', '-', '-']);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ let rsv = 50; // 如果最低价等于最高价,RSV默认值为50
|
|
|
|
|
+ if (data[i][2] !== data[i][3]) {
|
|
|
|
|
+ const n9 = data.slice(i - 8, i + 1).map((val) => val[1]), // 取前9个收盘价
|
|
|
|
|
+ max = Math.max(...n9),
|
|
|
|
|
+ min = Math.min(...n9);
|
|
|
|
|
+ // 计算RSV
|
|
|
|
|
+ rsv = ((data[i][1] - min) / (max - min)) * 100;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const yestK = Number(kdj[kdj.length - 1][0]); // 取前一日K值
|
|
|
|
|
+ const yestD = Number(kdj[kdj.length - 1][1]); // 取前一日D值
|
|
|
|
|
+
|
|
|
|
|
+ if (isNaN(yestK) || isNaN(yestD)) {
|
|
|
|
|
+ // 如果前一日的K值或D值不存在则默认值为50
|
|
|
|
|
+ kdj.push(['50', '50', '50']);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const k = (2 / 3) * yestK + (1 / 3) * rsv,
|
|
|
|
|
+ d = (2 / 3) * yestD + (1 / 3) * yestK,
|
|
|
|
|
+ j = 3 * k - 2 * d;
|
|
|
|
|
+
|
|
|
|
|
+ kdj.push([k.toFixed(2), d.toFixed(2), j.toFixed(2)]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return kdj;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const clacCCI = (data: number[][]) => {
|
|
|
|
|
+ const cci: string[] = [];
|
|
|
|
|
+ for (let i = 0; i < data.length; i++) {
|
|
|
|
|
+ if (i < 13) {
|
|
|
|
|
+ cci.push('-');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const tp = (data[i][1] + data[i][2] + data[i][3]) / 3, // (收盘价 + 最低价 + 最高价) / 3
|
|
|
|
|
+ n14 = data.slice(i - 13, i + 1), // 取前14条数据
|
|
|
|
|
+ ma = n14.reduce((sum, val) => sum + (val[1] + val[2] + val[3]) / 3, 0) / 14, // 计算前14条数据的(TP)价总和÷N
|
|
|
|
|
+ md = n14.reduce((sum, val) => sum + Math.abs(ma - (val[1] + val[2] + val[3]) / 3), 0) / 14, // 计算前14条数据的(MA-TP)价总和÷N
|
|
|
|
|
+ result = (tp - ma) / md / 0.015;
|
|
|
|
|
+
|
|
|
|
|
+ cci.push(result.toFixed(2));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return cci;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
// 更新图表K线数据
|
|
// 更新图表K线数据
|
|
|
const updateChartData = () => {
|
|
const updateChartData = () => {
|
|
|
- const { datas, times } = chartData,
|
|
|
|
|
|
|
+ const { datas, times, volume, kdj } = chartData,
|
|
|
lastIndex = datas.length - 1, // 历史行情最后索引位置
|
|
lastIndex = datas.length - 1, // 历史行情最后索引位置
|
|
|
lastTime = moment(times[times.length - 1]), // 历史行情最后时间
|
|
lastTime = moment(times[times.length - 1]), // 历史行情最后时间
|
|
|
newTime = moment(props.quoteData.lasttime), // 实时行情最新时间
|
|
newTime = moment(props.quoteData.lasttime), // 实时行情最新时间
|
|
@@ -176,8 +238,9 @@ export default defineComponent({
|
|
|
// 判断时间差是否大于周期时间
|
|
// 判断时间差是否大于周期时间
|
|
|
if (diffTime > cycleMilliseconds) {
|
|
if (diffTime > cycleMilliseconds) {
|
|
|
lastTime.add(cycleMilliseconds, 'ms');
|
|
lastTime.add(cycleMilliseconds, 'ms');
|
|
|
- times.push(lastTime.format('MM-DD HH:mm')); // 添加历史行情时间
|
|
|
|
|
|
|
+ times.push(lastTime.format('YYYY/MM/DD HH:mm:ss')); // 添加历史行情时间
|
|
|
datas.push([newPrice, newPrice, newPrice, newPrice]); // 添加历史行情数据
|
|
datas.push([newPrice, newPrice, newPrice, newPrice]); // 添加历史行情数据
|
|
|
|
|
+ volume.push(0);
|
|
|
historyIndexs.push(lastIndex + 1); // 添加历史行情索引
|
|
historyIndexs.push(lastIndex + 1); // 添加历史行情索引
|
|
|
} else {
|
|
} else {
|
|
|
const lastPrice = datas[lastIndex];
|
|
const lastPrice = datas[lastIndex];
|
|
@@ -191,21 +254,25 @@ export default defineComponent({
|
|
|
datas[lastIndex] = lastPrice; // 更新历史行情数据
|
|
datas[lastIndex] = lastPrice; // 更新历史行情数据
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 延迟图表更新,减少卡顿
|
|
|
|
|
- debounce(() => {
|
|
|
|
|
- chartData.ma5 = calcMA(datas, 5);
|
|
|
|
|
- chartData.ma10 = calcMA(datas, 10);
|
|
|
|
|
- chartData.ma15 = calcMA(datas, 15);
|
|
|
|
|
|
|
+ chartData.ma5 = calcMA(datas, 5);
|
|
|
|
|
+ chartData.ma10 = calcMA(datas, 10);
|
|
|
|
|
+ chartData.ma15 = calcMA(datas, 15);
|
|
|
|
|
|
|
|
- const { dif, dea, macd } = calcMACD(datas);
|
|
|
|
|
- chartData.dif = dif;
|
|
|
|
|
- chartData.dea = dea;
|
|
|
|
|
- chartData.macd = macd;
|
|
|
|
|
|
|
+ const { dif, dea, macd } = calcMACD(datas);
|
|
|
|
|
+ chartData.dif = dif;
|
|
|
|
|
+ chartData.dea = dea;
|
|
|
|
|
+ chartData.macd = macd;
|
|
|
|
|
+
|
|
|
|
|
+ chartData.kdj = calcKDJ(datas);
|
|
|
|
|
+ chartData.cci = clacCCI(datas);
|
|
|
|
|
|
|
|
- updateOptions();
|
|
|
|
|
|
|
+ // 延迟图表更新,减少卡顿
|
|
|
|
|
+ debounce(() => {
|
|
|
|
|
+ updateOptions(props.seriesType);
|
|
|
}, 1000);
|
|
}, 1000);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ // 监听行情最新价推送
|
|
|
watch(
|
|
watch(
|
|
|
() => props.quoteData.last,
|
|
() => props.quoteData.last,
|
|
|
() => {
|
|
() => {
|
|
@@ -215,6 +282,16 @@ export default defineComponent({
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
|
|
+ // 监听指标类型
|
|
|
|
|
+ watch(
|
|
|
|
|
+ () => props.seriesType,
|
|
|
|
|
+ (val) => {
|
|
|
|
|
+ if (!loading.value) {
|
|
|
|
|
+ updateOptions(val);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
// 监听周期选择变化
|
|
// 监听周期选择变化
|
|
|
watchEffect(() => {
|
|
watchEffect(() => {
|
|
|
loading.value = true;
|
|
loading.value = true;
|
|
@@ -225,10 +302,11 @@ export default defineComponent({
|
|
|
};
|
|
};
|
|
|
// 查询K线数据
|
|
// 查询K线数据
|
|
|
queryHistoryDatas(params).then((res) => {
|
|
queryHistoryDatas(params).then((res) => {
|
|
|
|
|
+ loading.value = false;
|
|
|
// 日期升序排序
|
|
// 日期升序排序
|
|
|
const kdata = res.sort((a, b) => moment(a.ts).valueOf() - moment(b.ts).valueOf());
|
|
const kdata = res.sort((a, b) => moment(a.ts).valueOf() - moment(b.ts).valueOf());
|
|
|
handleData(kdata);
|
|
handleData(kdata);
|
|
|
- initOptions();
|
|
|
|
|
|
|
+ initOptions(props.seriesType);
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|