@oanda/labs-crowd-view-widget 1.0.45 → 1.0.47

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 (142) hide show
  1. package/CHANGELOG.md +380 -0
  2. package/dist/main/CrowdViewWidget/Main.js +9 -5
  3. package/dist/main/CrowdViewWidget/Main.js.map +1 -1
  4. package/dist/main/CrowdViewWidget/components/Chart/Chart.js +3 -2
  5. package/dist/main/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  6. package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js +5 -14
  7. package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  8. package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js +20 -8
  9. package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
  10. package/dist/main/CrowdViewWidget/components/Chart/index.js +4 -4
  11. package/dist/main/CrowdViewWidget/components/Chart/index.js.map +1 -1
  12. package/dist/main/CrowdViewWidget/components/Chart/types.js.map +1 -1
  13. package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js +17 -101
  14. package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
  15. package/dist/main/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js +37 -0
  16. package/dist/main/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js.map +1 -0
  17. package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js +19 -4
  18. package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
  19. package/dist/main/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js +14 -0
  20. package/dist/main/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js.map +1 -0
  21. package/dist/main/CrowdViewWidget/components/Chart/utils/index.js +83 -0
  22. package/dist/main/CrowdViewWidget/components/Chart/utils/index.js.map +1 -0
  23. package/dist/main/CrowdViewWidget/components/Chart/utils/processBuckets.js +29 -0
  24. package/dist/main/CrowdViewWidget/components/Chart/utils/processBuckets.js.map +1 -0
  25. package/dist/main/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js +23 -0
  26. package/dist/main/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js.map +1 -0
  27. package/dist/main/CrowdViewWidget/components/Chart/utils/processPriceCandles.js +43 -0
  28. package/dist/main/CrowdViewWidget/components/Chart/utils/processPriceCandles.js.map +1 -0
  29. package/dist/main/CrowdViewWidget/components/Chart/utils/validateData.js +23 -0
  30. package/dist/main/CrowdViewWidget/components/Chart/utils/validateData.js.map +1 -0
  31. package/dist/main/CrowdViewWidget/components/Legend/Legend.js +6 -4
  32. package/dist/main/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  33. package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js +5 -3
  34. package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
  35. package/dist/main/CrowdViewWidget/constants.js +105 -5
  36. package/dist/main/CrowdViewWidget/constants.js.map +1 -1
  37. package/dist/main/CrowdViewWidget/selectConfig.js +18 -60
  38. package/dist/main/CrowdViewWidget/selectConfig.js.map +1 -1
  39. package/dist/main/CrowdViewWidget/types.js +20 -0
  40. package/dist/main/CrowdViewWidget/types.js.map +1 -1
  41. package/dist/main/translations/sources/en.json +21 -16
  42. package/dist/module/CrowdViewWidget/Main.js +9 -5
  43. package/dist/module/CrowdViewWidget/Main.js.map +1 -1
  44. package/dist/module/CrowdViewWidget/components/Chart/Chart.js +3 -2
  45. package/dist/module/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  46. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js +5 -14
  47. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  48. package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js +20 -8
  49. package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
  50. package/dist/module/CrowdViewWidget/components/Chart/index.js +1 -1
  51. package/dist/module/CrowdViewWidget/components/Chart/index.js.map +1 -1
  52. package/dist/module/CrowdViewWidget/components/Chart/types.js.map +1 -1
  53. package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js +13 -97
  54. package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
  55. package/dist/module/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js +29 -0
  56. package/dist/module/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js.map +1 -0
  57. package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js +20 -5
  58. package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
  59. package/dist/module/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js +7 -0
  60. package/dist/module/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js.map +1 -0
  61. package/dist/module/CrowdViewWidget/components/Chart/utils/index.js +8 -0
  62. package/dist/module/CrowdViewWidget/components/Chart/utils/index.js.map +1 -0
  63. package/dist/module/CrowdViewWidget/components/Chart/utils/processBuckets.js +22 -0
  64. package/dist/module/CrowdViewWidget/components/Chart/utils/processBuckets.js.map +1 -0
  65. package/dist/module/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js +16 -0
  66. package/dist/module/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js.map +1 -0
  67. package/dist/module/CrowdViewWidget/components/Chart/utils/processPriceCandles.js +36 -0
  68. package/dist/module/CrowdViewWidget/components/Chart/utils/processPriceCandles.js.map +1 -0
  69. package/dist/module/CrowdViewWidget/components/Chart/utils/validateData.js +16 -0
  70. package/dist/module/CrowdViewWidget/components/Chart/utils/validateData.js.map +1 -0
  71. package/dist/module/CrowdViewWidget/components/Legend/Legend.js +6 -4
  72. package/dist/module/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  73. package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js +5 -3
  74. package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
  75. package/dist/module/CrowdViewWidget/constants.js +104 -4
  76. package/dist/module/CrowdViewWidget/constants.js.map +1 -1
  77. package/dist/module/CrowdViewWidget/selectConfig.js +3 -45
  78. package/dist/module/CrowdViewWidget/selectConfig.js.map +1 -1
  79. package/dist/module/CrowdViewWidget/types.js +19 -1
  80. package/dist/module/CrowdViewWidget/types.js.map +1 -1
  81. package/dist/module/translations/sources/en.json +21 -16
  82. package/dist/types/CrowdViewWidget/components/Chart/Chart.d.ts +1 -1
  83. package/dist/types/CrowdViewWidget/components/Chart/index.d.ts +1 -1
  84. package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +13 -8
  85. package/dist/types/CrowdViewWidget/components/Chart/utils/aggregateBuckets.d.ts +2 -0
  86. package/dist/types/CrowdViewWidget/components/Chart/utils/chartUtils.d.ts +11 -6
  87. package/dist/types/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.d.ts +3 -0
  88. package/dist/types/CrowdViewWidget/components/Chart/utils/index.d.ts +7 -0
  89. package/dist/types/CrowdViewWidget/components/Chart/utils/processBuckets.d.ts +3 -0
  90. package/dist/types/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.d.ts +8 -0
  91. package/dist/types/CrowdViewWidget/components/Chart/utils/processPriceCandles.d.ts +27 -0
  92. package/dist/types/CrowdViewWidget/components/Chart/utils/validateData.d.ts +2 -0
  93. package/dist/types/CrowdViewWidget/components/Legend/Legend.d.ts +3 -1
  94. package/dist/types/CrowdViewWidget/constants.d.ts +12 -4
  95. package/dist/types/CrowdViewWidget/selectConfig.d.ts +2 -2
  96. package/dist/types/CrowdViewWidget/types.d.ts +18 -1
  97. package/dist/types/CrowdViewWidget/utils/instrumentUtils.d.ts +1 -4
  98. package/package.json +4 -3
  99. package/src/CrowdViewWidget/Main.tsx +46 -40
  100. package/src/CrowdViewWidget/components/Chart/Chart.tsx +2 -2
  101. package/src/CrowdViewWidget/components/Chart/ChartWithData.tsx +4 -26
  102. package/src/CrowdViewWidget/components/Chart/chartOptions.ts +41 -27
  103. package/src/CrowdViewWidget/components/Chart/index.ts +1 -1
  104. package/src/CrowdViewWidget/components/Chart/types.ts +14 -4
  105. package/src/CrowdViewWidget/components/Chart/useCrowdViewData.ts +30 -154
  106. package/src/CrowdViewWidget/components/Chart/utils/aggregateBuckets.ts +44 -0
  107. package/src/CrowdViewWidget/components/Chart/utils/chartUtils.ts +41 -12
  108. package/src/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.ts +13 -0
  109. package/src/CrowdViewWidget/components/Chart/utils/index.ts +7 -0
  110. package/src/CrowdViewWidget/components/Chart/utils/processBuckets.ts +43 -0
  111. package/src/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.ts +30 -0
  112. package/src/CrowdViewWidget/components/Chart/utils/processPriceCandles.ts +53 -0
  113. package/src/CrowdViewWidget/components/Chart/utils/validateData.ts +27 -0
  114. package/src/CrowdViewWidget/components/Legend/Legend.tsx +14 -3
  115. package/src/CrowdViewWidget/components/Legend/LegendBar.tsx +5 -2
  116. package/src/CrowdViewWidget/constants.ts +114 -4
  117. package/src/CrowdViewWidget/selectConfig.ts +5 -60
  118. package/src/CrowdViewWidget/types.ts +18 -1
  119. package/src/translations/sources/en.json +21 -16
  120. package/test/Main.test.tsx +1 -1
  121. package/test/components/Chart/utils/chartUtils.test.ts +13 -27
  122. package/test/components/Legend.test.tsx +8 -3
  123. package/test/components/LegendBar.test.tsx +3 -2
  124. package/test/utils/aggregateBuckets.test.ts +82 -0
  125. package/test/utils/getTargetBucketWidth.test.ts +37 -0
  126. package/test/utils/instrumentUtils.test.ts +13 -7
  127. package/test/utils/processBuckets.test.ts +153 -0
  128. package/test/utils/processOrderPositionBooks.test.ts +127 -0
  129. package/test/utils/processPriceCandles.test.ts +245 -0
  130. package/test/utils/validateData.test.ts +201 -0
  131. package/dist/main/CrowdViewWidget/types/index.js +0 -17
  132. package/dist/main/CrowdViewWidget/types/index.js.map +0 -1
  133. package/dist/main/CrowdViewWidget/types/instruments.js +0 -45
  134. package/dist/main/CrowdViewWidget/types/instruments.js.map +0 -1
  135. package/dist/module/CrowdViewWidget/types/index.js +0 -2
  136. package/dist/module/CrowdViewWidget/types/index.js.map +0 -1
  137. package/dist/module/CrowdViewWidget/types/instruments.js +0 -39
  138. package/dist/module/CrowdViewWidget/types/instruments.js.map +0 -1
  139. package/dist/types/CrowdViewWidget/types/index.d.ts +0 -1
  140. package/dist/types/CrowdViewWidget/types/instruments.d.ts +0 -36
  141. package/src/CrowdViewWidget/types/index.ts +0 -1
  142. package/src/CrowdViewWidget/types/instruments.ts +0 -37
