@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.
- package/CHANGELOG.md +188 -0
- package/dist/main/CrowdViewWidget/Main.js +3 -1
- package/dist/main/CrowdViewWidget/Main.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js +16 -5
- 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 +5 -3
- package/dist/main/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
- package/dist/main/CrowdViewWidget/constants.js +104 -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 +3 -1
- package/dist/module/CrowdViewWidget/Main.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js +16 -5
- 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 +5 -3
- package/dist/module/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
- package/dist/module/CrowdViewWidget/constants.js +103 -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/index.d.ts +1 -1
- package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +11 -7
- 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 +11 -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 +2 -3
- package/src/CrowdViewWidget/components/Chart/chartOptions.ts +35 -25
- package/src/CrowdViewWidget/components/Chart/index.ts +1 -1
- package/src/CrowdViewWidget/components/Chart/types.ts +12 -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 +37 -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 +13 -2
- package/src/CrowdViewWidget/constants.ts +113 -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 +12 -26
- package/test/components/Legend.test.tsx +6 -1
- 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
|
@@ -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
|
-
{
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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',
|
|
@@ -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 {
|
|
@@ -42,7 +50,7 @@ export interface ChartProps {
|
|
|
42
50
|
export interface ChartWithDataProps {
|
|
43
51
|
bookType: BookType;
|
|
44
52
|
division: Division;
|
|
45
|
-
instrument:
|
|
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 {
|
|
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
|
+
};
|
|
@@ -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
|
|
97
|
-
buckets
|
|
98
|
-
bucketWidth
|
|
99
|
-
selectedPrice
|
|
100
|
-
|
|
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(
|
|
143
|
-
<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,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
|
+
};
|