|
|
@@ -6,8 +6,9 @@
|
|
|
import { defineComponent, PropType, ref, watch, watchEffect } from 'vue';
|
|
|
import { QueryHistoryDatasRsp, QueryQuoteDayRsp, QueryHistoryDatas, CycleType } from '@/services/go/quote/interface';
|
|
|
import { QueryHistoryDatas as queryHistoryDatas } from '@/services/go/quote';
|
|
|
-import { handleEchart, ChartData } from './setup';
|
|
|
+import { debounce } from '@/utils/time';
|
|
|
import EchartBase from '../echart-base/index.vue';
|
|
|
+import { handleEchart } from './setup';
|
|
|
import moment from 'moment';
|
|
|
|
|
|
export default defineComponent({
|
|
|
@@ -29,55 +30,54 @@ export default defineComponent({
|
|
|
},
|
|
|
setup(props) {
|
|
|
const loading = ref(true);
|
|
|
+ const historyIndexs: number[] = []; // 行情历史数据中所有非补充数据的索引位置(用于计算均线)
|
|
|
const { chartData, options, updateOptions, initOptions } = handleEchart();
|
|
|
|
|
|
// 处理图表数据
|
|
|
- const handleData = (rawData: QueryHistoryDatasRsp[]): ChartData => {
|
|
|
+ const handleData = (rawData: QueryHistoryDatasRsp[]): void => {
|
|
|
+ historyIndexs.length = 0; // 清空数据
|
|
|
const datas: number[][] = [],
|
|
|
times: string[] = [];
|
|
|
|
|
|
- rawData.forEach((item) => {
|
|
|
+ rawData.forEach((item, index) => {
|
|
|
const { o, c, h, l, ts } = item;
|
|
|
- times.push(moment(ts).format('YYYY-MM-DD HH:mm:ss'));
|
|
|
datas.push([o, c, h, l]);
|
|
|
+ times.push(moment(ts).format('MM-DD HH:mm'));
|
|
|
+ if (!item.f) historyIndexs.push(index); // 排除补充数据
|
|
|
});
|
|
|
|
|
|
- return {
|
|
|
- datas,
|
|
|
- times,
|
|
|
- ma5: calcMA(rawData, 5),
|
|
|
- ma10: calcMA(rawData, 10),
|
|
|
- ma15: calcMA(rawData, 15),
|
|
|
- ...calcMACD(datas),
|
|
|
- };
|
|
|
+ chartData.datas = datas;
|
|
|
+ chartData.times = times;
|
|
|
+ chartData.ma5 = calcMA(datas, 5);
|
|
|
+ chartData.ma10 = calcMA(datas, 10);
|
|
|
+ chartData.ma15 = calcMA(datas, 15);
|
|
|
+
|
|
|
+ const { macd, dif, dea } = calcMACD(datas);
|
|
|
+ chartData.macd = macd;
|
|
|
+ chartData.dif = dif;
|
|
|
+ chartData.dea = dea;
|
|
|
};
|
|
|
|
|
|
// 计算平均线
|
|
|
- const calcMA = (rawData: QueryHistoryDatasRsp[], count: number) => {
|
|
|
+ const calcMA = (data: number[][], count: number) => {
|
|
|
const result: string[] = [];
|
|
|
- if (rawData.length >= count) {
|
|
|
- // 所有非补充数据的索引id
|
|
|
- const dataIndexs = rawData.reduce((prev: number[], cur, index) => {
|
|
|
- if (!cur.f) prev.push(index);
|
|
|
- return prev;
|
|
|
- }, []);
|
|
|
+ if (data.length >= count) {
|
|
|
// 均线起始位置
|
|
|
- const startIndex = dataIndexs[count - 1];
|
|
|
-
|
|
|
- rawData.forEach((item, index) => {
|
|
|
+ const startIndex = historyIndexs[count - 1];
|
|
|
+ data.forEach((item, index) => {
|
|
|
if (index < startIndex) {
|
|
|
result.push('-');
|
|
|
} else {
|
|
|
- const j = dataIndexs.findIndex((val) => val === index);
|
|
|
+ const j = historyIndexs.findIndex((val) => val === index);
|
|
|
// 判断是否补充数据
|
|
|
if (j === -1) {
|
|
|
// 取上个平均值
|
|
|
result.push(result[result.length - 1]);
|
|
|
} else {
|
|
|
// 向后取MA数
|
|
|
- const splitIndexs = dataIndexs.slice(j - (count - 1), j + 1);
|
|
|
- // 计算总价
|
|
|
- const total = splitIndexs.reduce((sum, val) => sum + rawData[val].c, 0);
|
|
|
+ const maIndexs = historyIndexs.slice(j - (count - 1), j + 1);
|
|
|
+ // 计算总价,data[val][1]=收盘价
|
|
|
+ const total = maIndexs.reduce((sum, val) => sum + data[val][1], 0);
|
|
|
// 计算均线
|
|
|
result.push((total / count).toFixed(2));
|
|
|
}
|
|
|
@@ -87,30 +87,19 @@ export default defineComponent({
|
|
|
return result;
|
|
|
};
|
|
|
|
|
|
- // 计算最后一根平均线
|
|
|
- const calcLastMA = (rawData: number[][], count: number): string | undefined => {
|
|
|
- if (rawData.length >= count) {
|
|
|
- const index = rawData.length - 1;
|
|
|
- // 向后取MA数
|
|
|
- const datas = rawData.slice(index - (count - 1), index + 1);
|
|
|
- // 计算总价,val[1]=收盘价
|
|
|
- const total = datas.reduce((sum, val) => sum + val[1], 0);
|
|
|
- // 计算均线
|
|
|
- return (total / count).toFixed(2);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
// 计算EMA
|
|
|
- const calcEMA = (data: number[], n: number) => {
|
|
|
+ const calcEMA = (close: number[], n: number) => {
|
|
|
const result: number[] = [],
|
|
|
a = 2 / (n + 1); // 平滑系数
|
|
|
|
|
|
- for (let i = 0; i < data.length; i++) {
|
|
|
+ for (let i = 0; i < close.length; i++) {
|
|
|
if (i === 0) {
|
|
|
- result.push(data[i]);
|
|
|
+ //第一个EMA(n)是前n个收盘价代数平均
|
|
|
+ const ema = close.slice(0, n).reduce((sum, val) => sum + val, 0) / n;
|
|
|
+ result.push(ema);
|
|
|
} else {
|
|
|
// EMA(n) = α × Close + (1 - α) × EMA(n - 1)
|
|
|
- const ema = a * data[i] + (1 - a) * result[i - 1];
|
|
|
+ const ema = a * close[i] + (1 - a) * result[i - 1];
|
|
|
result.push(ema);
|
|
|
}
|
|
|
}
|
|
|
@@ -123,12 +112,12 @@ export default defineComponent({
|
|
|
};
|
|
|
|
|
|
// 计算DIF
|
|
|
- const calcDIF = (data: number[]) => {
|
|
|
+ const calcDIF = (close: number[]) => {
|
|
|
const result: number[] = [],
|
|
|
- emaShort = calcEMA(data, 12),
|
|
|
- emaLong = calcEMA(data, 26);
|
|
|
+ emaShort = calcEMA(close, 12),
|
|
|
+ emaLong = calcEMA(close, 26);
|
|
|
|
|
|
- for (let i = 0; i < data.length; i++) {
|
|
|
+ for (let i = 0; i < close.length; i++) {
|
|
|
const dif = emaShort[i] - emaLong[i];
|
|
|
result.push(dif);
|
|
|
}
|
|
|
@@ -136,9 +125,9 @@ export default defineComponent({
|
|
|
};
|
|
|
|
|
|
// 计算MACD
|
|
|
- const calcMACD = (rawData: number[][]) => {
|
|
|
+ const calcMACD = (data: number[][]) => {
|
|
|
const macd = [],
|
|
|
- close = rawData.map((item) => item[1]), // 收盘价数据
|
|
|
+ close = data.map((item) => item[1]), // 收盘价数据
|
|
|
dif = calcDIF(close),
|
|
|
dea = calcDEA(dif);
|
|
|
|
|
|
@@ -152,90 +141,97 @@ export default defineComponent({
|
|
|
};
|
|
|
};
|
|
|
|
|
|
+ // 更新图表K线数据
|
|
|
+ const updateChartData = () => {
|
|
|
+ const { datas, times } = chartData,
|
|
|
+ lastIndex = datas.length - 1, // 历史行情最后索引位置
|
|
|
+ lastTime = moment(times[times.length - 1]), // 历史行情最后时间
|
|
|
+ newTime = moment(props.quoteData.lasttime), // 实时行情最新时间
|
|
|
+ newPrice = props.quoteData.last; // 实时行情最新价
|
|
|
+
|
|
|
+ let cycleMilliseconds = 60 * 1000; // 周期毫秒数
|
|
|
+
|
|
|
+ switch (props.cycleType) {
|
|
|
+ case CycleType.minutes5:
|
|
|
+ cycleMilliseconds *= 5;
|
|
|
+ break;
|
|
|
+ case CycleType.minutes30:
|
|
|
+ cycleMilliseconds *= 30;
|
|
|
+ break;
|
|
|
+ case CycleType.minutes60:
|
|
|
+ cycleMilliseconds *= 60;
|
|
|
+ break;
|
|
|
+ case CycleType.hours2:
|
|
|
+ cycleMilliseconds *= 2 * 60;
|
|
|
+ break;
|
|
|
+ case CycleType.Hours4:
|
|
|
+ cycleMilliseconds *= 4 * 60;
|
|
|
+ break;
|
|
|
+ case CycleType.days:
|
|
|
+ cycleMilliseconds *= 24 * 60;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ const diffTime = newTime.valueOf() - lastTime.valueOf(); // 计算时间差
|
|
|
+ // 判断时间差是否大于周期时间
|
|
|
+ if (diffTime > cycleMilliseconds) {
|
|
|
+ lastTime.add(cycleMilliseconds, 'ms');
|
|
|
+ times.push(lastTime.format('MM-DD HH:mm')); // 添加历史行情时间
|
|
|
+ datas.push([newPrice, newPrice, newPrice, newPrice]); // 添加历史行情数据
|
|
|
+ historyIndexs.push(lastIndex + 1); // 添加历史行情索引
|
|
|
+ } else {
|
|
|
+ const lastPrice = datas[lastIndex];
|
|
|
+ if (lastPrice[2] > newPrice) {
|
|
|
+ lastPrice[2] = newPrice; //更新最低价
|
|
|
+ }
|
|
|
+ if (lastPrice[3] < newPrice) {
|
|
|
+ lastPrice[3] = newPrice; //更新最高价
|
|
|
+ }
|
|
|
+ lastPrice[1] = newPrice; //更新收盘价
|
|
|
+ datas[lastIndex] = lastPrice; // 更新历史行情数据
|
|
|
+ }
|
|
|
+
|
|
|
+ // 延迟图表更新,减少卡顿
|
|
|
+ debounce(() => {
|
|
|
+ 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;
|
|
|
+
|
|
|
+ updateOptions();
|
|
|
+ }, 1000);
|
|
|
+ };
|
|
|
+
|
|
|
+ watch(
|
|
|
+ () => props.quoteData.last,
|
|
|
+ () => {
|
|
|
+ if (!loading.value) {
|
|
|
+ updateChartData();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
// 监听周期选择变化
|
|
|
watchEffect(() => {
|
|
|
loading.value = true;
|
|
|
const params: QueryHistoryDatas = {
|
|
|
cycleType: props.cycleType,
|
|
|
goodsCode: props.quoteData.goodscode,
|
|
|
- isAsc: true,
|
|
|
+ count: 1440,
|
|
|
};
|
|
|
// 查询K线数据
|
|
|
queryHistoryDatas(params).then((res) => {
|
|
|
- chartData.value = handleData(res);
|
|
|
+ // 日期升序排序
|
|
|
+ const kdata = res.sort((a, b) => moment(a.ts).valueOf() - moment(b.ts).valueOf());
|
|
|
+ handleData(kdata);
|
|
|
initOptions();
|
|
|
});
|
|
|
});
|
|
|
|
|
|
- watch(
|
|
|
- () => props.quoteData.last,
|
|
|
- (val) => {
|
|
|
- if (!loading.value) {
|
|
|
- console.log('lastTime', props.quoteData.lasttime);
|
|
|
- const { datas, times, ma5, ma10, ma15 } = chartData.value,
|
|
|
- lastTime = moment(times[times.length - 1]), // 行情最后时间
|
|
|
- newTime = moment(props.quoteData.lasttime); // 实时最新时间
|
|
|
-
|
|
|
- let cycleMilliseconds = 60 * 1000; // 周期毫秒数
|
|
|
-
|
|
|
- switch (props.cycleType) {
|
|
|
- case CycleType.minutes5:
|
|
|
- cycleMilliseconds *= 5;
|
|
|
- break;
|
|
|
- case CycleType.minutes30:
|
|
|
- cycleMilliseconds *= 30;
|
|
|
- break;
|
|
|
- case CycleType.minutes60:
|
|
|
- cycleMilliseconds *= 60;
|
|
|
- break;
|
|
|
- case CycleType.hours2:
|
|
|
- cycleMilliseconds *= 2 * 60;
|
|
|
- break;
|
|
|
- case CycleType.Hours4:
|
|
|
- cycleMilliseconds *= 4 * 60;
|
|
|
- break;
|
|
|
- case CycleType.days:
|
|
|
- cycleMilliseconds *= 24 * 60;
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- const diffTime = newTime.valueOf() - lastTime.valueOf(); // 计算时间差
|
|
|
- // 判断时间差是否大于周期时间
|
|
|
- if (diffTime > cycleMilliseconds) {
|
|
|
- lastTime.add(cycleMilliseconds, 'ms');
|
|
|
- times.push(lastTime.format());
|
|
|
- datas.push([val, val, val, val]);
|
|
|
- } else {
|
|
|
- const lastIndex = datas.length - 1,
|
|
|
- lastData = datas[lastIndex];
|
|
|
- if (lastData[2] > val) {
|
|
|
- lastData[2] = val; //更新最低价
|
|
|
- }
|
|
|
- if (lastData[3] < val) {
|
|
|
- lastData[3] = val; //更新最高价
|
|
|
- }
|
|
|
- lastData[1] = val; //更新收盘价
|
|
|
- datas[lastIndex] = lastData; // 更新数据
|
|
|
- }
|
|
|
-
|
|
|
- const _ma5 = calcLastMA(datas, 5),
|
|
|
- _ma10 = calcLastMA(datas, 10),
|
|
|
- _ma15 = calcLastMA(datas, 15);
|
|
|
-
|
|
|
- if (_ma5) ma5[ma5.length - 1] = _ma5;
|
|
|
- if (_ma10) ma10[ma10.length - 1] = _ma10;
|
|
|
- if (_ma15) ma15[ma15.length - 1] = _ma15;
|
|
|
-
|
|
|
- const { dif, dea, macd } = calcMACD(datas);
|
|
|
- chartData.value.dif = dif;
|
|
|
- chartData.value.dea = dea;
|
|
|
- chartData.value.macd = macd;
|
|
|
-
|
|
|
- updateOptions();
|
|
|
- }
|
|
|
- }
|
|
|
- );
|
|
|
-
|
|
|
return {
|
|
|
loading,
|
|
|
options,
|