@oanda/labs-crowd-view-widget 1.0.44 → 1.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/CHANGELOG.md +372 -0
  2. package/dist/main/CrowdViewWidget/Main.js +20 -7
  3. package/dist/main/CrowdViewWidget/Main.js.map +1 -1
  4. package/dist/main/CrowdViewWidget/components/Chart/Chart.js +6 -10
  5. package/dist/main/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  6. package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js +46 -20
  7. package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
  8. package/dist/main/CrowdViewWidget/components/Chart/index.js +4 -4
  9. package/dist/main/CrowdViewWidget/components/Chart/index.js.map +1 -1
  10. package/dist/main/CrowdViewWidget/components/Chart/types.js.map +1 -1
  11. package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js +18 -88
  12. package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
  13. package/dist/main/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js +37 -0
  14. package/dist/main/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js.map +1 -0
  15. package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js +54 -2
  16. package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
  17. package/dist/main/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js +14 -0
  18. package/dist/main/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js.map +1 -0
  19. package/dist/main/CrowdViewWidget/components/Chart/utils/index.js +83 -0
  20. package/dist/main/CrowdViewWidget/components/Chart/utils/index.js.map +1 -0
  21. package/dist/main/CrowdViewWidget/components/Chart/utils/processBuckets.js +29 -0
  22. package/dist/main/CrowdViewWidget/components/Chart/utils/processBuckets.js.map +1 -0
  23. package/dist/main/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js +23 -0
  24. package/dist/main/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js.map +1 -0
  25. package/dist/main/CrowdViewWidget/components/Chart/utils/processPriceCandles.js +43 -0
  26. package/dist/main/CrowdViewWidget/components/Chart/utils/processPriceCandles.js.map +1 -0
  27. package/dist/main/CrowdViewWidget/components/Chart/utils/validateData.js +23 -0
  28. package/dist/main/CrowdViewWidget/components/Chart/utils/validateData.js.map +1 -0
  29. package/dist/main/CrowdViewWidget/components/Legend/Legend.js +5 -3
  30. package/dist/main/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  31. package/dist/main/CrowdViewWidget/constants.js +105 -5
  32. package/dist/main/CrowdViewWidget/constants.js.map +1 -1
  33. package/dist/main/CrowdViewWidget/selectConfig.js +18 -60
  34. package/dist/main/CrowdViewWidget/selectConfig.js.map +1 -1
  35. package/dist/main/CrowdViewWidget/types.js +20 -0
  36. package/dist/main/CrowdViewWidget/types.js.map +1 -1
  37. package/dist/main/translations/sources/en.json +29 -0
  38. package/dist/module/CrowdViewWidget/Main.js +21 -8
  39. package/dist/module/CrowdViewWidget/Main.js.map +1 -1
  40. package/dist/module/CrowdViewWidget/components/Chart/Chart.js +7 -11
  41. package/dist/module/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  42. package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js +47 -21
  43. package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
  44. package/dist/module/CrowdViewWidget/components/Chart/index.js +1 -1
  45. package/dist/module/CrowdViewWidget/components/Chart/index.js.map +1 -1
  46. package/dist/module/CrowdViewWidget/components/Chart/types.js.map +1 -1
  47. package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js +14 -84
  48. package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
  49. package/dist/module/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js +29 -0
  50. package/dist/module/CrowdViewWidget/components/Chart/utils/aggregateBuckets.js.map +1 -0
  51. package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js +52 -2
  52. package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
  53. package/dist/module/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js +7 -0
  54. package/dist/module/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.js.map +1 -0
  55. package/dist/module/CrowdViewWidget/components/Chart/utils/index.js +8 -0
  56. package/dist/module/CrowdViewWidget/components/Chart/utils/index.js.map +1 -0
  57. package/dist/module/CrowdViewWidget/components/Chart/utils/processBuckets.js +22 -0
  58. package/dist/module/CrowdViewWidget/components/Chart/utils/processBuckets.js.map +1 -0
  59. package/dist/module/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js +16 -0
  60. package/dist/module/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.js.map +1 -0
  61. package/dist/module/CrowdViewWidget/components/Chart/utils/processPriceCandles.js +36 -0
  62. package/dist/module/CrowdViewWidget/components/Chart/utils/processPriceCandles.js.map +1 -0
  63. package/dist/module/CrowdViewWidget/components/Chart/utils/validateData.js +16 -0
  64. package/dist/module/CrowdViewWidget/components/Chart/utils/validateData.js.map +1 -0
  65. package/dist/module/CrowdViewWidget/components/Legend/Legend.js +5 -3
  66. package/dist/module/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  67. package/dist/module/CrowdViewWidget/constants.js +104 -4
  68. package/dist/module/CrowdViewWidget/constants.js.map +1 -1
  69. package/dist/module/CrowdViewWidget/selectConfig.js +3 -45
  70. package/dist/module/CrowdViewWidget/selectConfig.js.map +1 -1
  71. package/dist/module/CrowdViewWidget/types.js +19 -1
  72. package/dist/module/CrowdViewWidget/types.js.map +1 -1
  73. package/dist/module/translations/sources/en.json +29 -0
  74. package/dist/types/CrowdViewWidget/components/Chart/index.d.ts +1 -1
  75. package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +12 -4
  76. package/dist/types/CrowdViewWidget/components/Chart/utils/aggregateBuckets.d.ts +2 -0
  77. package/dist/types/CrowdViewWidget/components/Chart/utils/chartUtils.d.ts +12 -2
  78. package/dist/types/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.d.ts +3 -0
  79. package/dist/types/CrowdViewWidget/components/Chart/utils/index.d.ts +7 -0
  80. package/dist/types/CrowdViewWidget/components/Chart/utils/processBuckets.d.ts +3 -0
  81. package/dist/types/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.d.ts +8 -0
  82. package/dist/types/CrowdViewWidget/components/Chart/utils/processPriceCandles.d.ts +27 -0
  83. package/dist/types/CrowdViewWidget/components/Chart/utils/validateData.d.ts +2 -0
  84. package/dist/types/CrowdViewWidget/components/Legend/Legend.d.ts +3 -1
  85. package/dist/types/CrowdViewWidget/constants.d.ts +11 -3
  86. package/dist/types/CrowdViewWidget/selectConfig.d.ts +2 -2
  87. package/dist/types/CrowdViewWidget/types.d.ts +18 -1
  88. package/dist/types/CrowdViewWidget/utils/instrumentUtils.d.ts +1 -4
  89. package/lokalise.config.json +1 -1
  90. package/package.json +4 -3
  91. package/src/CrowdViewWidget/Main.tsx +25 -10
  92. package/src/CrowdViewWidget/components/Chart/Chart.tsx +6 -12
  93. package/src/CrowdViewWidget/components/Chart/chartOptions.ts +70 -36
  94. package/src/CrowdViewWidget/components/Chart/index.ts +1 -1
  95. package/src/CrowdViewWidget/components/Chart/types.ts +16 -4
  96. package/src/CrowdViewWidget/components/Chart/useCrowdViewData.ts +41 -140
  97. package/src/CrowdViewWidget/components/Chart/utils/aggregateBuckets.ts +44 -0
  98. package/src/CrowdViewWidget/components/Chart/utils/chartUtils.ts +96 -3
  99. package/src/CrowdViewWidget/components/Chart/utils/getTargetBucketWidth.ts +13 -0
  100. package/src/CrowdViewWidget/components/Chart/utils/index.ts +7 -0
  101. package/src/CrowdViewWidget/components/Chart/utils/processBuckets.ts +43 -0
  102. package/src/CrowdViewWidget/components/Chart/utils/processOrderPositionBooks.ts +30 -0
  103. package/src/CrowdViewWidget/components/Chart/utils/processPriceCandles.ts +53 -0
  104. package/src/CrowdViewWidget/components/Chart/utils/validateData.ts +27 -0
  105. package/src/CrowdViewWidget/components/Legend/Legend.tsx +13 -2
  106. package/src/CrowdViewWidget/constants.ts +113 -3
  107. package/src/CrowdViewWidget/selectConfig.ts +5 -60
  108. package/src/CrowdViewWidget/types.ts +18 -1
  109. package/src/translations/sources/en.json +29 -0
  110. package/test/Main.test.tsx +73 -27
  111. package/test/components/Chart/utils/chartUtils.test.ts +158 -0
  112. package/test/components/Legend.test.tsx +6 -1
  113. package/test/utils/aggregateBuckets.test.ts +82 -0
  114. package/test/utils/getTargetBucketWidth.test.ts +37 -0
  115. package/test/utils/instrumentUtils.test.ts +13 -7
  116. package/test/utils/processBuckets.test.ts +153 -0
  117. package/test/utils/processOrderPositionBooks.test.ts +127 -0
  118. package/test/utils/processPriceCandles.test.ts +245 -0
  119. package/test/utils/validateData.test.ts +201 -0
  120. package/dist/main/CrowdViewWidget/types/index.js +0 -17
  121. package/dist/main/CrowdViewWidget/types/index.js.map +0 -1
  122. package/dist/main/CrowdViewWidget/types/instruments.js +0 -45
  123. package/dist/main/CrowdViewWidget/types/instruments.js.map +0 -1
  124. package/dist/module/CrowdViewWidget/types/index.js +0 -2
  125. package/dist/module/CrowdViewWidget/types/index.js.map +0 -1
  126. package/dist/module/CrowdViewWidget/types/instruments.js +0 -39
  127. package/dist/module/CrowdViewWidget/types/instruments.js.map +0 -1
  128. package/dist/types/CrowdViewWidget/types/index.d.ts +0 -1
  129. package/dist/types/CrowdViewWidget/types/instruments.d.ts +0 -36
  130. package/src/CrowdViewWidget/types/index.ts +0 -1
  131. package/src/CrowdViewWidget/types/instruments.ts +0 -37
