import { reactive } from 'vue' import { QueryHistoryDatasRsp } from '@/services/go/quote/interface' import { EchartsDataset, Candlestick, MACD } from './interface' import moment from 'moment' export function useDataset() { const dataset = reactive({ invalid: [], candlestick: { dimensions: ['date', 'open', 'close', 'lowest', 'highest', 'ma5', 'ma10', 'ma15'], source: [], }, macd: { dimensions: ['date', 'macd', 'dif', 'dea'], source: [], }, vol: { dimensions: ['date', 'vol'], source: [], }, kdj: { dimensions: ['date', 'k', 'd', 'j'], source: [], }, cci: { dimensions: ['date', 'cci'], source: [], } }) // 清空数据 const clearData = () => { dataset.invalid = []; dataset.candlestick.source = []; dataset.macd.source = []; dataset.vol.source = []; dataset.kdj.source = []; dataset.cci.source = []; } // 处理行情数据 const handleData = (rawData: QueryHistoryDatasRsp[], onReady?: () => void) => { for (let i = 0; i < rawData.length; i++) { const { o, c, h, l, ts, f, tv } = rawData[i]; const date = moment(ts).format('YYYY-MM-DD HH:mm:ss'); if (f) dataset.invalid.push(i); // 添加补充数据的索引位置 dataset.candlestick.source.push({ date, open: o, close: c, lowest: l, highest: h, ma5: '-', ma10: '-', ma15: '-', }) dataset.macd.source.push({ date, ema12: 0, ema26: 0, dif: 0, dea: 0, macd: 0, }) dataset.vol.source.push({ date, vol: tv, }) dataset.kdj.source.push({ date, k: '-', d: '-', j: '-' }) dataset.cci.source.push({ date, cci: '-', }) } // 计算各种指标 calcIndicator(); onReady && onReady(); } // 计算MA const calcMA = (count: 5 | 10 | 15, startIndex = 0) => { const key: keyof Candlestick = count === 5 ? 'ma5' : count === 10 ? 'ma10' : 'ma15'; const candlestickSource = dataset.candlestick.source; for (let i = startIndex; i < candlestickSource.length; i++) { // 判断是否补充数据 if (dataset.invalid.includes(i)) { const value = i > 0 ? candlestickSource[i - 1][key] : '-'; // 如果存在,取上一条记录的MA值 candlestickSource[i][key] = value; } else { let n = 0; const tmpList: Candlestick[] = []; for (let j = i; j >= 0; j--) { if (dataset.invalid.includes(j)) continue; // 如果是补充数据,跳过本次循环 if (n === count) break; // 如果 n 等于计数,结束循环 tmpList.push(candlestickSource[j]); n++; } if (n === count) { // 计算总价(收盘价总和) const total = tmpList.reduce((res, e) => res + e.close, 0); // 计算均价 candlestickSource[i][key] = (total / count).toFixed(2); } else { candlestickSource[i][key] = '-'; } } } } // 计算EMA const calcEMA = (count: 12 | 26, startIndex = 0) => { const key: keyof MACD = count === 12 ? 'ema12' : 'ema26'; const macdSource = dataset.macd.source; const candlestickSource = dataset.candlestick.source; const a = 2 / (count + 1); // 平滑系数 for (let i = startIndex; i < candlestickSource.length; i++) { const close = candlestickSource[i].close; // 收盘价 if (i === 0) { macdSource[i][key] = close; // 初始EMA取收盘价 } else { const prevEMA = macdSource[i - 1][key]; // 昨日EMA const value = a * close + (1 - a) * Number(prevEMA); macdSource[i][key] = value; } } } // 计算MACD const calcMACD = (startIndex = 0) => { // 先计算EMA calcEMA(12, startIndex); // EMA(12) = 2 ÷ 13 * 今日收盘价(12) + 11 ÷ 13 * 昨日EMA(12) calcEMA(26, startIndex); const macdSource = dataset.macd.source; for (let i = startIndex; i < macdSource.length; i++) { const { ema12, ema26 } = macdSource[i]; const prevDEA = i > 0 ? macdSource[i - 1].dea : 0; // 昨日DEA const dif = ema12 - ema26; // DIF = 今日EMA(12) - 今日EMA(26) const dea = prevDEA * 8 / 10 + dif * 2 / 10; // DEA = 昨日DEA × 8 ÷ 10 + 今日DIF × 2 ÷ 10 const macd = (dif - dea) * 2; // MACD = (DIFF - DEA) × 2 macdSource[i].dif = Number(dif.toFixed(2)); macdSource[i].dea = Number(dea.toFixed(2)); macdSource[i].macd = Number(macd.toFixed(2)); } } // 计算KDJ const calcKDJ = (startIndex = 0) => { const count = 9; // 以9日周期计算 const candlestickSource = dataset.candlestick.source; const kdjSource = dataset.kdj.source; for (let i = startIndex; i < candlestickSource.length; i++) { let n = 0; const tmpList: Candlestick[] = []; for (let j = i; j >= 0; j--) { if (n === count) break; // 如果 n 等于计数,结束循环 tmpList.push(candlestickSource[j]); n++; } if (n === count) { const close = candlestickSource[i].close, // 收盘价 n9 = tmpList.map((item) => item.close), h9 = Math.max(...n9), // 9天内最高价 l9 = Math.min(...n9); // 9天内最低价 const rsv = (close - l9) / (h9 - l9) * 100, // RSV = (Ct-L9) ÷ (H9-L9) * 100 prevK = Number(kdjSource[i - 1].k), // 昨日K值 prevD = Number(kdjSource[i - 1].d); // 昨日D值 const kValue = isNaN(prevK) ? 50 : prevK; // 若无昨日K值,则可用50来代替 const dValue = isNaN(prevD) ? 50 : prevD; // 若无昨日D值,则可用50来代替 const k = (2 / 3) * kValue + (1 / 3) * rsv, // K = 2 ÷ 3 × 昨日K值 + 1 ÷ 3 × RSV d = (2 / 3) * dValue + (1 / 3) * k, // D = 2 ÷ 3 × 昨日D值 + 1 ÷ 3 × K值 j = 3 * k - 2 * d; // J = 3 * K值 - 2 * D值 kdjSource[i].k = k.toFixed(2); kdjSource[i].d = d.toFixed(2); kdjSource[i].j = j.toFixed(2); } } } // 计算CCI const calcCCI = (startIndex = 0) => { const count = 14; // 以14日周期计算 const candlestickSource = dataset.candlestick.source; const cciSource = dataset.cci.source; for (let i = startIndex; i < candlestickSource.length; i++) { let n = 0; const tmpList: Candlestick[] = []; for (let j = i; j >= 0; j--) { if (n === count) break; // 如果 n 等于计数,结束循环 tmpList.push(candlestickSource[j]); n++; } if (n === count) { const calcTP = (item: Candlestick) => (item.highest + item.lowest + item.close) / 3; // TP = (最高价 + 最低价 + 收盘价) ÷ 3 const tp = calcTP(candlestickSource[i]), ma = tmpList.reduce((res, e) => res + calcTP(e), 0) / count, // MA = 近N日TP的累计之和 ÷ N md = tmpList.reduce((res, e) => res + Math.abs(calcTP(e) - ma), 0) / count; // MD = 近N日TP的绝对值的累计之和 ÷ N const cci = (tp - ma) / (md * 0.015); cciSource[i].cci = cci.toFixed(2); } } } // 计算各项指标 const calcIndicator = (startIndex = 0) => { calcMA(5, startIndex); calcMA(10, startIndex); calcMA(15, startIndex); calcMACD(startIndex); calcKDJ(startIndex); calcCCI(startIndex); } return { dataset, handleData, clearData, calcIndicator, } }