@oanda/labs-crowd-view-widget 1.0.45 → 1.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.
Files changed (125) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/dist/main/CrowdViewWidget/Main.js +3 -1
  3. package/dist/main/CrowdViewWidget/Main.js.map +1 -1
  4. package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js +16 -5
  5. package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
  6. package/dist/main/CrowdViewWidget/components/Chart/index.js +4 -4
  7. package/dist/main/CrowdViewWidget/components/Chart/index.js.map +1 -1
  8. package/dist/main/CrowdViewWidget/components/Chart/types.js.map +1 -1
  9. package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js +17 -101
  10. package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
  11. package/dist/main/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js +37 -0
  12. package/dist/main/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js.map +1 -0
  13. package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js +19 -4
  14. package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
  15. package/dist/main/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js +14 -0
  16. package/dist/main/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js.map +1 -0
  17. package/dist/main/CrowdViewWidget/components/Chart/utils/index.js +83 -0
  18. package/dist/main/CrowdViewWidget/components/Chart/utils/index.js.map +1 -0
  19. package/dist/main/CrowdViewWidget/components/Chart/utils/processBuckets.js +29 -0
  20. package/dist/main/CrowdViewWidget/components/Chart/utils/processBuckets.js.map +1 -0
  21. package/dist/main/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js +23 -0
  22. package/dist/main/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js.map +1 -0
  23. package/dist/main/CrowdViewWidget/components/Chart/utils/processPriceCandles.js +43 -0
  24. package/dist/main/CrowdViewWidget/components/Chart/utils/processPriceCandles.js.map +1 -0
  25. package/dist/main/CrowdViewWidget/components/Chart/utils/validateData.js +23 -0
  26. package/dist/main/CrowdViewWidget/components/Chart/utils/validateData.js.map +1 -0
  27. package/dist/main/CrowdViewWidget/components/Legend/Legend.js +5 -3
  28. package/dist/main/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  29. package/dist/main/CrowdViewWidget/constants.js +104 -5
  30. package/dist/main/CrowdViewWidget/constants.js.map +1 -1
  31. package/dist/main/CrowdViewWidget/selectConfig.js +18 -60
  32. package/dist/main/CrowdViewWidget/selectConfig.js.map +1 -1
  33. package/dist/main/CrowdViewWidget/types.js +20 -0
  34. package/dist/main/CrowdViewWidget/types.js.map +1 -1
  35. package/dist/main/translations/sources/en.json +21 -16
  36. package/dist/module/CrowdViewWidget/Main.js +3 -1
  37. package/dist/module/CrowdViewWidget/Main.js.map +1 -1
  38. package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js +16 -5
  39. package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
  40. package/dist/module/CrowdViewWidget/components/Chart/index.js +1 -1
  41. package/dist/module/CrowdViewWidget/components/Chart/index.js.map +1 -1
  42. package/dist/module/CrowdViewWidget/components/Chart/types.js.map +1 -1
  43. package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js +13 -97
  44. package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
  45. package/dist/module/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js +29 -0
  46. package/dist/module/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js.map +1 -0
  47. package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js +20 -5
  48. package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
  49. package/dist/module/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js +7 -0
  50. package/dist/module/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js.map +1 -0
  51. package/dist/module/CrowdViewWidget/components/Chart/utils/index.js +8 -0
  52. package/dist/module/CrowdViewWidget/components/Chart/utils/index.js.map +1 -0
  53. package/dist/module/CrowdViewWidget/components/Chart/utils/processBuckets.js +22 -0
  54. package/dist/module/CrowdViewWidget/components/Chart/utils/processBuckets.js.map +1 -0
  55. package/dist/module/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js +16 -0
  56. package/dist/module/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js.map +1 -0
  57. package/dist/module/CrowdViewWidget/components/Chart/utils/processPriceCandles.js +36 -0
  58. package/dist/module/CrowdViewWidget/components/Chart/utils/processPriceCandles.js.map +1 -0
  59. package/dist/module/CrowdViewWidget/components/Chart/utils/validateData.js +16 -0
  60. package/dist/module/CrowdViewWidget/components/Chart/utils/validateData.js.map +1 -0
  61. package/dist/module/CrowdViewWidget/components/Legend/Legend.js +5 -3
  62. package/dist/module/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  63. package/dist/module/CrowdViewWidget/constants.js +103 -4
  64. package/dist/module/CrowdViewWidget/constants.js.map +1 -1
  65. package/dist/module/CrowdViewWidget/selectConfig.js +3 -45
  66. package/dist/module/CrowdViewWidget/selectConfig.js.map +1 -1
  67. package/dist/module/CrowdViewWidget/types.js +19 -1
  68. package/dist/module/CrowdViewWidget/types.js.map +1 -1
  69. package/dist/module/translations/sources/en.json +21 -16
  70. package/dist/types/CrowdViewWidget/components/Chart/index.d.ts +1 -1
  71. package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +11 -7
  72. package/dist/types/CrowdViewWidget/components/Chart/utils/aggregateBuckets.d.ts +2 -0
  73. package/dist/types/CrowdViewWidget/components/Chart/utils/chartUtils.d.ts +11 -6
  74. package/dist/types/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.d.ts +3 -0
  75. package/dist/types/CrowdViewWidget/components/Chart/utils/index.d.ts +7 -0
  76. package/dist/types/CrowdViewWidget/components/Chart/utils/processBuckets.d.ts +3 -0
  77. package/dist/types/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.d.ts +8 -0
  78. package/dist/types/CrowdViewWidget/components/Chart/utils/processPriceCandles.d.ts +27 -0
  79. package/dist/types/CrowdViewWidget/components/Chart/utils/validateData.d.ts +2 -0
  80. package/dist/types/CrowdViewWidget/components/Legend/Legend.d.ts +3 -1
  81. package/dist/types/CrowdViewWidget/constants.d.ts +11 -4
  82. package/dist/types/CrowdViewWidget/selectConfig.d.ts +2 -2
  83. package/dist/types/CrowdViewWidget/types.d.ts +18 -1
  84. package/dist/types/CrowdViewWidget/utils/instrumentUtils.d.ts +1 -4
  85. package/package.json +4 -3
  86. package/src/CrowdViewWidget/Main.tsx +2 -3
  87. package/src/CrowdViewWidget/components/Chart/chartOptions.ts +35 -25
  88. package/src/CrowdViewWidget/components/Chart/index.ts +1 -1
  89. package/src/CrowdViewWidget/components/Chart/types.ts +12 -4
  90. package/src/CrowdViewWidget/components/Chart/useCrowdViewData.ts +30 -154
  91. package/src/CrowdViewWidget/components/Chart/utils/aggregateBuckets.ts +44 -0
  92. package/src/CrowdViewWidget/components/Chart/utils/chartUtils.ts +37 -12
  93. package/src/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.ts +13 -0
  94. package/src/CrowdViewWidget/components/Chart/utils/index.ts +7 -0
  95. package/src/CrowdViewWidget/components/Chart/utils/processBuckets.ts +43 -0
  96. package/src/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.ts +30 -0
  97. package/src/CrowdViewWidget/components/Chart/utils/processPriceCandles.ts +53 -0
  98. package/src/CrowdViewWidget/components/Chart/utils/validateData.ts +27 -0
  99. package/src/CrowdViewWidget/components/Legend/Legend.tsx +13 -2
  100. package/src/CrowdViewWidget/constants.ts +113 -4
  101. package/src/CrowdViewWidget/selectConfig.ts +5 -60
  102. package/src/CrowdViewWidget/types.ts +18 -1
  103. package/src/translations/sources/en.json +21 -16
  104. package/test/Main.test.tsx +1 -1
  105. package/test/components/Chart/utils/chartUtils.test.ts +12 -26
  106. package/test/components/Legend.test.tsx +6 -1
  107. package/test/utils/aggregateBuckets.test.ts +82 -0
  108. package/test/utils/getTargetBucketWidth.test.ts +37 -0
  109. package/test/utils/instrumentUtils.test.ts +13 -7
  110. package/test/utils/processBuckets.test.ts +153 -0
  111. package/test/utils/processOrderPositionBooks.test.ts +127 -0
  112. package/test/utils/processPriceCandles.test.ts +245 -0
  113. package/test/utils/validateData.test.ts +201 -0
  114. package/dist/main/CrowdViewWidget/types/index.js +0 -17
  115. package/dist/main/CrowdViewWidget/types/index.js.map +0 -1
  116. package/dist/main/CrowdViewWidget/types/instruments.js +0 -45
  117. package/dist/main/CrowdViewWidget/types/instruments.js.map +0 -1
  118. package/dist/module/CrowdViewWidget/types/index.js +0 -2
  119. package/dist/module/CrowdViewWidget/types/index.js.map +0 -1
  120. package/dist/module/CrowdViewWidget/types/instruments.js +0 -39
  121. package/dist/module/CrowdViewWidget/types/instruments.js.map +0 -1
  122. package/dist/types/CrowdViewWidget/types/index.d.ts +0 -1
  123. package/dist/types/CrowdViewWidget/types/instruments.d.ts +0 -36
  124. package/src/CrowdViewWidget/types/index.ts +0 -1
  125. package/src/CrowdViewWidget/types/instruments.ts +0 -37