@@ -1,10 +1,12 @@
1
+ import { InstrumentId } from './types';
2
+
1
3
  export const BOOKS_THRESHOLDS = {
2
4
  MIN: 0.15,
3
5
  MAX: 0.55,
4
6
  } as const;
5
7
 
6
8
  export const BUCKET_CONFIG = {
7
- DEFAULT_WIDTH: 0.0005,
9
+ MULTIPLIER: 4,
8
10
  PRICE_PADDING_MULTIPLIER: 2,
9
11
  } as const;
10
12
 
@@ -19,9 +21,117 @@ export const CHART_CONFIG = {
19
21
  Y_LABEL_SIZE_DESKTOP: 60,
20
22
  INITIAL_START_ZOOM: 80,
21
23
  INITIAL_END_ZOOM: 100,
24
+ X_AXIS_DATE_PADDING: ' ',
22
25
  } as const;
23
26
 
24
27
  export const COLOR_MAP = {
25
- long: ['#ffffff', '#fdb833'],
26
- short: ['#ffffff', '#0096c7'],
28
+ long: ['#eaf5fa', '#83c4e0'],
29
+ short: ['#fef7e7', '#fcd171'],
27
30
  } as const;
31
+
32
+ export const INSTRUMENTS_CONFIG: Record<
33
+ InstrumentId,
34
+ {
35
+ precision: number;
36
+ defaultBucketWidth: number;
37
+ v20name: string;
38
+ mt5name: string;
39
+ }
40
+ > = {
41
+ [InstrumentId.EUR_AUD]: {
42
+ mt5name: 'EURAUD',
43
+ v20name: 'EUR_AUD',
44
+ precision: 5,
45
+ defaultBucketWidth: 0.0005,
46
+ },
47
+ [InstrumentId.EUR_GBP]: {
48
+ mt5name: 'EURGBP',
49
+ v20name: 'EUR_GBP',
50
+ precision: 5,
51
+ defaultBucketWidth: 0.0005,
52
+ },
53
+ [InstrumentId.EUR_JPY]: {
54
+ mt5name: 'EURJPY',
55
+ v20name: 'EUR_JPY',
56
+ precision: 3,
57
+ defaultBucketWidth: 0.05,
58
+ },
59
+ [InstrumentId.EUR_USD]: {
60
+ mt5name: 'EURUSD',
61
+ v20name: 'EUR_USD',
62
+ precision: 5,
63
+ defaultBucketWidth: 0.0005,
64
+ },
65
+ [InstrumentId.EUR_CHF]: {
66
+ mt5name: 'EURCHF',
67
+ v20name: 'EUR_CHF',
68
+ precision: 5,
69
+ defaultBucketWidth: 0.0005,
70
+ },
71
+ [InstrumentId.USD_CHF]: {
72
+ mt5name: 'USDCHF',
73
+ v20name: 'USD_CHF',
74
+ precision: 5,
75
+ defaultBucketWidth: 0.0005,
76
+ },
77
+ [InstrumentId.USD_JPY]: {
78
+ mt5name: 'USDJPY',
79
+ v20name: 'USD_JPY',
80
+ precision: 3,
81
+ defaultBucketWidth: 0.05,
82
+ },
83
+ [InstrumentId.USD_CAD]: {
84
+ mt5name: 'USDCAD',
85
+ v20name: 'USD_CAD',
86
+ precision: 5,
87
+ defaultBucketWidth: 0.0005,
88
+ },
89
+ [InstrumentId.GBP_USD]: {
90
+ mt5name: 'GBPUSD',
91
+ v20name: 'GBP_USD',
92
+ precision: 5,
93
+ defaultBucketWidth: 0.0005,
94
+ },
95
+ [InstrumentId.GBP_JPY]: {
96
+ mt5name: 'GBPJPY',
97
+ v20name: 'GBP_JPY',
98
+ precision: 3,
99
+ defaultBucketWidth: 0.05,
100
+ },
101
+ [InstrumentId.GBP_CHF]: {
102
+ mt5name: 'GBPCHF',
103
+ v20name: 'GBP_CHF',
104
+ precision: 5,
105
+ defaultBucketWidth: 0.0005,
106
+ },
107
+ [InstrumentId.AUD_JPY]: {
108
+ mt5name: 'AUDJPY',
109
+ v20name: 'AUD_JPY',
110
+ precision: 3,
111
+ defaultBucketWidth: 0.05,
112
+ },
113
+ [InstrumentId.AUD_USD]: {
114
+ mt5name: 'AUDUSD',
115
+ v20name: 'AUD_USD',
116
+ precision: 5,
117
+ defaultBucketWidth: 0.0005,
118
+ },
119
+ [InstrumentId.NZD_USD]: {
120
+ mt5name: 'NZDUSD',
121
+ v20name: 'NZD_USD',
122
+ precision: 5,
123
+ defaultBucketWidth: 0.0005,
124
+ },
125
+ [InstrumentId.XAU_USD]: {
126
+ mt5name: 'XAUUSD',
127
+ v20name: 'XAU_USD',
128
+ precision: 3,
129
+ defaultBucketWidth: 0.5,
130
+ },
131
+ [InstrumentId.XAG_USD]: {
132
+ mt5name: 'XAGUSD',
133
+ v20name: 'XAG_USD',
134
+ precision: 5,
135
+ defaultBucketWidth: 0.0005,
136
+ },
137
+ };
@@ -1,5 +1,5 @@
1
1
  import { BookType, Granularity } from '../gql/types/graphql';
