st-comp 0.0.45 → 0.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ import axios from 'axios'
2
+
3
+ export const getKlineDataApi = async(params) => {
4
+ const res = await axios({
5
+ method: 'post',
6
+ headers: {
7
+ token: '041fd377d5d5efc7e08e2ed5b61b0c8d',
8
+ },
9
+ url: 'http://invest.hzyotoy.com/common/qt/getSingleCycleSingleVariety',
10
+ data: params,
11
+ })
12
+ return res.data.body
13
+ }
@@ -0,0 +1,571 @@
1
+ import { stMath } from "st-func";
2
+
3
+ const defaultConfig = {
4
+ defaultShowCount: 200, // 默认展示条数
5
+ addCount: 2000, // 首屏加载条数,滚动加载条数
6
+ preLoadCount: 800, // 预加载数据条数(用于计算指标线)
7
+ maxShowCount: 5000, // 一屏最多渲染条数上限
8
+ gridLeft: 60, // echarts绘图左侧grid距离
9
+ gridRight: 60, // echarts绘图右侧grid距离
10
+ };
11
+
12
+ // 格式化配置
13
+ export const formatConfig = (config) => {
14
+ return {
15
+ ...defaultConfig,
16
+ ...config,
17
+ };
18
+ };
19
+
20
+ // DOM缩放监视器
21
+ class ResizeListener {
22
+ constructor(dom, callback) {
23
+ this.resizeRo = null;
24
+ this.isFirst = true;
25
+ this.init(dom, callback);
26
+ }
27
+ init(dom, callback) {
28
+ this.resizeRo = new ResizeObserver(() => {
29
+ if (this.isFirst) {
30
+ this.isFirst = null;
31
+ return;
32
+ }
33
+ callback();
34
+ });
35
+ this.resizeRo.observe(dom);
36
+ }
37
+ dispose() {
38
+ this.resizeRo.disconnect();
39
+ this.resizeRo = null;
40
+ }
41
+ }
42
+ export const resizeListener = (dom, callback) => {
43
+ return new ResizeListener(dom, callback);
44
+ };
45
+
46
+ // 防抖
47
+ export const debounce = (callBack, delay = 500, immediate = false) => {
48
+ let timer = null;
49
+ return function (...args) {
50
+ // 清除已有的定时器
51
+ clearTimeout(timer);
52
+ if (immediate) {
53
+ // 如果是立即执行模式,且为第一次触发,则执行函数
54
+ const runNow = !timer;
55
+ timer = setTimeout(() => {
56
+ timer = null;
57
+ }, delay);
58
+ if (runNow) {
59
+ callBack.apply(this, args);
60
+ }
61
+ } else {
62
+ // 非立即执行模式,延迟指定时间后再执行函数
63
+ timer = setTimeout(() => {
64
+ callBack.apply(this, args);
65
+ }, delay);
66
+ }
67
+ };
68
+ };
69
+
70
+ // ------------------生成绘图配置项函数------------------
71
+ // 预警线
72
+ export const getWarningLineGraphic = (data, mainChart) => {
73
+ // 获取基础的配置项信息
74
+ const mainChartOption = mainChart.getOption();
75
+ const gridLeft = mainChartOption.grid[0].left.split("px")[0];
76
+ const gridRight = mainChartOption.grid[0].right.split("px")[0];
77
+ // 生成配置项
78
+ const elements = data.reduce((result, item) => {
79
+ const { value, text, customInfo } = item;
80
+ let dragStart = 0; // 记录拖拽开始的位置
81
+ result.push({
82
+ type: "group",
83
+ name: "warningLine",
84
+ info: customInfo,
85
+ draggable: "vertical",
86
+ children: [
87
+ {
88
+ type: "line",
89
+ info: customInfo,
90
+ shape: {
91
+ x1: gridLeft, // 开始X坐标
92
+ y1: mainChart.convertToPixel({ yAxisIndex: 0 }, value), // 开始Y坐标
93
+ x2: mainChart.getWidth() - gridRight, // 结束X坐标
94
+ y2: mainChart.convertToPixel({ yAxisIndex: 0 }, value), // 结束Y坐标
95
+ },
96
+ style: {
97
+ stroke: "#FFF",
98
+ lineWidth: 1,
99
+ lineDash: [8, 4],
100
+ },
101
+ z: 10,
102
+ },
103
+ {
104
+ type: "text",
105
+ info: customInfo,
106
+ x: mainChart.getWidth() - gridRight,
107
+ y: mainChart.convertToPixel({ yAxisIndex: 0 }, value) - 5,
108
+ style: {
109
+ fill: "#FFF",
110
+ text,
111
+ stroke: "#000",
112
+ lineWidth: 1,
113
+ opacity: 1,
114
+ },
115
+ z: 10,
116
+ },
117
+ ],
118
+ // 事件:开始拖拽
119
+ ondragstart: (params) => {
120
+ dragStart = params.offsetY; // 记录拖拽开始坐标
121
+ },
122
+ // 事件:结束拖拽
123
+ ondragend: (params) => {
124
+ /**
125
+ * 之所以不直接使用dragEnd,是因为echarts拖拽会有偏差问题
126
+ * 需要使用偏移量才会更加准备,不然会出现画完线渲染位置和拖拽位置不一致
127
+ */
128
+ const dragEnd = params.offsetY; // 记录结束拖拽坐标
129
+ const dragInterval = dragEnd - dragStart; // 拖拽偏移量
130
+ if (dragInterval !== 0) {
131
+ // 计算总偏移量
132
+ const y = dragInterval + mainChart.convertToPixel({ yAxisIndex: 0 }, value);
133
+ // 计算偏移后对应的Y轴数据
134
+ const yAxisValue = mainChart.convertFromPixel({ yAxisIndex: 0 }, y);
135
+ console.log("拖拽结束,可以在这里发送请求,并重查额外画线数据后重新绘制");
136
+ }
137
+ },
138
+ });
139
+ return result;
140
+ }, []);
141
+ return elements;
142
+ };
143
+ // 持仓线
144
+ export const getPositionLineGraphic = (data, mainChart) => {
145
+ // 获取基础的配置项信息
146
+ const mainChartOption = mainChart.getOption();
147
+ const gridLeft = mainChartOption.grid[0].left.split("px")[0];
148
+ const gridRight = mainChartOption.grid[0].right.split("px")[0];
149
+ // 生成配置项
150
+ const elements = data.reduce((result, item) => {
151
+ const { value, text, customInfo } = item;
152
+ result.push({
153
+ type: "group",
154
+ name: "positionLine",
155
+ info: customInfo,
156
+ children: [
157
+ {
158
+ type: "line",
159
+ info: customInfo,
160
+ shape: {
161
+ x1: gridLeft, // 开始X坐标
162
+ y1: mainChart.convertToPixel({ yAxisIndex: 0 }, value), // 开始Y坐标
163
+ x2: mainChart.getWidth() - gridRight, // 结束X坐标
164
+ y2: mainChart.convertToPixel({ yAxisIndex: 0 }, value), // 结束Y坐标
165
+ },
166
+ style: {
167
+ stroke: "#e45d07",
168
+ lineWidth: 1,
169
+ },
170
+ z: 10,
171
+ },
172
+ {
173
+ // 保证文字居中,外层包裹一层group用于定位
174
+ type: "group",
175
+ info: customInfo,
176
+ x: mainChart.getWidth() / 2,
177
+ y: mainChart.convertToPixel({ yAxisIndex: 0 }, value) - 5,
178
+ children: [
179
+ {
180
+ type: "text",
181
+ left: "center",
182
+ style: {
183
+ fill: "#FFF",
184
+ text,
185
+ stroke: "#000",
186
+ lineWidth: 1,
187
+ opacity: 0, // 暂时隐藏
188
+ },
189
+ z: 10,
190
+ },
191
+ ],
192
+ },
193
+ ],
194
+ // 事件:鼠标滑入
195
+ onmouseover: (params) => {
196
+ let groupItem = null;
197
+ if (params.target.type === "line") {
198
+ groupItem = params.target.parent;
199
+ } else if (params.target.type === "tspan") {
200
+ groupItem = params.target.parent.parent.parent;
201
+ }
202
+ // 展示文本
203
+ groupItem.children()[1].children()[0].animate("style", false).when(200, { opacity: 1 }).start();
204
+ },
205
+ // 事件:鼠标滑出
206
+ onmouseout: (params) => {
207
+ let groupItem = null;
208
+ if (params.target.type === "line") {
209
+ groupItem = params.target.parent;
210
+ } else if (params.target.type === "tspan") {
211
+ groupItem = params.target.parent.parent.parent;
212
+ }
213
+ // 隐藏文本
214
+ groupItem.children()[1].children()[0].animate("style", false).when(200, { opacity: 0 }).start();
215
+ },
216
+ });
217
+ return result;
218
+ }, []);
219
+ return elements;
220
+ };
221
+ // 条件单线
222
+ export const getConditionLineGraphic = (data, mainChart) => {
223
+ // 获取基础的配置项信息
224
+ const mainChartOption = mainChart.getOption();
225
+ const gridLeft = mainChartOption.grid[0].left.split("px")[0];
226
+ const gridRight = mainChartOption.grid[0].right.split("px")[0];
227
+ // 键盘事件函数
228
+ let handleKeyDown = null;
229
+ let handleKeyUp = null;
230
+ // 工具函数: 控制辅线内容展示与否
231
+ const handleLineShow = (groupItem, show) => {
232
+ // 条件单辅线-止盈线内容
233
+ groupItem.children()[2]?.animate("style", false).when(200, { opacity: ~~show }).start();
234
+ groupItem.children()[3].children()[0]?.animate("style", false).when(200, { opacity: ~~show }).start();
235
+ // 条件单辅线-止损线内容
236
+ groupItem.children()[4]?.animate("style", false).when(200, { opacity: ~~show }).start();
237
+ groupItem.children()[5].children()[0]?.animate("style", false).when(200, { opacity: ~~show }).start();
238
+ };
239
+ const elements = data.reduce((result, item) => {
240
+ const { value, text, profitValue, profitText, lossValue, lossText, customInfo } = item;
241
+ result.push({
242
+ type: "group",
243
+ name: "conditionLine",
244
+ info: customInfo,
245
+ children: [
246
+ // 条件单主线
247
+ {
248
+ type: "line",
249
+ info: customInfo,
250
+ shape: {
251
+ x1: gridLeft, // 开始X坐标
252
+ y1: mainChart.convertToPixel({ yAxisIndex: 0 }, value), // 开始Y坐标
253
+ x2: mainChart.getWidth() - gridRight, // 结束X坐标
254
+ y2: mainChart.convertToPixel({ yAxisIndex: 0 }, value), // 结束Y坐标
255
+ },
256
+ style: {
257
+ stroke: "#FFF",
258
+ lineWidth: 1,
259
+ },
260
+ z: 10,
261
+ },
262
+ {
263
+ // 保证文字居中,外层包裹一层group用于定位
264
+ type: "group",
265
+ info: customInfo,
266
+ x: mainChart.getWidth() / 2,
267
+ y: mainChart.convertToPixel({ yAxisIndex: 0 }, value) - 5,
268
+ children: [
269
+ {
270
+ type: "text",
271
+ left: "center",
272
+ style: {
273
+ fill: "#FFF",
274
+ text,
275
+ stroke: "#000",
276
+ lineWidth: 1,
277
+ },
278
+ z: 10,
279
+ },
280
+ ],
281
+ },
282
+ // 条件单辅线-止盈线
283
+ {
284
+ type: "line",
285
+ info: customInfo,
286
+ shape: {
287
+ x1: gridLeft, // 开始X坐标
288
+ y1: mainChart.convertToPixel({ yAxisIndex: 0 }, profitValue), // 开始Y坐标
289
+ x2: mainChart.getWidth() - gridRight, // 结束X坐标
290
+ y2: mainChart.convertToPixel({ yAxisIndex: 0 }, profitValue), // 结束Y坐标
291
+ },
292
+ style: {
293
+ stroke: "#b71e44",
294
+ lineWidth: 1,
295
+ lineDash: [8, 4],
296
+ opacity: 0,
297
+ },
298
+ z: 10,
299
+ },
300
+ {
301
+ // 保证文字居中,外层包裹一层group用于定位
302
+ type: "group",
303
+ info: customInfo,
304
+ x: mainChart.getWidth() / 2,
305
+ y: mainChart.convertToPixel({ yAxisIndex: 0 }, profitValue) - 5,
306
+ children: [
307
+ {
308
+ type: "text",
309
+ left: "center",
310
+ style: {
311
+ fill: "#FFF",
312
+ text: profitText,
313
+ stroke: "#000",
314
+ lineWidth: 1,
315
+ opacity: 0, // 暂时隐藏
316
+ },
317
+ z: 10,
318
+ },
319
+ ],
320
+ },
321
+ // 条件单辅线-止损线
322
+ {
323
+ type: "line",
324
+ info: customInfo,
325
+ shape: {
326
+ x1: gridLeft, // 开始X坐标
327
+ y1: mainChart.convertToPixel({ yAxisIndex: 0 }, lossValue), // 开始Y坐标
328
+ x2: mainChart.getWidth() - gridRight, // 结束X坐标
329
+ y2: mainChart.convertToPixel({ yAxisIndex: 0 }, lossValue), // 结束Y坐标
330
+ },
331
+ style: {
332
+ stroke: "#749b66",
333
+ lineWidth: 1,
334
+ lineDash: [8, 4],
335
+ opacity: 0,
336
+ },
337
+ z: 10,
338
+ },
339
+ {
340
+ // 保证文字居中,外层包裹一层group用于定位
341
+ type: "group",
342
+ info: customInfo,
343
+ x: mainChart.getWidth() / 2,
344
+ y: mainChart.convertToPixel({ yAxisIndex: 0 }, lossValue) - 5,
345
+ children: [
346
+ {
347
+ type: "text",
348
+ left: "center",
349
+ style: {
350
+ fill: "#FFF",
351
+ text: lossText,
352
+ stroke: "#000",
353
+ lineWidth: 1,
354
+ opacity: 0, // 暂时隐藏
355
+ },
356
+ z: 10,
357
+ },
358
+ ],
359
+ },
360
+ ],
361
+ // 事件:鼠标滑入
362
+ onmouseover: (params) => {
363
+ let groupItem = null;
364
+ if (params.target.type === "line") {
365
+ groupItem = params.target.parent;
366
+ } else if (params.target.type === "tspan") {
367
+ groupItem = params.target.parent.parent.parent;
368
+ }
369
+ // 开始监视Alt键
370
+ handleKeyDown = (e) => {
371
+ e.preventDefault();
372
+ if (e.code === "AltLeft" || e.code === "AltRight") {
373
+ handleLineShow(groupItem, true);
374
+ }
375
+ };
376
+ handleKeyUp = (e) => {
377
+ if (e.code === "AltLeft" || e.code === "AltRight") {
378
+ handleLineShow(groupItem, false);
379
+ }
380
+ };
381
+ window.addEventListener("keydown", handleKeyDown);
382
+ window.addEventListener("keyup", handleKeyUp);
383
+ },
384
+ // 事件:鼠标滑出
385
+ onmouseout: (params) => {
386
+ let groupItem = null;
387
+ if (params.target.type === "line") {
388
+ groupItem = params.target.parent;
389
+ } else if (params.target.type === "tspan") {
390
+ groupItem = params.target.parent.parent.parent;
391
+ }
392
+ // 取消监视Alt键
393
+ handleLineShow(groupItem, false);
394
+ window.removeEventListener("keydown", handleKeyDown);
395
+ window.removeEventListener("keyup", handleKeyUp);
396
+ },
397
+ });
398
+ return result;
399
+ }, []);
400
+ return elements;
401
+ };
402
+ // 相关度线
403
+ export const getRelevanceLineSeries = (data) => {
404
+ return {
405
+ type: "line",
406
+ name: "relevance",
407
+ data,
408
+ symbol: "none",
409
+ yAxisIndex: 1,
410
+ connectNulls: true,
411
+ itemStyle: {
412
+ color: "#FFF",
413
+ },
414
+ lineStyle: {
415
+ width: 1,
416
+ },
417
+ label: {
418
+ show: true, // 显示文字
419
+ position: "top", // 文字位置
420
+ formatter: (params) => {
421
+ return `相关度: ${params.data[1]}`;
422
+ },
423
+ textStyle: {
424
+ color: "#FFF", // 文字颜色
425
+ fontSize: 14, // 文字字体大小
426
+ },
427
+ },
428
+ };
429
+ };
430
+ // 分布统计线
431
+ export const getDistributionLineGraphic = (data, mainChart) => {
432
+ // 获取基础的配置项信息
433
+ const mainChartOption = mainChart.getOption();
434
+ const gridLeft = ~~mainChartOption.grid[0].left.split("px")[0];
435
+ const gridRight = ~~mainChartOption.grid[0].right.split("px")[0];
436
+ // 获取当屏K线数据
437
+ const { startValue, endValue } = mainChartOption.dataZoom[0];
438
+ const nowScreenKlineData = data.slice(startValue, endValue + 1);
439
+ // 根据当屏K线数据计算不同收盘价出现的次数
440
+ const countMap = nowScreenKlineData.reduce((result, item) => {
441
+ const closePrice = item[4];
442
+ if (result[closePrice]) {
443
+ result[closePrice] += 1;
444
+ } else {
445
+ result[closePrice] = 1;
446
+ }
447
+ return result;
448
+ }, {});
449
+ // -----------计算相关展示数值-----------
450
+ // (1).均值
451
+ const meanValue =
452
+ nowScreenKlineData.reduce((result, item) => {
453
+ return result + item[4];
454
+ }, 0) / nowScreenKlineData.length;
455
+ // (2).距均值
456
+ const disMeanValue = nowScreenKlineData[nowScreenKlineData.length - 1][4] - meanValue;
457
+ // (3).中值
458
+ const medianValue = percentToValue(nowScreenKlineData, 1, 50);
459
+ // (4).标准差
460
+ const standardDeviationValue = calculateStandardDeviation(nowScreenKlineData);
461
+ // (5).距均值/标准差
462
+ const disMeanSDValue = standardDeviationValue ? disMeanValue / standardDeviationValue : 0;
463
+ // (6).百分位
464
+ const percentileValue = valueToPercent(nowScreenKlineData, 1, nowScreenKlineData[nowScreenKlineData.length - 1][4]);
465
+ // -------------------------------------
466
+ const countLine = Object.keys(countMap).map((key) => {
467
+ return {
468
+ type: "line",
469
+ shape: {
470
+ x1: mainChart.getWidth() - gridRight - 100 * countMap[key],
471
+ y1: mainChart.convertToPixel({ yAxisIndex: 0 }, key),
472
+ x2: mainChart.getWidth() - gridRight,
473
+ y2: mainChart.convertToPixel({ yAxisIndex: 0 }, key),
474
+ },
475
+ style: {
476
+ stroke: "rgb(124,124,124)",
477
+ lineWidth: 1,
478
+ },
479
+ z: 10,
480
+ };
481
+ });
482
+ const countText = {
483
+ type: "text",
484
+ x: gridLeft + 6,
485
+ y: mainChart.getHeight() - 50,
486
+ style: {
487
+ fill: "#FFF",
488
+ text: `均值: ${stMath.formatValue(meanValue)}\xa0\xa0\xa0\xa0距均值: ${stMath.formatValue(
489
+ disMeanValue
490
+ )}\xa0\xa0\xa0\xa0中值: ${stMath.formatValue(medianValue)}\xa0\xa0\xa0\xa0标准差: ${stMath.formatValue(
491
+ standardDeviationValue
492
+ )}\xa0\xa0\xa0\xa0距均值/标准差: ${stMath.formatValue(
493
+ disMeanSDValue
494
+ )}\xa0\xa0\xa0\xa0百分位: ${stMath.formatValue(percentileValue)}%`,
495
+ lineWidth: 1,
496
+ opacity: 1,
497
+ },
498
+ z: 10,
499
+ };
500
+ const elements = {
501
+ type: "group",
502
+ name: "distribution",
503
+ draggable: false,
504
+ children: [...countLine, countText],
505
+ };
506
+ return elements;
507
+ };
508
+
509
+ // ------------------计算函数------------------
510
+ // 根据值计算分位
511
+ const valueToPercent = (data, dataIndex, value) => {
512
+ const { round, add, subtract, multiply, divide } = stMath;
513
+ const sortData = data.map((i) => i[dataIndex]).sort((a, b) => a - b);
514
+ let position = 0;
515
+ for (let i = 0; i < sortData.length; i++) {
516
+ if (sortData[i] === value) {
517
+ // 位置为整数
518
+ position = i;
519
+ break;
520
+ } else if (sortData[i] >= value) {
521
+ // 位置为小数
522
+ position = add(i, divide(subtract(sortData[i + 1], sortData[i]), sortData[i]));
523
+ break;
524
+ }
525
+ }
526
+ return round(divide(multiply(position, 100), data.length + 1));
527
+ };
528
+ // 根据分位计算值
529
+ const percentToValue = (data, dataIndex, percent) => {
530
+ const { round, add, subtract, multiply, divide } = stMath;
531
+ const sortData = data.map((i) => i[dataIndex]).sort((a, b) => a - b);
532
+ const position = multiply(data.length + 1, divide(percent, 100));
533
+ const positionInterger = Math.floor(position);
534
+ const positionDecimal = subtract(position, positionInterger);
535
+ if (positionDecimal === 0) {
536
+ // 位置为整数
537
+ return round(sortData[position]);
538
+ } else {
539
+ // 位置为小数
540
+ return round(
541
+ add(
542
+ sortData[positionInterger],
543
+ multiply(
544
+ subtract(
545
+ sortData[positionInterger >= data.length - 1 ? positionInterger : positionInterger + 1],
546
+ sortData[positionInterger]
547
+ ),
548
+ positionDecimal
549
+ )
550
+ )
551
+ );
552
+ }
553
+ };
554
+ // 计算标准差
555
+ const calculateStandardDeviation = (data) => {
556
+ const numbers = data.map((i) => i[4]);
557
+ const n = numbers.length;
558
+ let sum = 0;
559
+ let squaredSum = 0;
560
+ // 计算平均值(均值)
561
+ for (const num of numbers) {
562
+ sum += num;
563
+ }
564
+ const mean = sum / n;
565
+ // 计算平方和
566
+ for (const num of numbers) {
567
+ squaredSum += Math.pow(num - mean, 2);
568
+ }
569
+ // 计算并返回标准差
570
+ return Math.sqrt(squaredSum / n);
571
+ };