@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.
- package/CHANGELOG.md +380 -0
- package/dist/main/CrowdViewWidget/Main.js +9 -5
- package/dist/main/CrowdViewWidget/Main.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/Chart.js +3 -2
- package/dist/main/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js +5 -14
- package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js +20 -8
- package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/index.js +4 -4
- package/dist/main/CrowdViewWidget/components/Chart/index.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/types.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js +17 -101
- package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js +37 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js.map +1 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js +19 -4
- package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js +14 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js.map +1 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/index.js +83 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/index.js.map +1 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/processBuckets.js +29 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/processBuckets.js.map +1 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js +23 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js.map +1 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/processPriceCandles.js +43 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/processPriceCandles.js.map +1 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/validateData.js +23 -0
- package/dist/main/CrowdViewWidget/components/Chart/utils/validateData.js.map +1 -0
- package/dist/main/CrowdViewWidget/components/Legend/Legend.js +6 -4
- package/dist/main/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js +5 -3
- package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
- package/dist/main/CrowdViewWidget/constants.js +105 -5
- package/dist/main/CrowdViewWidget/constants.js.map +1 -1
- package/dist/main/CrowdViewWidget/selectConfig.js +18 -60
- package/dist/main/CrowdViewWidget/selectConfig.js.map +1 -1
- package/dist/main/CrowdViewWidget/types.js +20 -0
- package/dist/main/CrowdViewWidget/types.js.map +1 -1
- package/dist/main/translations/sources/en.json +21 -16
- package/dist/module/CrowdViewWidget/Main.js +9 -5
- package/dist/module/CrowdViewWidget/Main.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/Chart.js +3 -2
- package/dist/module/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js +5 -14
- package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js +20 -8
- package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/index.js +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/index.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/types.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js +13 -97
- package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js +29 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js.map +1 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js +20 -5
- package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js +7 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js.map +1 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/index.js +8 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/index.js.map +1 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/processBuckets.js +22 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/processBuckets.js.map +1 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js +16 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js.map +1 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/processPriceCandles.js +36 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/processPriceCandles.js.map +1 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/validateData.js +16 -0
- package/dist/module/CrowdViewWidget/components/Chart/utils/validateData.js.map +1 -0
- package/dist/module/CrowdViewWidget/components/Legend/Legend.js +6 -4
- package/dist/module/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js +5 -3
- package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
- package/dist/module/CrowdViewWidget/constants.js +104 -4
- package/dist/module/CrowdViewWidget/constants.js.map +1 -1
- package/dist/module/CrowdViewWidget/selectConfig.js +3 -45
- package/dist/module/CrowdViewWidget/selectConfig.js.map +1 -1
- package/dist/module/CrowdViewWidget/types.js +19 -1
- package/dist/module/CrowdViewWidget/types.js.map +1 -1
- package/dist/module/translations/sources/en.json +21 -16
- package/dist/types/CrowdViewWidget/components/Chart/Chart.d.ts +1 -1
- package/dist/types/CrowdViewWidget/components/Chart/index.d.ts +1 -1
- package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +13 -8
- package/dist/types/CrowdViewWidget/components/Chart/utils/aggregateBuckets.d.ts +2 -0
- package/dist/types/CrowdViewWidget/components/Chart/utils/chartUtils.d.ts +11 -6
- package/dist/types/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.d.ts +3 -0
- package/dist/types/CrowdViewWidget/components/Chart/utils/index.d.ts +7 -0
- package/dist/types/CrowdViewWidget/components/Chart/utils/processBuckets.d.ts +3 -0
- package/dist/types/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.d.ts +8 -0
- package/dist/types/CrowdViewWidget/components/Chart/utils/processPriceCandles.d.ts +27 -0
- package/dist/types/CrowdViewWidget/components/Chart/utils/validateData.d.ts +2 -0
- package/dist/types/CrowdViewWidget/components/Legend/Legend.d.ts +3 -1
- package/dist/types/CrowdViewWidget/constants.d.ts +12 -4
- package/dist/types/CrowdViewWidget/selectConfig.d.ts +2 -2
- package/dist/types/CrowdViewWidget/types.d.ts +18 -1
- package/dist/types/CrowdViewWidget/utils/instrumentUtils.d.ts +1 -4
- package/package.json +4 -3
- package/src/CrowdViewWidget/Main.tsx +46 -40
- package/src/CrowdViewWidget/components/Chart/Chart.tsx +2 -2
- package/src/CrowdViewWidget/components/Chart/ChartWithData.tsx +4 -26
- package/src/CrowdViewWidget/components/Chart/chartOptions.ts +41 -27
- package/src/CrowdViewWidget/components/Chart/index.ts +1 -1
- package/src/CrowdViewWidget/components/Chart/types.ts +14 -4
- package/src/CrowdViewWidget/components/Chart/useCrowdViewData.ts +30 -154
- package/src/CrowdViewWidget/components/Chart/utils/aggregateBuckets.ts +44 -0
- package/src/CrowdViewWidget/components/Chart/utils/chartUtils.ts +41 -12
- package/src/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.ts +13 -0
- package/src/CrowdViewWidget/components/Chart/utils/index.ts +7 -0
- package/src/CrowdViewWidget/components/Chart/utils/processBuckets.ts +43 -0
- package/src/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.ts +30 -0
- package/src/CrowdViewWidget/components/Chart/utils/processPriceCandles.ts +53 -0
- package/src/CrowdViewWidget/components/Chart/utils/validateData.ts +27 -0
- package/src/CrowdViewWidget/components/Legend/Legend.tsx +14 -3
- package/src/CrowdViewWidget/components/Legend/LegendBar.tsx +5 -2
- package/src/CrowdViewWidget/constants.ts +114 -4
- package/src/CrowdViewWidget/selectConfig.ts +5 -60
- package/src/CrowdViewWidget/types.ts +18 -1
- package/src/translations/sources/en.json +21 -16
- package/test/Main.test.tsx +1 -1
- package/test/components/Chart/utils/chartUtils.test.ts +13 -27
- package/test/components/Legend.test.tsx +8 -3
- package/test/components/LegendBar.test.tsx +3 -2
- package/test/utils/aggregateBuckets.test.ts +82 -0
- package/test/utils/getTargetBucketWidth.test.ts +37 -0
- package/test/utils/instrumentUtils.test.ts +13 -7
- package/test/utils/processBuckets.test.ts +153 -0
- package/test/utils/processOrderPositionBooks.test.ts +127 -0
- package/test/utils/processPriceCandles.test.ts +245 -0
- package/test/utils/validateData.test.ts +201 -0
- package/dist/main/CrowdViewWidget/types/index.js +0 -17
- package/dist/main/CrowdViewWidget/types/index.js.map +0 -1
- package/dist/main/CrowdViewWidget/types/instruments.js +0 -45
- package/dist/main/CrowdViewWidget/types/instruments.js.map +0 -1
- package/dist/module/CrowdViewWidget/types/index.js +0 -2
- package/dist/module/CrowdViewWidget/types/index.js.map +0 -1
- package/dist/module/CrowdViewWidget/types/instruments.js +0 -39
- package/dist/module/CrowdViewWidget/types/instruments.js.map +0 -1
- package/dist/types/CrowdViewWidget/types/index.d.ts +0 -1
- package/dist/types/CrowdViewWidget/types/instruments.d.ts +0 -36
- package/src/CrowdViewWidget/types/index.ts +0 -1
- 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
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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-
|
|
83
|
-
'lw-w-[280px]': isDesktop,
|
|
66
|
+
className={cn('lw-mb-6 lw-mt-12', {
|
|
67
|
+
'lw-flex': isDesktop,
|
|
84
68
|
})}
|
|
85
69
|
>
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
{
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
],
|
|
@@ -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:
|
|
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:
|
|
27
|
+
orderPositionBooks: [string, number | null, number][];
|
|
22
28
|
bucketWidth: number;
|
|
23
|
-
buckets:
|
|
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:
|
|
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 {
|
|
13
|
+
import { BUCKET_CONFIG, INSTRUMENTS_CONFIG } from '../../constants';
|
|
14
14
|
import type { UseCrowdViewDataProps, UseCrowdViewDataReturn } from './types';
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
+
};
|