2
- import { InstrumentId, InstrumentIdOC } from './types/instruments';
2
+ import { InstrumentId } from './types';
3
3
 
4
4
  const navigationConfig = [
5
5
  {
@@ -13,65 +13,6 @@ const navigationConfig = [
13
13
  ];
14
14
 
15
15
  const instrumentSelectConfigOC = [
16
- {
17
- id: InstrumentIdOC.EUR_AUD,
18
- label: 'EUR/AUD',
19
- },
20
- {
21
- id: InstrumentIdOC.EUR_GBP,
22
- label: 'EUR/GBP',
23
- },
24
- {
25
- id: InstrumentIdOC.EUR_JPY,
26
- label: 'EUR/JPY',
27
- },
28
- {
29
- id: InstrumentIdOC.EUR_USD,
30
- label: 'EUR/USD',
31
- },
32
- {
33
- id: InstrumentIdOC.EUR_CHF,
34
- label: 'EUR/CHF',
35
- },
36
- {
37
- id: InstrumentIdOC.USD_CHF,
38
- label: 'USD/CHF',
39
- },
40
- {
41
- id: InstrumentIdOC.USD_JPY,
42
- label: 'USD/JPY',
43
- },
44
- {
45
- id: InstrumentIdOC.USD_CAD,
46
- label: 'USD/CAD',
47
- },
48
- {
49
- id: InstrumentIdOC.GBP_USD,
50
- label: 'GBP/USD',
51
- },
52
- {
53
- id: InstrumentIdOC.GBP_JPY,
54
- label: 'GBP/JPY',
55
- },
56
- {
57
- id: InstrumentIdOC.GBP_CHF,
58
- label: 'GBP/CHF',
59
- },
60
- {
61
- id: InstrumentIdOC.AUD_JPY,
62
- label: 'AUD/JPY',
63
- },
64
- {
65
- id: InstrumentIdOC.AUD_USD,
66
- label: 'AUD/USD',
67
- },
68
- {
69
- id: InstrumentIdOC.NZD_USD,
70
- label: 'NZD/USD',
71
- },
72
- ];
73
-
74
- const instrumentSelectConfig = [
75
16
  {
76
17
  id: InstrumentId.EUR_AUD,
77
18
  label: 'EUR/AUD',
@@ -128,6 +69,10 @@ const instrumentSelectConfig = [
128
69
  id: InstrumentId.NZD_USD,
129
70
  label: 'NZD/USD',
130
71
  },
72
+ ];
73
+
74
+ const instrumentSelectConfig = [
75
+ ...instrumentSelectConfigOC,
131
76
  {
132
77
  id: InstrumentId.XAU_USD,
133
78
  label: 'XAU/USD',
@@ -1,8 +1,25 @@
1
1
  import type { WidgetConfig } from '@oanda/labs-widget-common';
2
2
 
3
3
  import type { Division } from '../gql/types/graphql';
4
- import type { InstrumentId } from './types/instruments';
5
4
 
5
+ export enum InstrumentId {
6
+ EUR_AUD = 'EURAUD',
7
+ EUR_GBP = 'EURGBP',
8
+ EUR_JPY = 'EURJPY',
9
+ EUR_USD = 'EURUSD',
10
+ EUR_CHF = 'EURCHF',
11
+ USD_CHF = 'USDCHF',
12
+ USD_JPY = 'USDJPY',
13
+ USD_CAD = 'USDCAD',
14
+ GBP_USD = 'GBPUSD',
15
+ GBP_JPY = 'GBPJPY',
16
+ GBP_CHF = 'GBPCHF',
17
+ AUD_JPY = 'AUDJPY',
18
+ AUD_USD = 'AUDUSD',
19
+ NZD_USD = 'NZDUSD',
20
+ XAU_USD = 'XAUUSD',
21
+ XAG_USD = 'XAGUSD',
22
+ }
6
23
  export interface CrowdViewConfig extends WidgetConfig {
7
24
  division: Division;
8
25
  }
@@ -1,2 +1,31 @@
1
1
  {
2
+ "1_hour": "1 hour",
3
+ "15_minutes": "15 minutes",
4
+ "4_hours": "4 hours",
5
+ "5_minutes": "5 minutes",
6
+ "buy_advantage": "Buy advantage",
7
+ "buy": "Buy",
8
+ "candle": "Candle",
9
+ "close_price": "Close price",
10
+ "data_unavailable": "Data unavailable",
11
+ "granularity": "Granularity",
12
+ "high": "High",
13
+ "instrument": "Instrument",
14
+ "long_advantage": "Sell advantage",
15
+ "long": "Long",
16
+ "low": "Low",
17
+ "no_matching_results": "No matching results",
18
+ "open_price": "Open price",
19
+ "order_book": "Order book",
20
+ "orders": "Orders",
21
+ "pagination_entries_range": "{{firstItemOnPage}}-{{lastItemOnPage}} of {{itemCount}} entries",
22
+ "position_book": "Position book",
23
+ "positions": "Positions",
24
+ "price_range": "Price range",
25
+ "search": "Search",
26
+ "sell_advantage": "Sell advantage",
27
+ "sell": "Sell",
28
+ "sentiment": "Sentiment",
29
+ "short_advantage": "Buy advantage",
30
+ "short": "Short"
2
31
  }
@@ -7,48 +7,94 @@ import { render } from '@testing-library/react';
7
7
  import React from 'react';
8
8
 
9
9
  import { Main } from '../src/CrowdViewWidget/Main';
10
- import { InstrumentId } from '../src/CrowdViewWidget/types/instruments';
10
+ import { InstrumentId } from '../src/CrowdViewWidget/types';
11
11
  import { getOrderPositionBooks } from '../src/gql/getOrderPositionBooks';
12
- import { BookType, Division } from '../src/gql/types/graphql';
12
+ import { getPriceCandles } from '../src/gql/getPriceCandles';
13
+ import {
14
+ BookType,
15
+ DataSource,
16
+ Division,
17
+ Granularity,
18
+ TimeSpan,
19
+ } from '../src/gql/types/graphql';
20
+
21
+ const instrument = InstrumentId.EUR_AUD;
22
+ const division = Division.Oap;
23
+ const granularity = Granularity.H4;
24
+ const timeSpan = TimeSpan.NinetyDays;
25
+
26
+ const candles = [
27
+ {
28
+ point: '2024-02-21T10:00:00Z',
29
+ high: 1.334,
30
+ low: 1.33,
31
+ open: 1.331,
32
+ close: 1.333,
33
+ },
34
+ {
35
+ point: '2024-02-21T10:30:00Z',
36
+ high: 1.335,
37
+ low: 1.3295,
38
+ open: 1.33,
39
+ close: 1.334,
40
+ },
41
+ {
42
+ point: '2024-02-21T11:00:00Z',
43
+ high: 1.333,
44
+ low: 1.329,
45
+ open: 1.331,
46
+ close: 1.332,
47
+ },
48
+ ];
49
+
50
+ const maxPrice = 1.335;
51
+ const minPrice = 1.329;
52
+ const bucketWidth = 0.0005;
53
+ const maxBookPrice = maxPrice + bucketWidth * 2;
54
+ const minBookPrice = minPrice - bucketWidth * 2;
13
55
 
14
56
  const mocks = [
57
+ {
58
+ request: {
59
+ query: getPriceCandles,
60
+ variables: {
61
+ dataSource: DataSource.Mt5,
62
+ division,
63
+ instrument,
64
+ granularity,
65
+ timeSpan,
66
+ },
67
+ },
68
+ result: {
69
+ data: {
70
+ priceCandles: {
71
+ candle: candles,
72
+ },
73
+ },
74
+ },
75
+ },
15
76
  {
16
77
  request: {
17
78
  query: getOrderPositionBooks,
18
79
  variables: {
19
- instrument: InstrumentId.EUR_AUD,
80
+ instrument,
20
81
  bookType: BookType.Order,
21
- recentHours: 1,
82
+ timeSpan,
83
+ granularity,
84
+ maxBookPrice,
85
+ minBookPrice,
22
86
  },
23
87
  },
24
88
  result: {
25
89
  data: {
26
90
  orderPositionBooks: [
27
91
  {
28
- bucketWidth: 0.0005,
29
- price: 0.8,
92
+ bucketWidth,
93
+ price: maxPrice,
30
94
  time: '2024-02-21T10:30:00Z',
31
95
  buckets: [
32
- {
33
- price: 0.02,
34
- longCountPercent: 0.0582,
35
- shortCountPercent: 0.0,
36
- },
37
- {
38
- price: 0.7,
39
- longCountPercent: 0.0582,
40
- shortCountPercent: 0.0,
41
- },
42
- {
43
- price: 0.9925,
44
- longCountPercent: 0.0582,
45
- shortCountPercent: 0.0,
46
- },
47
- {
48
- price: 1.0,
49
- longCountPercent: 0.1163,
50
- shortCountPercent: 0.0,
51
- },
96
+ { price: 1.3345, sentiment: 0.2 },
97
+ { price: 1.3305, sentiment: -0.3 },
52
98
  ],
53
99
  },
54
100
  ],
@@ -62,7 +108,7 @@ describe('Main component', () => {
62
108
  const { findByTestId } = render(
63
109
  <MockedProvider mocks={mocks}>
64
110
  <MockLayoutProvider>
65
- <Main division={Division.Oap} />
111
+ <Main division={division} />
66
112
  </MockLayoutProvider>
67
113
  </MockedProvider>
68
114
  );
@@ -0,0 +1,158 @@
1
+ import {
2
+ formatXAxisLabel,
3
+ getLabelData,
4
+ getRectColor,
5
+ getTimeSpanForGranularity,
6
+ getTooltipFormatter,
7
+ isDifferenceGreaterThanTwoWeeks,
8
+ } from '../../../../src/CrowdViewWidget/components/Chart/utils/chartUtils';
9
+ import {
10
+ BOOKS_THRESHOLDS,
11
+ COLOR_MAP,
12
+ } from '../../../../src/CrowdViewWidget/constants';
13
+ import {
14
+ BookType,
15
+ Granularity,
16
+ TimeSpan,
17
+ } from '../../../../src/gql/types/graphql';
18
+
19
+ describe('chartUtils', () => {
20
+ describe('getTimeSpanForGranularity', () => {
21
+ it('maps granularity to expected TimeSpan', () => {
22
+ expect(getTimeSpanForGranularity(Granularity.M5)).toBe(TimeSpan.TwoDays);
23
+ expect(getTimeSpanForGranularity(Granularity.M15)).toBe(
24
+ TimeSpan.FiveDays
25
+ );
26
+ expect(getTimeSpanForGranularity(Granularity.H1)).toBe(
27
+ TimeSpan.TwentyDays
28
+ );
29
+ expect(getTimeSpanForGranularity(Granularity.H4)).toBe(
30
+ TimeSpan.NinetyDays
31
+ );
32
+ });
33
+ });
34
+
35
+ describe('isDifferenceGreaterThanTwoWeeks', () => {
36
+ // Note: Function returns true when difference is LESS than threshold (14 days)
37
+ it('returns true when time difference is less than 14 days', () => {
38
+ const start = new Date('2025-01-01T00:00:00Z').toISOString();
39
+ const end = new Date('2025-01-05T00:00:00Z').toISOString();
40
+ expect(isDifferenceGreaterThanTwoWeeks(start, end)).toBe(true);
41
+ });
42
+
43
+ it('returns false when time difference is 14 days or more', () => {
44
+ const start = new Date('2025-01-01T00:00:00Z').toISOString();
45
+ const end = new Date('2025-02-10T00:00:00Z').toISOString();
46
+ expect(isDifferenceGreaterThanTwoWeeks(start, end)).toBe(false);
47
+ });
48
+ });
49
+
50
+ describe('getRectColor', () => {
51
+ it('uses long color scale for positive sentiment', () => {
52
+ const color = getRectColor(BOOKS_THRESHOLDS.MAX);
53
+ expect(typeof color).toBe('string');
54
+ // At max threshold, should be at or near target color
55
+ expect(color.toLowerCase()).toContain(
56
+ COLOR_MAP.long[1].slice(1).toLowerCase().substring(0, 3)
57
+ );
58
+ });
59
+
60
+ it('uses short color scale for negative sentiment', () => {
61
+ const color = getRectColor(-BOOKS_THRESHOLDS.MAX);
62
+ expect(typeof color).toBe('string');
63
+ expect(color.toLowerCase()).toContain(
64
+ COLOR_MAP.short[1].slice(1).toLowerCase().substring(0, 3)
65
+ );
66
+ });
67
+ });
68
+
69
+ describe('formatXAxisLabel', () => {
70
+ const sampleIso = '2025-03-15T10:30:00Z';
71
+
72
+ it('formats time when flag is true', () => {
73
+ const result = formatXAxisLabel(sampleIso, true);
74
+ expect(typeof result).toBe('string');
75
+ expect(result).toContain(':');
76
+ });
77
+
78
+ it('formats day when flag is false', () => {
79
+ const result = formatXAxisLabel(sampleIso, false);
80
+ expect(typeof result).toBe('string');
81
+ // Contains day of month (15) with surrounding spaces per implementation
82
+ expect(result).toMatch(/\s15\s/);
83
+ });
84
+ });
85
+
86
+ describe('getLabelData', () => {
87
+ const dates = [
88
+ '2025-03-01T00:00:00Z',
89
+ '2025-03-01T12:00:00Z',
90
+ '2025-03-02T00:00:00Z',
91
+ '2025-03-03T00:00:00Z',
92
+ ];
93
+
94
+ it('emits label when day changes for < two weeks case', () => {
95
+ const labels = getLabelData({
96
+ xAxisData: dates,
97
+ isGreaterThanTwoWeeks: true,
98
+ });
99
+ // First change happens between 1st and 2nd
100
+ expect(labels.length).toBeGreaterThanOrEqual(2);
101
+ expect(labels[0]).toHaveProperty('xAxis', '2025-03-02T00:00:00Z');
102
+ });
103
+
104
+ it('emits label when month changes for >= two weeks case', () => {
105
+ const monthSpanDates = [
106
+ '2025-01-31T00:00:00Z',
107
+ '2025-02-01T00:00:00Z',
108
+ '2025-02-15T00:00:00Z',
109
+ ];
110
+ const labels = getLabelData({
111
+ xAxisData: monthSpanDates,
112
+ isGreaterThanTwoWeeks: false,
113
+ });
114
+ expect(labels.length).toBe(1);
115
+ expect(labels[0]).toHaveProperty('xAxis', '2025-02-01T00:00:00Z');
116
+ });
117
+ });
118
+
119
+ describe('getTooltipFormatter', () => {
120
+ const labelCallback = (k: string) => k;
121
+
122
+ it('renders candle and book details when available', () => {
123
+ const params = [
124
+ {
125
+ axisValue: '2025-03-15T10:30:00Z',
126
+ value: [0, 1.11111, 1.22222, 1.00001, 1.33333],
127
+ },
128
+ { value: ['2025-03-15T10:30:00Z', 1.33333, 0] },
129
+ ];
130
+
131
+ const buckets = [
132
+ [
133
+ { price: 1.33, sentiment: 0.2 },
134
+ { price: 1.3305, sentiment: -0.3 },
135
+ ],
136
+ ];
137
+
138
+ const html = getTooltipFormatter({
139
+ params,
140
+ buckets,
141
+ bucketWidth: 0.0005,
142
+ selectedPrice: 1.3306,
143
+ precision: 5,
144
+ bookType: BookType.Order,
145
+ labelCallback,
146
+ });
147
+ expect(html).toContain('candle');
148
+ expect(html).toContain('open_price');
149
+ expect(html).toContain('close_price');
150
+ expect(html).toContain('low');
151
+ expect(html).toContain('high');
152
+ expect(html).toContain('orders');
153
+ expect(html).toContain('price_range');
154
+ // Selected price 1.3306 falls into second bucket 1.3305 - 1.3310 which has negative sentiment
155
+ expect(html).toContain('sell_advantage');
156
+ });
157
+ });
158
+ });
@@ -6,13 +6,18 @@ import { render } from '@testing-library/react';
6
6
  import React from 'react';
7
7
 
8
8
  import { Legend } from '../../src/CrowdViewWidget/components';
9
+ import { BookType } from '../../src/gql/types/graphql';
9
10
 
10
11
  describe('Crowd View Widget', () => {
11
12
  describe('components', () => {
12
13
  describe('<Legend />', () => {
13
14
  it('renders two LegendBar components', () => {
14
15
  const { getAllByText } = render(
15
- <Legend longValues={[0.15, 0.55]} shortValues={[0.15, 0.55]} />
16
+ <Legend
17
+ bookType={BookType.Position}
18
+ longValues={[0.15, 0.55]}
19
+ shortValues={[0.15, 0.55]}
20
+ />
16
21
  );
17
22
 
18
23
  expect(getAllByText(/long/)).toHaveLength(2);
@@ -0,0 +1,82 @@
1
+ import { aggregateBuckets } from '../../src/CrowdViewWidget/components';
2
+
3
+ describe('aggregateBuckets', () => {
4
+ it('should correctly aggregate a standard set of buckets', () => {
5
+ const buckets = [
6
+ { price: 1, sentiment: 1 },
7
+ { price: 1.05, sentiment: 2 },
8
+ { price: 1.1, sentiment: 4 },
9
+ { price: 1.15, sentiment: 3 },
10
+ { price: 1.2, sentiment: 7 },
11
+ { price: 1.35, sentiment: 8 },
12
+ { price: 1.6, sentiment: 10 },
13
+ { price: 1.65, sentiment: 1 },
14
+ { price: 1.8, sentiment: 18 },
15
+ ];
16
+ const newBucketWidth = 0.1;
17
+ const expected = [
18
+ { price: 1.0, sentiment: 3 },
19
+ { price: 1.1, sentiment: 7 },
20
+ { price: 1.2, sentiment: 7 },
21
+ { price: 1.3, sentiment: 8 },
22
+ { price: 1.6, sentiment: 11 },
23
+ { price: 1.8, sentiment: 18 },
24
+ ];
25
+
26
+ expect(aggregateBuckets(buckets, newBucketWidth)).toEqual(expected);
27
+ });
28
+
29
+ it('should return an empty array if the input array is empty', () => {
30
+ expect(aggregateBuckets([], 0.1)).toEqual([]);
31
+ });
32
+
33
+ it('should correctly process an array with a single bucket', () => {
34
+ const buckets = [{ price: 2.58, sentiment: 15 }];
35
+ const newBucketWidth = 0.2;
36
+ const expected = [{ price: 2.4, sentiment: 15 }];
37
+
38
+ expect(aggregateBuckets(buckets, newBucketWidth)).toEqual(expected);
39
+ });
40
+
41
+ it('should handle gaps in data without creating empty buckets', () => {
42
+ const buckets = [
43
+ { price: 10.1, sentiment: 5 }, // Belongs to bucket 10.0
44
+ { price: 10.9, sentiment: 8 }, // Belongs to bucket 10.8
45
+ ];
46
+ const newBucketWidth = 0.2;
47
+ const expected = [
48
+ { price: 10.0, sentiment: 5 },
49
+ { price: 10.8, sentiment: 8 },
50
+ ];
51
+
52
+ expect(aggregateBuckets(buckets, newBucketWidth)).toEqual(expected);
53
+ });
54
+
55
+ it('should aggregate all items into a single bucket if they fall within the new width', () => {
56
+ const buckets = [
57
+ { price: 1.1, sentiment: 10 },
58
+ { price: 1.5, sentiment: 20 },
59
+ { price: 1.9, sentiment: 30 },
60
+ ];
61
+ const newBucketWidth = 2.0;
62
+ const expected = [{ price: 0.0, sentiment: 60 }];
63
+
64
+ expect(aggregateBuckets(buckets, newBucketWidth)).toEqual(expected);
65
+ });
66
+
67
+ it('should correctly sum sentiments including zero and negative values', () => {
68
+ const buckets = [
69
+ { price: 5.0, sentiment: 100 },
70
+ { price: 5.05, sentiment: -20 },
71
+ { price: 5.11, sentiment: 0 },
72
+ { price: 5.18, sentiment: 5 },
73
+ ];
74
+ const newBucketWidth = 0.1;
75
+ const expected = [
76
+ { price: 5.0, sentiment: 80 },
77
+ { price: 5.1, sentiment: 5 },
78
+ ];
79
+
80
+ expect(aggregateBuckets(buckets, newBucketWidth)).toEqual(expected);
81
+ });
82
+ });
@@ -0,0 +1,37 @@
1
+ import { getTargetBucketWidth } from '../../src/CrowdViewWidget/components';
2
+ import {
3
+ BUCKET_CONFIG,
4
+ INSTRUMENTS_CONFIG,
5
+ } from '../../src/CrowdViewWidget/constants';
6
+ import { InstrumentId } from '../../src/CrowdViewWidget/types';
7
+ import { Granularity } from '../../src/gql/types/graphql';
8
+
9
+ describe('getTargetBucketWidth', () => {
10
+ const instrument = InstrumentId.EUR_AUD;
11
+ const { defaultBucketWidth } = INSTRUMENTS_CONFIG[instrument];
12
+ const optimizedBucketWidth = defaultBucketWidth * BUCKET_CONFIG.MULTIPLIER;
13
+
14
+ it('should return optimized width for H1 granularity', () => {
15
+ expect(getTargetBucketWidth(Granularity.H1, instrument)).toBe(
16
+ optimizedBucketWidth
17
+ );
18
+ });
19
+
20
+ it('should return optimized width for H4 granularity', () => {
21
+ expect(getTargetBucketWidth(Granularity.H4, instrument)).toBe(
22
+ optimizedBucketWidth
23
+ );
24
+ });
25
+
26
+ it('should return default width for M5 granularity', () => {
27
+ expect(getTargetBucketWidth(Granularity.M5, instrument)).toBe(
28
+ defaultBucketWidth
29
+ );
30
+ });
31
+
32
+ it('should return default width for M15 granularity', () => {
33
+ expect(getTargetBucketWidth(Granularity.M15, instrument)).toBe(
34
+ defaultBucketWidth
35
+ );
36
+ });
37
+ });