@@ -12,8 +12,7 @@ import type { Granularity } from '../gql/types/graphql';
12
12
  import { BookType } from '../gql/types/graphql';
13
13
  import { ChartWithData, Legend } from './components';
14
14
  import { granularitySelectConfig, navigationConfig } from './selectConfig';
15
- import type { MainProps } from './types';
16
- import type { InstrumentId } from './types/instruments';
15
+ import type { InstrumentId, MainProps } from './types';
17
16
  import { getInstrumentConfigForDivision } from './utils/instrumentUtils';
18
17
 
19
18
  const Main = ({ division }: MainProps) => {
@@ -53,48 +52,55 @@ const Main = ({ division }: MainProps) => {
53
52
  className="lw-text-sm lw-tracking-normal"
54
53
  data-testid="crowd-view-widget"
55
54
  >
56
- <Tabs
57
- mobileFullWidth
58
- activeTab={bookType}
59
- handleClick={(e) => setBookType(e.currentTarget.value as BookType)}
60
- items={navigationConfig}
61
- labelCallback={lang}
62
- />
63
- <div className="lw-mb-6 lw-mt-12 lw-flex">
64
- <div
65
- className={cn('lw-mr-2', {
66
- 'lw-w-full': !isDesktop,
67
- 'lw-w-[280px]': isDesktop,
68
- })}
69
- >
70
- <Select
71
- options={instrumentSelectConfigWithDivision}
72
- searchPlaceholder={lang('search')}
73
- selectLabel={lang('instrument')}
74
- selectedOption={instrument}
75
- setSelectedOption={(val) =>
76
- setInstrument(val as { id: InstrumentId; label: string })
77
- }
78
- />
79
- </div>
55
+ <div>
56
+ <Tabs
57
+ mobileFullWidth
58
+ activeTab={bookType}
59
+ handleClick={(e) =>
60
+ setBookType(e.currentTarget.value as BookType)
61
+ }
62
+ items={navigationConfig}
63
+ labelCallback={lang}
64
+ />
80
65
  <div
81
- className={cn({
82
- 'lw-w-full': !isDesktop,
83
- 'lw-w-[280px]': isDesktop,
66
+ className={cn('lw-mb-6 lw-mt-12', {
67
+ 'lw-flex': isDesktop,
84
68
  })}
85
69
  >
86
- <Select
87
- options={granularitySelectConfigWithLang}
88
- searchPlaceholder={lang('search')}
89
- selectLabel={lang('granularity')}
90
- selectedOption={granularity}
91
- setSelectedOption={(val) =>
92
- setGranularity(val as { id: Granularity; label: string })
93
- }
94
- />
70
+ <div
71
+ className={cn('lw-mr-2', {
72
+ 'lw-w-full lw-mb-2': !isDesktop,
73
+ 'lw-w-[280px]': isDesktop,
74
+ })}
75
+ >
76
+ <Select
77
+ options={instrumentSelectConfigWithDivision}
78
+ searchPlaceholder={lang('search')}
79
+ selectLabel={lang('instrument')}
80
+ selectedOption={instrument}
81
+ setSelectedOption={(val) =>
82
+ setInstrument(val as { id: InstrumentId; label: string })
83
+ }
84
+ />
85
+ </div>
86
+ <div
87
+ className={cn({
88
+ 'lw-w-full': !isDesktop,
89
+ 'lw-w-[280px]': isDesktop,
90
+ })}
91
+ >
92
+ <Select
93
+ options={granularitySelectConfigWithLang}
94
+ searchPlaceholder={lang('search')}
95
+ selectLabel={lang('granularity')}
96
+ selectedOption={granularity}
97
+ setSelectedOption={(val) =>
98
+ setGranularity(val as { id: Granularity; label: string })
99
+ }
100
+ />
101
+ </div>
95
102
  </div>
96
103
  </div>
97
-
98
104
  <ChartWithData
99
105
  bookType={bookType}
100
106
  division={division}
@@ -102,7 +108,7 @@ const Main = ({ division }: MainProps) => {
102
108
  instrument={instrument.id}
103
109
  />
104
110
 
105
- <Legend />
111
+ <Legend bookType={bookType} />
106
112
  </div>
107
113
  )}
108
114
  </>
@@ -48,7 +48,7 @@ echarts.use([
48
48
  echarts.registerTheme('dark_theme', getChartTheme(Theme.Dark));
49
49
  echarts.registerTheme('light_theme', getChartTheme(Theme.Light));
50
50
 
51
- const Chart = ({ data }: ChartProps) => {
51
+ const Chart = ({ data, isDesktop }: ChartProps) => {
52
52
  const { isDark } = useLayoutProvider();
53
53
  const { lang } = useLocale();
54
54
 
@@ -58,7 +58,7 @@ const Chart = ({ data }: ChartProps) => {
58
58
  echarts={echarts}
59
59
  isDark={isDark}
60
60
  lazyUpdate={true}
61
- option={getOption(data, isDark, lang)}
61
+ option={getOption(data, isDark, isDesktop, lang)}
62
62
  opts={{ renderer: 'canvas' }}
63
63
  onEvents={{
64
64
  datazoom: (_params: unknown, instance: EChartsType) => {
@@ -5,7 +5,6 @@ import {
5
5
  SpinnerSize,
6
6
  useLayoutProvider,
7
7
  } from '@oanda/labs-widget-common';
8
- import classnames from 'classnames';
9
8
  import React from 'react';
10
9
 
11
10
  import { Chart } from './Chart';
@@ -30,41 +29,20 @@ const ChartWithData = ({
30
29
 
31
30
  return (
32
31
  <>
33
- <div
34
- className={classnames('lw-relative lw-w-full', {
35
- 'lw-h-[450px]': isDesktop,
36
- 'lw-h-[390px]': !isDesktop,
37
- })}
38
- >
32
+ <div className="lw-relative lw-h-[450px] lw-w-full">
39
33
  {error && (
40
- <div
41
- className={classnames(
42
- 'lw-absolute lw-left-0 lw-top-0 lw-flex lw-w-full lw-items-center lw-justify-center lw-border lw-border-solid lw-border-border-primary',
43
- {
44
- 'lw-h-full': isDesktop,
45
- 'lw-h-[calc(100%-40px)]': !isDesktop,
46
- }
47
- )}
48
- >
34
+ <div className="lw-absolute lw-left-0 lw-top-0 lw-flex lw-h-[calc(100%-30px)] lw-w-full lw-items-center lw-justify-center lw-border lw-border-solid lw-border-border-primary">
49
35
  <ChartError />
50
36
  </div>
51
37
  )}
52
38
  {loading && (
53
- <div
54
- className={classnames(
55
- 'lw-absolute lw-left-0 lw-top-0 lw-flex lw-w-full lw-items-center lw-justify-center lw-border lw-border-solid lw-border-border-primary',
56
- {
57
- 'lw-h-full': isDesktop,
58
- 'lw-h-[calc(100%-40px)]': !isDesktop,
59
- }
60
- )}
61
- >
39
+ <div className="lw-absolute lw-left-0 lw-top-0 lw-flex lw-h-[calc(100%-30px)] lw-w-full lw-items-center lw-justify-center lw-border lw-border-solid lw-border-border-primary">
62
40
  <Spinner size={SpinnerSize.lg} />
63
41
  </div>
64
42
  )}
65
43
  {!loading && !error && !!data && (
66
44
  <div className="lw-absolute lw-left-0 lw-top-0 lw-flex lw-h-full lw-w-full">
67
- <Chart data={data} />
45
+ <Chart data={data} isDesktop={isDesktop} />
68
46
  </div>
69
47
  )}
70
48
  </div>
@@ -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,8 +16,17 @@ 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,
29
+ isDesktop,
21
30
  labelCallback
22
31
  ) => {
23
32
  let selectedPrice: number;
@@ -41,7 +50,9 @@ export const getOption: GetOptionType = (
41
50
  chartWidth: CHART_CONFIG.WIDTH,
42
51
  chartHeight: CHART_CONFIG.HEIGHT,
43
52
  xLabelsSize: CHART_CONFIG.X_LABEL_SIZE,
44
- yLabelSize: CHART_CONFIG.Y_LABEL_SIZE_DESKTOP,
53
+ yLabelSize: isDesktop
54
+ ? CHART_CONFIG.Y_LABEL_SIZE_DESKTOP
55
+ : CHART_CONFIG.Y_LABEL_SIZE_MOBILE,
45
56
  bottomLeftBox: false,
46
57
  });
47
58
 
@@ -64,7 +75,7 @@ export const getOption: GetOptionType = (
64
75
  formatter: (params) => {
65
76
  if (params.axisDimension === 'y') {
66
77
  selectedPrice = Number(params.value);
67
- return Number(params.value).toFixed(5);
78
+ return Number(params.value).toFixed(precision);
68
79
  }
69
80
 
70
81
  if (params.axisDimension === 'x') {
@@ -83,13 +94,15 @@ export const getOption: GetOptionType = (
83
94
  },
84
95
  confine: true,
85
96
  formatter: (params) =>
86
- getTooltipFormatter(
97
+ getTooltipFormatter({
87
98
  params,
88
99
  buckets,
89
100
  bucketWidth,
90
101
  selectedPrice,
91
- labelCallback
92
- ),
102
+ labelCallback,
103
+ precision,
104
+ bookType,
105
+ }),
93
106
  },
94
107
  xAxis: {
95
108
  type: 'category',
@@ -114,6 +127,8 @@ export const getOption: GetOptionType = (
114
127
  axisLabel: {
115
128
  showMaxLabel: false,
116
129
  showMinLabel: false,
130
+ margin: isDesktop ? 4 : 2,
131
+ formatter: (value: number) => value.toFixed(precision - 1),
117
132
  },
118
133
  },
119
134
  series: [
@@ -125,6 +140,7 @@ export const getOption: GetOptionType = (
125
140
  color: colorPalette.raspberryLight,
126
141
  color0: colorPalette.bottleGreenLight,
127
142
  },
143
+
128
144
  markPoint: {
129
145
  symbol: 'circle',
130
146
  symbolSize: 0,
@@ -147,26 +163,24 @@ export const getOption: GetOptionType = (
147
163
  bucketWidth,
148
164
  ]) as number[];
149
165
 
150
- const items = metaValues.map(
151
- ({ price, sentiment }: { price: number; sentiment: number }) => {
152
- const start = api.coord([xVal, price]);
166
+ const items = metaValues.map(({ price, sentiment }: Bucket) => {
167
+ const start = api.coord([xVal, price]);
153
168
 
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
- );
169
+ return {
170
+ type: 'rect',
171
+ shape: {
172
+ x: start[0] - rectWidth / 2,
173
+ y: start[1] - rectHeight,
174
+ width: rectWidth + 1,
175
+ height: rectHeight,
176
+ },
177
+ style: {
178
+ fill: getRectColor(sentiment),
179
+ },
180
+ silent: true,
181
+ emphasisDisabled: true,
182
+ };
183
+ });
170
184
 
171
185
  return {
172
186
  type: 'group',
@@ -183,7 +197,7 @@ export const getOption: GetOptionType = (
183
197
  name: 'main-grid',
184
198
  top: '0px',
185
199
  left: '0px',
186
- right: `${CHART_CONFIG.Y_LABEL_SIZE_DESKTOP}px`,
200
+ right: `${isDesktop ? CHART_CONFIG.Y_LABEL_SIZE_DESKTOP : CHART_CONFIG.Y_LABEL_SIZE_MOBILE}px`,
187
201
  bottom: `${CHART_CONFIG.X_LABEL_SIZE}px`,
188
202
  },
189
203
  ],
@@ -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 {
@@ -32,17 +40,19 @@ export interface UseCrowdViewDataReturn {
32
40
  export type GetOptionType = (
33
41
  props: CrowdViewData,
34
42
  isDark: boolean,
43
+ isDesktop: boolean,
35
44
  labelCallback: (key: string, params?: Record<string, unknown>) => string
36
45
  ) => EChartsOption;
37
46
 
38
47
  export interface ChartProps {
39
48
  data: CrowdViewData;
49
+ isDesktop: boolean;
40
50
  }
41
51
 
42
52
  export interface ChartWithDataProps {
43
53
  bookType: BookType;
44
54
  division: Division;
45
- instrument: string;
55
+ instrument: InstrumentId;
46
56
  granularity: Granularity;
47
57
  }
48
58
 
@@ -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
+ };