@@ -5,7 +5,7 @@ import {
5
5
  } from '@oanda/labs-widget-common';
6
6
 
7
7
  import { CHART_CONFIG } from '../../constants';
8
- import type { GetOptionType } from './types';
8
+ import type { Bucket, GetOptionType } from './types';
9
9
  import {
10
10
  formatXAxisLabel,
11
11
  getLabelData,
@@ -16,7 +16,15 @@ import {
16
16
 
17
17
  // @ts-expect-error
18
18
  export const getOption: GetOptionType = (
19
- { xAxisData, candlesSeriesData, orderPositionBooks, bucketWidth, buckets },
19
+ {
20
+ xAxisData,
21
+ candlesSeriesData,
22
+ orderPositionBooks,
23
+ bucketWidth,
24
+ buckets,
25
+ precision,
26
+ bookType,
27
+ },
20
28
  isDark,
21
29
  labelCallback
22
30
  ) => {
@@ -64,7 +72,7 @@ export const getOption: GetOptionType = (
64
72
  formatter: (params) => {
65
73
  if (params.axisDimension === 'y') {
66
74
  selectedPrice = Number(params.value);
67
- return Number(params.value).toFixed(5);
75
+ return Number(params.value).toFixed(precision);
68
76
  }
69
77
 
70
78
  if (params.axisDimension === 'x') {
@@ -83,13 +91,15 @@ export const getOption: GetOptionType = (
83
91
  },
84
92
  confine: true,
85
93
  formatter: (params) =>
86
- getTooltipFormatter(
94
+ getTooltipFormatter({
87
95
  params,
88
96
  buckets,
89
97
  bucketWidth,
90
98
  selectedPrice,
91
- labelCallback
92
- ),
99
+ labelCallback,
100
+ precision,
101
+ bookType,
102
+ }),
93
103
  },
94
104
  xAxis: {
95
105
  type: 'category',
@@ -114,6 +124,7 @@ export const getOption: GetOptionType = (
114
124
  axisLabel: {
115
125
  showMaxLabel: false,
116
126
  showMinLabel: false,
127
+ formatter: (value: number) => value.toFixed(precision - 1),
117
128
  },
118
129
  },
119
130
  series: [
@@ -125,6 +136,7 @@ export const getOption: GetOptionType = (
125
136
  color: colorPalette.raspberryLight,
126
137
  color0: colorPalette.bottleGreenLight,
127
138
  },
139
+
128
140
  markPoint: {
129
141
  symbol: 'circle',
130
142
  symbolSize: 0,
@@ -147,26 +159,24 @@ export const getOption: GetOptionType = (
147
159
  bucketWidth,
148
160
  ]) as number[];
149
161
 
150
- const items = metaValues.map(
151
- ({ price, sentiment }: { price: number; sentiment: number }) => {
152
- const start = api.coord([xVal, price]);
162
+ const items = metaValues.map(({ price, sentiment }: Bucket) => {
163
+ const start = api.coord([xVal, price]);
153
164
 
154
- return {
155
- type: 'rect',
156
- shape: {
157
- x: start[0] - rectWidth / 2,
158
- y: start[1] - rectHeight,
159
- width: rectWidth,
160
- height: rectHeight,
161
- },
162
- style: {
163
- fill: getRectColor(sentiment),
164
- },
165
- silent: true,
166
- emphasisDisabled: true,
167
- };
168
- }
169
- );
165
+ return {
166
+ type: 'rect',
167
+ shape: {
168
+ x: start[0] - rectWidth / 2,
169
+ y: start[1] - rectHeight,
170
+ width: rectWidth + 1,
171
+ height: rectHeight,
172
+ },
173
+ style: {
174
+ fill: getRectColor(sentiment),
175
+ },
176
+ silent: true,
177
+ emphasisDisabled: true,
178
+ };
179
+ });
170
180
 
171
181
  return {
172
182
  type: 'group',
@@ -3,4 +3,4 @@ export * from './chartOptions';
3
3
  export * from './ChartWithData';
4
4
  export * from './types';
5
5
  export * from './useCrowdViewData';
6
- export * from './utils/chartUtils';
6
+ export * from './utils';
@@ -5,9 +5,15 @@ import type {
5
5
  Division,
6
6
  Granularity,
7
7
  } from '../../../gql/types/graphql';
8
+ import type { InstrumentId } from '../../types';
9
+
10
+ export interface Bucket {
11
+ price: number;
12
+ sentiment: number;
13
+ }
8
14
 
9
15
  export interface UseCrowdViewDataProps {
10
- instrument: string;
16
+ instrument: InstrumentId;
11
17
  bookType: BookType;
12
18
  division: Division;
13
19
  granularity: Granularity;
@@ -18,9 +24,11 @@ interface CrowdViewData {
18
24
  // [open, close, low, high]
19
25
  candlesSeriesData: [number, number, number, number][];
20
26
  // [time, price, index]
21
- orderPositionBooks: ([string, number, number] | null)[];
27
+ orderPositionBooks: [string, number | null, number][];
22
28
  bucketWidth: number;
23
- buckets: { price: number; sentiment: number }[][];
29
+ buckets: Bucket[][];
30
+ precision: number;
31
+ bookType: BookType;
24
32
  }
25
33
 
26
34
  export interface UseCrowdViewDataReturn {
@@ -42,7 +50,7 @@ export interface ChartProps {
42
50
  export interface ChartWithDataProps {
43
51
  bookType: BookType;
44
52
  division: Division;
45
- instrument: string;
53
+ instrument: InstrumentId;
46
54
  granularity: Granularity;
47
55
  }
48
56
 
@@ -10,146 +10,16 @@ import type {
10
10
  GetPriceCandlesQueryVariables,
11
11
  } from '../../../gql/types/graphql';
12
12
  import { BookType, DataSource, Division } from '../../../gql/types/graphql';
13
- import { BOOKS_THRESHOLDS, BUCKET_CONFIG } from '../../constants';
13
+ import { BUCKET_CONFIG, INSTRUMENTS_CONFIG } from '../../constants';
14
14
  import type { UseCrowdViewDataProps, UseCrowdViewDataReturn } from './types';
15
- import { getTimeSpanForGranularity } from './utils/chartUtils';
16
-
17
- const processPriceCandles = (
18
- priceCandlesData: GetPriceCandlesQuery | undefined
19
- ) => {
20
- if (!priceCandlesData?.priceCandles?.candle?.length) {
21
- return {
22
- minPrice: 0,
23
- maxPrice: 0,
24
- hasValidCandles: false,
25
- candleMap: new Map(),
26
- candles: [],
27
- };
28
- }
29
-
30
- const candles = priceCandlesData.priceCandles.candle;
31
- let calculatedMinPrice = Number.MAX_VALUE;
32
- let calculatedMaxPrice = Number.MIN_VALUE;
33
-
34
- const candleMap = new Map<
35
- string,
36
- {
37
- point?: string;
38
- high?: number;
39
- low?: number;
40
- open?: number;
41
- close?: number;
42
- }
43
- >();
44
-
45
- candles.forEach((candle) => {
46
- if (!candle) return;
47
-
48
- if (candle.high > calculatedMaxPrice) {
49
- calculatedMaxPrice = candle.high;
50
- }
51
- if (candle.low < calculatedMinPrice) {
52
- calculatedMinPrice = candle.low;
53
- }
54
-
55
- if (candle.point) {
56
- candleMap.set(candle.point, candle);
57
- }
58
- });
59
-
60
- return {
61
- minPrice: calculatedMinPrice,
62
- maxPrice: calculatedMaxPrice,
63
- hasValidCandles: true,
64
- candleMap,
65
- candles,
66
- };
67
- };
68
-
69
- const processOrderPositionBooks = (
70
- orderPositionData: GetOrderPositionBooksQuery | undefined,
71
- candleMap: Map<
72
- string,
73
- {
74
- point?: string;
75
- high?: number;
76
- low?: number;
77
- open?: number;
78
- close?: number;
79
- }
80
- >
81
- ) => {
82
- if (!orderPositionData?.orderPositionBooks?.length) {
83
- return [];
84
- }
85
-
86
- return orderPositionData.orderPositionBooks
87
- .filter((book): book is NonNullable<typeof book> => {
88
- return book !== null && book.buckets?.length > 0;
89
- })
90
- .map((book, index) => {
91
- const candle = candleMap.get(book.time);
92
- const price = candle?.high ?? null;
93
-
94
- return [book.time, price, index] as [string, number, number];
95
- });
96
- };
97
-
98
- const processBuckets = (
99
- orderPositionData: GetOrderPositionBooksQuery | undefined
100
- ) => {
101
- if (!orderPositionData?.orderPositionBooks?.length) {
102
- return [];
103
- }
104
-
105
- return orderPositionData.orderPositionBooks
106
- .filter((book): book is NonNullable<typeof book> => {
107
- return book !== null && book.buckets?.length > 0;
108
- })
109
- .map((book) => {
110
- const filteredBuckets = book.buckets
111
- .filter((bucket) => {
112
- if (
113
- !bucket ||
114
- bucket.sentiment === undefined ||
115
- bucket.sentiment === null ||
116
- bucket.price === undefined
117
- ) {
118
- return false;
119
- }
120
- return Math.abs(bucket.sentiment) >= BOOKS_THRESHOLDS.MIN;
121
- })
122
- .map((bucket) => ({
123
- price: bucket!.price!,
124
- sentiment: bucket!.sentiment!,
125
- }));
126
-
127
- return filteredBuckets;
128
- });
129
- };
130
-
131
- const validateData = (
132
- priceCandlesData: GetPriceCandlesQuery | undefined,
133
- orderPositionData: GetOrderPositionBooksQuery | undefined,
134
- hasValidCandles: boolean
135
- ): Error | null => {
136
- const hasValidPriceData =
137
- (priceCandlesData?.priceCandles?.candle?.length ?? 0) >= 1;
138
- const hasValidOrderData =
139
- (orderPositionData?.orderPositionBooks?.length ?? 0) >= 1;
140
-
141
- if (!hasValidPriceData) {
142
- return new Error('Insufficient price candle data');
143
- }
144
- if (!hasValidOrderData) {
145
- return new Error('Insufficient order position data');
146
- }
147
- if (!hasValidCandles) {
148
- return new Error('Invalid candle data');
149
- }
150
-
151
- return null;
152
- };
15
+ import {
16
+ getTargetBucketWidth,
17
+ getTimeSpanForGranularity,
18
+ processBuckets,
19
+ processOrderPositionBooks,
20
+ processPriceCandles,
21
+ validateData,
22
+ } from './utils';
153
23
 
154
24
  export const useCrowdViewData = ({
155
25
  instrument,
@@ -165,9 +35,12 @@ export const useCrowdViewData = ({
165
35
  getPriceCandles,
166
36
  {
167
37
  variables: {
168
- dataSource: division === Division.Oc ? DataSource.V20 : DataSource.Mt5,
38
+ dataSource: division === Division.Ogm ? DataSource.Mt5 : DataSource.V20,
169
39
  division,
170
- instrument,
40
+ instrument:
41
+ division === Division.Ogm
42
+ ? INSTRUMENTS_CONFIG[instrument].mt5name
43
+ : INSTRUMENTS_CONFIG[instrument].v20name,
171
44
  granularity,
172
45
  timeSpan: getTimeSpanForGranularity(granularity),
173
46
  },
@@ -183,18 +56,16 @@ export const useCrowdViewData = ({
183
56
  const { minPrice, maxPrice, hasValidCandles, candleMap, candles } =
184
57
  priceCandlesProcessed;
185
58
 
59
+ const targetBucketWidth = getTargetBucketWidth(granularity, instrument);
60
+
186
61
  const maxBookPrice = useMemo(
187
- () =>
188
- maxPrice +
189
- BUCKET_CONFIG.DEFAULT_WIDTH * BUCKET_CONFIG.PRICE_PADDING_MULTIPLIER,
190
- [maxPrice]
62
+ () => maxPrice + targetBucketWidth * BUCKET_CONFIG.PRICE_PADDING_MULTIPLIER,
63
+ [maxPrice, targetBucketWidth]
191
64
  );
192
65
 
193
66
  const minBookPrice = useMemo(
194
- () =>
195
- minPrice -
196
- BUCKET_CONFIG.DEFAULT_WIDTH * BUCKET_CONFIG.PRICE_PADDING_MULTIPLIER,
197
- [minPrice]
67
+ () => minPrice - targetBucketWidth * BUCKET_CONFIG.PRICE_PADDING_MULTIPLIER,
68
+ [minPrice, targetBucketWidth]
198
69
  );
199
70
 
200
71
  const {
@@ -205,7 +76,7 @@ export const useCrowdViewData = ({
205
76
  getOrderPositionBooks,
206
77
  {
207
78
  variables: {
208
- instrument,
79
+ instrument: INSTRUMENTS_CONFIG[instrument].v20name,
209
80
  bookType: bookType || BookType.Order,
210
81
  timeSpan: getTimeSpanForGranularity(granularity),
211
82
  granularity,
@@ -225,8 +96,8 @@ export const useCrowdViewData = ({
225
96
  );
226
97
 
227
98
  const buckets = useMemo(
228
- () => processBuckets(orderPositionData),
229
- [orderPositionData]
99
+ () => processBuckets(orderPositionData, targetBucketWidth),
100
+ [orderPositionData, targetBucketWidth]
230
101
  );
231
102
 
232
103
  const error = useMemo((): Error | null => {
@@ -269,15 +140,20 @@ export const useCrowdViewData = ({
269
140
  xAxisData,
270
141
  candlesSeriesData,
271
142
  orderPositionBooks,
272
- bucketWidth: orderPositionData.orderPositionBooks?.[0]?.bucketWidth || 0,
143
+ bucketWidth: targetBucketWidth,
144
+ precision: INSTRUMENTS_CONFIG[instrument].precision,
145
+ bookType,
273
146
  };
274
147
  }, [
275
148
  priceCandlesData,
276
149
  orderPositionData,
277
150
  error,
278
151
  candles,
279
- orderPositionBooks,
280
152
  buckets,
153
+ orderPositionBooks,
154
+ targetBucketWidth,
155
+ instrument,
156
+ bookType,
281
157
  ]);
282
158
 
283
159
  return {
@@ -0,0 +1,44 @@
1
+ import Decimal from 'decimal.js';
2
+
3
+ import type { Bucket } from '../types';
4
+
5
+ export const aggregateBuckets = (
6
+ buckets: Bucket[],
7
+ newBucketWidth: number
8
+ ): Bucket[] => {
9
+ if (!buckets.length) {
10
+ return [];
11
+ }
12
+
13
+ const bucketWidthDecimal = new Decimal(newBucketWidth);
14
+
15
+ const aggregatedMap = new Map<string, Decimal>();
16
+
17
+ for (const bucket of buckets) {
18
+ const priceDecimal = new Decimal(bucket.price);
19
+ const sentimentDecimal = new Decimal(bucket.sentiment);
20
+
21
+ const bucketIndex = priceDecimal.div(bucketWidthDecimal).floor();
22
+ const groupingKey = bucketIndex.mul(bucketWidthDecimal);
23
+
24
+ const groupingKeyStr = groupingKey.toString();
25
+
26
+ const currentSentiment =
27
+ aggregatedMap.get(groupingKeyStr) ?? new Decimal(0);
28
+ aggregatedMap.set(groupingKeyStr, currentSentiment.add(sentimentDecimal));
29
+ }
30
+
31
+ const aggregatedBuckets = Array.from(aggregatedMap.entries()).map(
32
+ ([priceStr, sentimentDecimal]) => {
33
+ const price = new Decimal(priceStr).toNumber();
34
+ const sentiment = sentimentDecimal.toNumber();
35
+
36
+ return {
37
+ price,
38
+ sentiment,
39
+ };
40
+ }
41
+ );
42
+
43
+ return aggregatedBuckets;
44
+ };
@@ -1,13 +1,13 @@
1
1
  import chroma from 'chroma-js';
2
2
 
3
- import { Granularity, TimeSpan } from '../../../../gql/types/graphql';
3
+ import { BookType, Granularity, TimeSpan } from '../../../../gql/types/graphql';
4
4
  import {
5
5
  BOOKS_THRESHOLDS,
6
6
  CHART_CONFIG,
7
7
  COLOR_MAP,
8
8
  TIME_THRESHOLDS,
9
9
  } from '../../../constants';
10
- import type { GetLabelsDataProps } from '../types';
10
+ import type { Bucket, GetLabelsDataProps } from '../types';
11
11
 
12
12
  export const getLabelData = ({
13
13
  xAxisData,
@@ -92,14 +92,27 @@ export const getRectColor = (sentiment: number) =>
92
92
  ? getGradientColor(sentiment * -1, COLOR_MAP.short[0], COLOR_MAP.short[1])
93
93
  : getGradientColor(sentiment, COLOR_MAP.long[0], COLOR_MAP.long[1]);
94
94
 
95
- export const getTooltipFormatter = (
96
- params: unknown,
97
- buckets: Array<Array<{ price: number; sentiment: number }>>,
98
- bucketWidth: number,
99
- selectedPrice: number,
100
- labelCallback: (key: string) => string
101
- ) => {
95
+ export const getTooltipFormatter = ({
96
+ params,
97
+ buckets,
98
+ bucketWidth,
99
+ selectedPrice,
100
+ precision,
101
+ bookType,
102
+ labelCallback,
103
+ }: {
104
+ params: unknown;
105
+ buckets: Bucket[][];
106
+ bucketWidth: number;
107
+ selectedPrice: number;
108
+ precision: number;
109
+ bookType: BookType;
110
+ labelCallback: (key: string) => string;
111
+ }) => {
102
112
  const arr = params as unknown as Array<Record<string, unknown>>;
113
+ if (!arr || !Array.isArray(arr) || arr.length === 0) {
114
+ return undefined;
115
+ }
103
116
  const candleParam = arr[0] as {
104
117
  axisValue: string;
105
118
  value: [number, number, number, number, number];
@@ -118,6 +131,10 @@ export const getTooltipFormatter = (
118
131
  const showCandles =
119
132
  !!candleData[1] && !!candleData[2] && !!candleData[3] && !!candleData[4];
120
133
 
134
+ if (!showCandles && !matchedBucket) {
135
+ return undefined;
136
+ }
137
+
121
138
  return `<p>${time.toLocaleString(undefined, {
122
139
  hour: '2-digit',
123
140
  minute: '2-digit',
@@ -138,9 +155,17 @@ ${
138
155
  }
139
156
  ${
140
157
  matchedBucket
141
- ? `<br /><p><b>${labelCallback('orders')}:</b></p>
142
- <p>${labelCallback('price_range')}: ${matchedBucket.price} - ${Number(matchedBucket.price + bucketWidth).toFixed(4)} </p>
143
- <p>${matchedBucket.sentiment < 0 ? labelCallback('sell_advantage') : labelCallback('buy_advantage')}: ${Math.abs(matchedBucket.sentiment)}% </p>`
158
+ ? `<br /><p><b>${labelCallback(bookType === BookType.Order ? 'orders' : 'positions')}:</b></p>
159
+ <p>${labelCallback('price_range')}: ${matchedBucket.price.toFixed(precision - 1)} - ${Number(matchedBucket.price + bucketWidth).toFixed(precision - 1)} </p>
160
+ <p>${
161
+ matchedBucket.sentiment < 0
162
+ ? labelCallback(
163
+ bookType === BookType.Order ? 'sell_advantage' : 'short_advantage'
164
+ )
165
+ : labelCallback(
166
+ bookType === BookType.Order ? 'buy_advantage' : 'long_advantage'
167
+ )
168
+ }: ${Math.abs(matchedBucket.sentiment)}% </p>`
144
169
  : ''
145
170
  }`;
146
171
  };
@@ -0,0 +1,13 @@
1
+ import { Granularity } from '../../../../gql/types/graphql';
2
+ import { BUCKET_CONFIG, INSTRUMENTS_CONFIG } from '../../../constants';
3
+ import type { InstrumentId } from '../../../types';
4
+
5
+ export const getTargetBucketWidth = (
6
+ granularity: Granularity,
7
+ instrument: InstrumentId
8
+ ): number => {
9
+ const bucketWidth = INSTRUMENTS_CONFIG[instrument].defaultBucketWidth;
10
+ return granularity === Granularity.H1 || granularity === Granularity.H4
11
+ ? bucketWidth * BUCKET_CONFIG.MULTIPLIER
12
+ : bucketWidth;
13
+ };
@@ -0,0 +1,7 @@
1
+ export * from './aggregateBuckets';
2
+ export * from './chartUtils';
3
+ export * from './getTargetBucketWidth';
4
+ export * from './processBuckets';
5
+ export * from './processOrderPositionBooks';
6
+ export * from './processPriceCandles';
7
+ export * from './validateData';
@@ -0,0 +1,43 @@
1
+ import type { GetOrderPositionBooksQuery } from '../../../../gql/types/graphql';
2
+ import { BOOKS_THRESHOLDS } from '../../../constants';
3
+ import type { Bucket } from '../types';
4
+ import { aggregateBuckets } from './aggregateBuckets';
5
+
6
+ export const processBuckets = (
7
+ orderPositionData: GetOrderPositionBooksQuery | undefined,
8
+ targetBucketWidth: number
9
+ ): Bucket[][] => {
10
+ if (!orderPositionData?.orderPositionBooks?.length) {
11
+ return [];
12
+ }
13
+
14
+ return orderPositionData.orderPositionBooks
15
+ .filter((book): book is NonNullable<typeof book> => {
16
+ return book !== null && book.buckets?.length > 0;
17
+ })
18
+ .map((book) => {
19
+ const validBuckets = book.buckets
20
+ .filter(
21
+ (bucket): bucket is NonNullable<typeof bucket> =>
22
+ bucket !== null &&
23
+ bucket.price !== undefined &&
24
+ bucket.sentiment !== undefined &&
25
+ bucket.sentiment !== null
26
+ )
27
+ .map((bucket) => ({
28
+ price: bucket.price!,
29
+ sentiment: bucket.sentiment!,
30
+ }));
31
+
32
+ const bucketsToFilter =
33
+ targetBucketWidth > (book.bucketWidth ?? 0)
34
+ ? aggregateBuckets(validBuckets, targetBucketWidth)
35
+ : validBuckets;
36
+
37
+ const filteredBuckets = bucketsToFilter.filter(
38
+ (bucket: Bucket) => Math.abs(bucket.sentiment) >= BOOKS_THRESHOLDS.MIN
39
+ );
40
+
41
+ return filteredBuckets;
42
+ });
43
+ };
@@ -0,0 +1,30 @@
1
+ import type { GetOrderPositionBooksQuery } from '../../../../gql/types/graphql';
2
+
3
+ export const processOrderPositionBooks = (
4
+ orderPositionData: GetOrderPositionBooksQuery | undefined,
5
+ candleMap: Map<
6
+ string,
7
+ {
8
+ point?: string;
9
+ high?: number;
10
+ low?: number;
11
+ open?: number;
12
+ close?: number;
13
+ }
14
+ >
15
+ ): [string, number | null, number][] => {
16
+ if (!orderPositionData?.orderPositionBooks?.length) {
17
+ return [];
18
+ }
19
+
20
+ return orderPositionData.orderPositionBooks
21
+ .filter((book): book is NonNullable<typeof book> => {
22
+ return book !== null && book.buckets?.length > 0;
23
+ })
24
+ .map((book, index) => {
25
+ const candle = candleMap.get(book.time);
26
+ const price = candle?.high ?? null;
27
+
28
+ return [book.time, price, index] as [string, number | null, number];
29
+ });
30
+ };
@@ -0,0 +1,53 @@
1
+ import type { GetPriceCandlesQuery } from '../../../../gql/types/graphql';
2
+
3
+ export const processPriceCandles = (
4
+ priceCandlesData: GetPriceCandlesQuery | undefined
5
+ ) => {
6
+ if (!priceCandlesData?.priceCandles?.candle?.length) {
7
+ return {
8
+ minPrice: 0,
9
+ maxPrice: 0,
10
+ hasValidCandles: false,
11
+ candleMap: new Map(),
12
+ candles: [],
13
+ };
14
+ }
15
+
16
+ const candles = priceCandlesData.priceCandles.candle;
17
+ let calculatedMinPrice = Number.MAX_VALUE;
18
+ let calculatedMaxPrice = Number.MIN_VALUE;
19
+
20
+ const candleMap = new Map<
21
+ string,
22
+ {
23
+ point?: string;
24
+ high?: number;
25
+ low?: number;
26
+ open?: number;
27
+ close?: number;
28
+ }
29
+ >();
30
+
31
+ candles.forEach((candle) => {
32
+ if (!candle) return;
33
+
34
+ if (candle.high > calculatedMaxPrice) {
35
+ calculatedMaxPrice = candle.high;
36
+ }
37
+ if (candle.low < calculatedMinPrice) {
38
+ calculatedMinPrice = candle.low;
39
+ }
40
+
41
+ if (candle.point) {
42
+ candleMap.set(candle.point, candle);
43
+ }
44
+ });
45
+
46
+ return {
47
+ minPrice: calculatedMinPrice,
48
+ maxPrice: calculatedMaxPrice,
49
+ hasValidCandles: true,
50
+ candleMap,
51
+ candles,
52
+ };
53
+ };