dataset.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import { reactive } from 'vue'
  2. import { QueryHistoryDatasRsp } from '@/services/go/quote/interface'
  3. import { EchartsDataset, Candlestick, MACD } from './interface'
  4. import moment from 'moment'
  5. export function useDataset() {
  6. const dataset = reactive<EchartsDataset>({
  7. invalid: [],
  8. candlestick: {
  9. dimensions: ['date', 'open', 'close', 'lowest', 'highest', 'ma5', 'ma10', 'ma15'],
  10. source: [],
  11. },
  12. macd: {
  13. dimensions: ['date', 'macd', 'dif', 'dea'],
  14. source: [],
  15. },
  16. vol: {
  17. dimensions: ['date', 'vol'],
  18. source: [],
  19. },
  20. kdj: {
  21. dimensions: ['date', 'k', 'd', 'j'],
  22. source: [],
  23. },
  24. cci: {
  25. dimensions: ['date', 'cci'],
  26. source: [],
  27. }
  28. })
  29. // 清空数据
  30. const clearData = () => {
  31. dataset.invalid = [];
  32. dataset.candlestick.source = [];
  33. dataset.macd.source = [];
  34. dataset.vol.source = [];
  35. dataset.kdj.source = [];
  36. dataset.cci.source = [];
  37. }
  38. // 处理行情数据
  39. const handleData = (rawData: QueryHistoryDatasRsp[], onReady?: () => void) => {
  40. for (let i = 0; i < rawData.length; i++) {
  41. const { o, c, h, l, ts, f, tv } = rawData[i];
  42. const date = moment(ts).format('YYYY-MM-DD HH:mm:ss');
  43. if (f) dataset.invalid.push(i); // 添加补充数据的索引位置
  44. dataset.candlestick.source.push({
  45. date,
  46. open: o,
  47. close: c,
  48. lowest: l,
  49. highest: h,
  50. ma5: '-',
  51. ma10: '-',
  52. ma15: '-',
  53. })
  54. dataset.macd.source.push({
  55. date,
  56. ema12: 0,
  57. ema26: 0,
  58. dif: 0,
  59. dea: 0,
  60. macd: 0,
  61. })
  62. dataset.vol.source.push({
  63. date,
  64. vol: tv,
  65. })
  66. dataset.kdj.source.push({
  67. date,
  68. k: '-',
  69. d: '-',
  70. j: '-'
  71. })
  72. dataset.cci.source.push({
  73. date,
  74. cci: '-',
  75. })
  76. }
  77. // 计算各种指标
  78. calcIndicator();
  79. onReady && onReady();
  80. }
  81. // 计算MA
  82. const calcMA = (count: 5 | 10 | 15, startIndex = 0) => {
  83. const key: keyof Candlestick = count === 5 ? 'ma5' : count === 10 ? 'ma10' : 'ma15';
  84. const candlestickSource = dataset.candlestick.source;
  85. for (let i = startIndex; i < candlestickSource.length; i++) {
  86. // 判断是否补充数据
  87. if (dataset.invalid.includes(i)) {
  88. const value = i > 0 ? candlestickSource[i - 1][key] : '-'; // 如果存在,取上一条记录的MA值
  89. candlestickSource[i][key] = value;
  90. } else {
  91. let n = 0;
  92. const tmpList: Candlestick[] = [];
  93. for (let j = i; j >= 0; j--) {
  94. if (dataset.invalid.includes(j)) continue; // 如果是补充数据,跳过本次循环
  95. if (n === count) break; // 如果 n 等于计数,结束循环
  96. tmpList.push(candlestickSource[j]);
  97. n++;
  98. }
  99. if (n === count) {
  100. // 计算总价(收盘价总和)
  101. const total = tmpList.reduce((res, e) => res + e.close, 0);
  102. // 计算均价
  103. candlestickSource[i][key] = (total / count).toFixed(2);
  104. } else {
  105. candlestickSource[i][key] = '-';
  106. }
  107. }
  108. }
  109. }
  110. // 计算EMA
  111. const calcEMA = (count: 12 | 26, startIndex = 0) => {
  112. const key: keyof MACD = count === 12 ? 'ema12' : 'ema26';
  113. const macdSource = dataset.macd.source;
  114. const candlestickSource = dataset.candlestick.source;
  115. const a = 2 / (count + 1); // 平滑系数
  116. for (let i = startIndex; i < candlestickSource.length; i++) {
  117. const close = candlestickSource[i].close; // 收盘价
  118. if (i === 0) {
  119. macdSource[i][key] = close; // 初始EMA取收盘价
  120. } else {
  121. const prevEMA = macdSource[i - 1][key]; // 昨日EMA
  122. const value = a * close + (1 - a) * Number(prevEMA);
  123. macdSource[i][key] = value;
  124. }
  125. }
  126. }
  127. // 计算MACD
  128. const calcMACD = (startIndex = 0) => {
  129. // 先计算EMA
  130. calcEMA(12, startIndex); // EMA(12) = 2 ÷ 13 * 今日收盘价(12) + 11 ÷ 13 * 昨日EMA(12)
  131. calcEMA(26, startIndex);
  132. const macdSource = dataset.macd.source;
  133. for (let i = startIndex; i < macdSource.length; i++) {
  134. const { ema12, ema26 } = macdSource[i];
  135. const prevDEA = i > 0 ? macdSource[i - 1].dea : 0; // 昨日DEA
  136. const dif = ema12 - ema26; // DIF = 今日EMA(12) - 今日EMA(26)
  137. const dea = prevDEA * 8 / 10 + dif * 2 / 10; // DEA = 昨日DEA × 8 ÷ 10 + 今日DIF × 2 ÷ 10
  138. const macd = (dif - dea) * 2; // MACD = (DIFF - DEA) × 2
  139. macdSource[i].dif = Number(dif.toFixed(2));
  140. macdSource[i].dea = Number(dea.toFixed(2));
  141. macdSource[i].macd = Number(macd.toFixed(2));
  142. }
  143. }
  144. // 计算KDJ
  145. const calcKDJ = (startIndex = 0) => {
  146. const count = 9; // 以9日周期计算
  147. const candlestickSource = dataset.candlestick.source;
  148. const kdjSource = dataset.kdj.source;
  149. for (let i = startIndex; i < candlestickSource.length; i++) {
  150. let n = 0;
  151. const tmpList: Candlestick[] = [];
  152. for (let j = i; j >= 0; j--) {
  153. if (n === count) break; // 如果 n 等于计数,结束循环
  154. tmpList.push(candlestickSource[j]);
  155. n++;
  156. }
  157. if (n === count) {
  158. const close = candlestickSource[i].close, // 收盘价
  159. n9 = tmpList.map((item) => item.close),
  160. h9 = Math.max(...n9), // 9天内最高价
  161. l9 = Math.min(...n9); // 9天内最低价
  162. const rsv = (close - l9) / (h9 - l9) * 100, // RSV = (Ct-L9) ÷ (H9-L9) * 100
  163. prevK = Number(kdjSource[i - 1].k), // 昨日K值
  164. prevD = Number(kdjSource[i - 1].d); // 昨日D值
  165. const kValue = isNaN(prevK) ? 50 : prevK; // 若无昨日K值,则可用50来代替
  166. const dValue = isNaN(prevD) ? 50 : prevD; // 若无昨日D值,则可用50来代替
  167. const k = (2 / 3) * kValue + (1 / 3) * rsv, // K = 2 ÷ 3 × 昨日K值 + 1 ÷ 3 × RSV
  168. d = (2 / 3) * dValue + (1 / 3) * k, // D = 2 ÷ 3 × 昨日D值 + 1 ÷ 3 × K值
  169. j = 3 * k - 2 * d; // J = 3 * K值 - 2 * D值
  170. kdjSource[i].k = k.toFixed(2);
  171. kdjSource[i].d = d.toFixed(2);
  172. kdjSource[i].j = j.toFixed(2);
  173. }
  174. }
  175. }
  176. // 计算CCI
  177. const calcCCI = (startIndex = 0) => {
  178. const count = 14; // 以14日周期计算
  179. const candlestickSource = dataset.candlestick.source;
  180. const cciSource = dataset.cci.source;
  181. for (let i = startIndex; i < candlestickSource.length; i++) {
  182. let n = 0;
  183. const tmpList: Candlestick[] = [];
  184. for (let j = i; j >= 0; j--) {
  185. if (n === count) break; // 如果 n 等于计数,结束循环
  186. tmpList.push(candlestickSource[j]);
  187. n++;
  188. }
  189. if (n === count) {
  190. const calcTP = (item: Candlestick) => (item.highest + item.lowest + item.close) / 3; // TP = (最高价 + 最低价 + 收盘价) ÷ 3
  191. const tp = calcTP(candlestickSource[i]),
  192. ma = tmpList.reduce((res, e) => res + calcTP(e), 0) / count, // MA = 近N日TP的累计之和 ÷ N
  193. md = tmpList.reduce((res, e) => res + Math.abs(calcTP(e) - ma), 0) / count; // MD = 近N日TP的绝对值的累计之和 ÷ N
  194. const cci = (tp - ma) / (md * 0.015);
  195. cciSource[i].cci = cci.toFixed(2);
  196. }
  197. }
  198. }
  199. // 计算各项指标
  200. const calcIndicator = (startIndex = 0) => {
  201. calcMA(5, startIndex);
  202. calcMA(10, startIndex);
  203. calcMA(15, startIndex);
  204. calcMACD(startIndex);
  205. calcKDJ(startIndex);
  206. calcCCI(startIndex);
  207. }
  208. return {
  209. dataset,
  210. handleData,
  211. clearData,
  212. calcIndicator,
  213. }
  214. }