overtime-live-trading-utils 2.1.45 → 3.0.0-rc.1

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 (43) hide show
  1. package/.circleci/config.yml +32 -32
  2. package/.prettierrc +9 -9
  3. package/codecov.yml +20 -20
  4. package/index.ts +26 -26
  5. package/jest.config.ts +16 -16
  6. package/main.js +1 -1
  7. package/package.json +30 -30
  8. package/src/constants/common.ts +8 -7
  9. package/src/constants/errors.ts +7 -6
  10. package/src/constants/sports.ts +78 -78
  11. package/src/enums/sports.ts +109 -109
  12. package/src/tests/mock/MockLeagueMap.ts +225 -170
  13. package/src/tests/mock/MockOpticOddsEvents.ts +662 -662
  14. package/src/tests/mock/MockOpticSoccer.ts +9864 -9378
  15. package/src/tests/mock/MockSoccerRedis.ts +2308 -2308
  16. package/src/tests/mock/OpticOddsMock/MockNBA.ts +17269 -0
  17. package/src/tests/mock/OpticOddsMock/MockRedisNba.ts +21 -0
  18. package/src/tests/unit/bookmakers.test.ts +149 -79
  19. package/src/tests/unit/markets.test.ts +177 -156
  20. package/src/tests/unit/odds.test.ts +104 -92
  21. package/src/tests/unit/resolution.test.ts +1489 -1489
  22. package/src/tests/unit/sports.test.ts +58 -58
  23. package/src/tests/unit/spread.test.ts +145 -131
  24. package/src/tests/utils/helper.ts +11 -0
  25. package/src/types/bookmakers.ts +7 -0
  26. package/src/types/missing-types.d.ts +2 -2
  27. package/src/types/odds.ts +80 -61
  28. package/src/types/resolution.ts +656 -660
  29. package/src/types/sports.ts +22 -19
  30. package/src/utils/bookmakers.ts +309 -159
  31. package/src/utils/constraints.ts +210 -210
  32. package/src/utils/gameMatching.ts +81 -81
  33. package/src/utils/markets.ts +120 -119
  34. package/src/utils/odds.ts +952 -918
  35. package/src/utils/opticOdds.ts +71 -71
  36. package/src/utils/resolution.ts +319 -319
  37. package/src/utils/sportPeriodMapping.ts +36 -36
  38. package/src/utils/sports.ts +51 -51
  39. package/src/utils/spread.ts +97 -97
  40. package/tsconfig.json +16 -16
  41. package/webpack.config.js +24 -24
  42. package/CLAUDE.md +0 -84
  43. package/resolution_live_markets.md +0 -356
@@ -1,19 +1,22 @@
1
-
2
- export type LeagueConfigInfo = {
3
- sportId: number;
4
- typeId: number;
5
- marketName: string;
6
- type: string;
7
- enabled: string;
8
- minOdds: number;
9
- maxOdds: number;
10
- addedSpread?: number;
11
- };
12
-
13
- export type ChildMarket = {
14
- leagueId: number;
15
- typeId: number;
16
- type: string;
17
- line: number;
18
- odds: Array<number>;
19
- };
1
+ export type LeagueConfigInfo = {
2
+ sportId: number;
3
+ typeId: number;
4
+ marketName: string;
5
+ type: string;
6
+ enabled: string;
7
+ minOdds: number;
8
+ maxOdds: number;
9
+ addedSpread?: number;
10
+ primaryBookmaker?: string;
11
+ secondaryBookmaker?: string;
12
+ };
13
+
14
+ export type ChildMarket = {
15
+ leagueId: number;
16
+ typeId: number;
17
+ type: string;
18
+ line: number;
19
+ odds: Array<number>;
20
+ };
21
+
22
+ export type LastPolledArray = { sportsbook: string; timestamp: number }[];
@@ -1,159 +1,309 @@
1
- import * as oddslib from 'oddslib';
2
- import {
3
- DIFF_BETWEEN_BOOKMAKERS_MESSAGE,
4
- NO_MATCHING_BOOKMAKERS_MESSAGE,
5
- ZERO_ODDS_MESSAGE,
6
- ZERO_ODDS_MESSAGE_SINGLE_BOOKMAKER,
7
- } from '../constants/errors';
8
-
9
- export const getBookmakersArray = (bookmakersData: any[], sportId: any, backupLiveOddsProviders: string[]) => {
10
- const sportBookmakersData = bookmakersData.find((data) => Number(data.sportId) === Number(sportId));
11
- if (sportBookmakersData) {
12
- if (sportBookmakersData.primaryBookmaker == '') {
13
- return backupLiveOddsProviders;
14
- }
15
- const bookmakersArray: string[] = [];
16
-
17
- sportBookmakersData.primaryBookmaker ? bookmakersArray.push(sportBookmakersData.primaryBookmaker) : '';
18
- sportBookmakersData.secondaryBookmaker ? bookmakersArray.push(sportBookmakersData.secondaryBookmaker) : '';
19
- sportBookmakersData.tertiaryBookmaker ? bookmakersArray.push(sportBookmakersData.tertiaryBookmaker) : '';
20
-
21
- return bookmakersArray;
22
- }
23
- return backupLiveOddsProviders;
24
- };
25
-
26
- export const checkOddsFromBookmakers = (
27
- oddsMap: Map<string, any>,
28
- arrayOfBookmakers: string[],
29
- isTwoPositionalSport: boolean,
30
- maxImpliedPercentageDifference: number,
31
- minOddsForDiffChecking: number
32
- ) => {
33
- // Main bookmaker odds
34
- const firstBookmakerOdds = oddsMap.get(arrayOfBookmakers[0].toLowerCase());
35
-
36
- if (!firstBookmakerOdds) {
37
- // If no matching bookmakers are found, return zero odds
38
- return {
39
- homeOdds: 0,
40
- awayOdds: 0,
41
- drawOdds: 0,
42
- errorMessage: NO_MATCHING_BOOKMAKERS_MESSAGE,
43
- };
44
- }
45
-
46
- const homeOdd = firstBookmakerOdds.homeOdds;
47
- const awayOdd = firstBookmakerOdds.awayOdds;
48
- const drawOdd = isTwoPositionalSport ? 0 : firstBookmakerOdds.drawOdds;
49
-
50
- // Check if any bookmaker has odds of 0 or 0.0001
51
- const hasZeroOrOne = arrayOfBookmakers.some((bookmakerId) => {
52
- const line = oddsMap.get(bookmakerId);
53
- if (line) {
54
- return (
55
- line.homeOdds === 0 ||
56
- line.awayOdds === 0 ||
57
- (!isTwoPositionalSport && line.drawOdds === 0) ||
58
- line.homeOdds === 1 ||
59
- line.awayOdds === 1 ||
60
- (!isTwoPositionalSport && line.drawOdds === 1)
61
- );
62
- }
63
- return false; // fix for es-lint
64
- });
65
-
66
- if (hasZeroOrOne) {
67
- // If any bookmaker has zero odds, return zero odds
68
- return {
69
- homeOdds: 0,
70
- awayOdds: 0,
71
- drawOdds: 0,
72
- errorMessage: arrayOfBookmakers.length === 1 ? ZERO_ODDS_MESSAGE_SINGLE_BOOKMAKER : ZERO_ODDS_MESSAGE,
73
- // TODO: Return sportsbook name with zero odds
74
- };
75
- }
76
-
77
- if (arrayOfBookmakers.length == 1) {
78
- return {
79
- homeOdds: homeOdd,
80
- awayOdds: awayOdd,
81
- drawOdds: isTwoPositionalSport ? 0 : drawOdd,
82
- };
83
- }
84
-
85
- // If none of the bookmakers have zero odds, check implied odds percentage difference
86
- const hasLargeImpliedPercentageDifference = arrayOfBookmakers.slice(1).some((bookmakerId) => {
87
- const line = oddsMap.get(bookmakerId);
88
- if (line) {
89
- const otherHomeOdd = line.homeOdds;
90
- const otherAwayOdd = line.awayOdds;
91
- const otherDrawOdd = line.drawOdds;
92
-
93
- const homeOddsImplied = oddslib.from('decimal', homeOdd).to('impliedProbability');
94
-
95
- const awayOddsImplied = oddslib.from('decimal', awayOdd).to('impliedProbability');
96
-
97
- // Calculate implied odds for the "draw" if it's not a two-positions sport
98
- const drawOddsImplied = isTwoPositionalSport
99
- ? 0
100
- : oddslib.from('decimal', drawOdd).to('impliedProbability');
101
-
102
- const otherHomeOddImplied = oddslib.from('decimal', otherHomeOdd).to('impliedProbability');
103
-
104
- const otherAwayOddImplied = oddslib.from('decimal', otherAwayOdd).to('impliedProbability');
105
-
106
- // Calculate implied odds for the "draw" if it's not a two-positions sport
107
- const otherDrawOddImplied = isTwoPositionalSport
108
- ? 0
109
- : oddslib.from('decimal', otherDrawOdd).to('impliedProbability');
110
-
111
- // Calculate the percentage difference for implied odds
112
- const homeOddsDifference = calculateImpliedOddsDifference(homeOddsImplied, otherHomeOddImplied);
113
-
114
- const awayOddsDifference = calculateImpliedOddsDifference(awayOddsImplied, otherAwayOddImplied);
115
-
116
- // Check implied odds difference for the "draw" only if it's not a two-positions sport
117
- const drawOddsDifference = isTwoPositionalSport
118
- ? 0
119
- : calculateImpliedOddsDifference(drawOddsImplied, otherDrawOddImplied);
120
-
121
- // Check if the percentage difference exceeds the threshold
122
- if (
123
- (homeOddsDifference > maxImpliedPercentageDifference &&
124
- homeOddsImplied > minOddsForDiffChecking &&
125
- otherHomeOddImplied > minOddsForDiffChecking) ||
126
- (awayOddsDifference > maxImpliedPercentageDifference &&
127
- awayOddsImplied > minOddsForDiffChecking &&
128
- otherAwayOddImplied > minOddsForDiffChecking) ||
129
- (!isTwoPositionalSport &&
130
- drawOddsDifference > maxImpliedPercentageDifference &&
131
- drawOddsImplied > minOddsForDiffChecking &&
132
- otherDrawOddImplied > minOddsForDiffChecking)
133
- ) {
134
- return true;
135
- }
136
- }
137
- return false;
138
- });
139
-
140
- if (hasLargeImpliedPercentageDifference) {
141
- return {
142
- homeOdds: 0,
143
- awayOdds: 0,
144
- drawOdds: 0,
145
- errorMessage: DIFF_BETWEEN_BOOKMAKERS_MESSAGE,
146
- };
147
- }
148
-
149
- return {
150
- homeOdds: homeOdd,
151
- awayOdds: awayOdd,
152
- drawOdds: isTwoPositionalSport ? 0 : drawOdd,
153
- };
154
- };
155
-
156
- export const calculateImpliedOddsDifference = (impliedOddsA: number, impliedOddsB: number): number => {
157
- const percentageDifference = (Math.abs(impliedOddsA - impliedOddsB) / impliedOddsA) * 100;
158
- return percentageDifference;
159
- };
1
+ import * as oddslib from 'oddslib';
2
+ import { MIN_ODDS_FOR_DIFF_CHECKING } from '../constants/common';
3
+ import {
4
+ DIFF_BETWEEN_BOOKMAKERS_MESSAGE,
5
+ NO_MATCHING_BOOKMAKERS_MESSAGE,
6
+ ZERO_ODDS_MESSAGE,
7
+ ZERO_ODDS_MESSAGE_SINGLE_BOOKMAKER,
8
+ } from '../constants/errors';
9
+ import { BookmakersConfig } from '../types/bookmakers';
10
+ import { OddsWithLeagueInfo } from '../types/odds';
11
+ import { LastPolledArray, LeagueConfigInfo } from '../types/sports';
12
+
13
+ export const getBookmakersArray = (
14
+ bookmakersData: BookmakersConfig[],
15
+ sportId: any,
16
+ backupLiveOddsProviders: string[]
17
+ ) => {
18
+ const sportBookmakersData = bookmakersData.find((data) => Number(data.sportId) === Number(sportId));
19
+ if (sportBookmakersData) {
20
+ if (sportBookmakersData.primaryBookmaker == '') {
21
+ return backupLiveOddsProviders;
22
+ }
23
+ const bookmakersArray: string[] = [];
24
+
25
+ sportBookmakersData.primaryBookmaker ? bookmakersArray.push(sportBookmakersData.primaryBookmaker) : '';
26
+ sportBookmakersData.secondaryBookmaker ? bookmakersArray.push(sportBookmakersData.secondaryBookmaker) : '';
27
+ sportBookmakersData.tertiaryBookmaker ? bookmakersArray.push(sportBookmakersData.tertiaryBookmaker) : '';
28
+
29
+ return bookmakersArray;
30
+ }
31
+ return backupLiveOddsProviders;
32
+ };
33
+
34
+ export const getBookmakersFromLeagueConfig = (sportId: string | number, leagueInfoArray: LeagueConfigInfo[]) => {
35
+ const uniqueBookmakers: string[] = [];
36
+
37
+ for (const leagueInfo of leagueInfoArray) {
38
+ if (Number(leagueInfo.sportId) === Number(sportId) && leagueInfo.enabled === 'true') {
39
+ const primary = leagueInfo.primaryBookmaker?.toLowerCase();
40
+ const secondary = leagueInfo.secondaryBookmaker?.toLowerCase();
41
+ if (primary) {
42
+ uniqueBookmakers.push(primary);
43
+ }
44
+ if (secondary && secondary !== primary) {
45
+ uniqueBookmakers.push(secondary);
46
+ }
47
+ break;
48
+ }
49
+ }
50
+
51
+ return uniqueBookmakers;
52
+ };
53
+
54
+ export const getBookmakersForLeague = (
55
+ sportId: string | number,
56
+ configPerMarket: LeagueConfigInfo[],
57
+ configPerLeague: BookmakersConfig[],
58
+ defaultBookmakers: string[],
59
+ maxNumOfBookmakers = 5
60
+ ) => {
61
+ // bookmakers defined per market for league
62
+ const bookmakersPerMarket = getBookmakersFromLeagueConfig(sportId, configPerMarket);
63
+ // bookmakers defined generally per league
64
+ const bookmakersPerLeague = getBookmakersArray(configPerLeague, sportId, defaultBookmakers);
65
+ // all unique bookmakers defined from both configs
66
+ const uniqueBookmakers = [...new Set([...bookmakersPerMarket, ...bookmakersPerLeague])];
67
+
68
+ const bookmakers = uniqueBookmakers.filter((s) => s.length).slice(0, maxNumOfBookmakers);
69
+
70
+ return bookmakers;
71
+ };
72
+
73
+ export const checkOddsFromBookmakers = (
74
+ oddsMap: Map<string, any>,
75
+ arrayOfBookmakers: string[],
76
+ isTwoPositionalSport: boolean,
77
+ maxImpliedPercentageDifference: number,
78
+ minOddsForDiffChecking: number
79
+ ) => {
80
+ // Main bookmaker odds
81
+ const firstBookmakerOdds = oddsMap.get(arrayOfBookmakers[0].toLowerCase());
82
+
83
+ if (!firstBookmakerOdds) {
84
+ // If no matching bookmakers are found, return zero odds
85
+ return {
86
+ homeOdds: 0,
87
+ awayOdds: 0,
88
+ drawOdds: 0,
89
+ errorMessage: NO_MATCHING_BOOKMAKERS_MESSAGE,
90
+ };
91
+ }
92
+
93
+ const homeOdd = firstBookmakerOdds.homeOdds;
94
+ const awayOdd = firstBookmakerOdds.awayOdds;
95
+ const drawOdd = isTwoPositionalSport ? 0 : firstBookmakerOdds.drawOdds;
96
+
97
+ // Check if any bookmaker has odds of 0 or 0.0001
98
+ const hasZeroOrOne = arrayOfBookmakers.some((bookmakerId) => {
99
+ const line = oddsMap.get(bookmakerId);
100
+ if (line) {
101
+ return (
102
+ line.homeOdds === 0 ||
103
+ line.awayOdds === 0 ||
104
+ (!isTwoPositionalSport && line.drawOdds === 0) ||
105
+ line.homeOdds === 1 ||
106
+ line.awayOdds === 1 ||
107
+ (!isTwoPositionalSport && line.drawOdds === 1)
108
+ );
109
+ }
110
+ return false; // fix for es-lint
111
+ });
112
+
113
+ if (hasZeroOrOne) {
114
+ // If any bookmaker has zero odds, return zero odds
115
+ return {
116
+ homeOdds: 0,
117
+ awayOdds: 0,
118
+ drawOdds: 0,
119
+ errorMessage: arrayOfBookmakers.length === 1 ? ZERO_ODDS_MESSAGE_SINGLE_BOOKMAKER : ZERO_ODDS_MESSAGE,
120
+ // TODO: Return sportsbook name with zero odds
121
+ };
122
+ }
123
+
124
+ if (arrayOfBookmakers.length == 1) {
125
+ return {
126
+ homeOdds: homeOdd,
127
+ awayOdds: awayOdd,
128
+ drawOdds: isTwoPositionalSport ? 0 : drawOdd,
129
+ };
130
+ }
131
+
132
+ // If none of the bookmakers have zero odds, check implied odds percentage difference
133
+ const hasLargeImpliedPercentageDifference = arrayOfBookmakers.slice(1).some((bookmakerId) => {
134
+ const line = oddsMap.get(bookmakerId);
135
+ if (line) {
136
+ const otherHomeOdd = line.homeOdds;
137
+ const otherAwayOdd = line.awayOdds;
138
+ const otherDrawOdd = line.drawOdds;
139
+
140
+ const homeOddsImplied = oddslib.from('decimal', homeOdd).to('impliedProbability');
141
+
142
+ const awayOddsImplied = oddslib.from('decimal', awayOdd).to('impliedProbability');
143
+
144
+ // Calculate implied odds for the "draw" if it's not a two-positions sport
145
+ const drawOddsImplied = isTwoPositionalSport
146
+ ? 0
147
+ : oddslib.from('decimal', drawOdd).to('impliedProbability');
148
+
149
+ const otherHomeOddImplied = oddslib.from('decimal', otherHomeOdd).to('impliedProbability');
150
+
151
+ const otherAwayOddImplied = oddslib.from('decimal', otherAwayOdd).to('impliedProbability');
152
+
153
+ // Calculate implied odds for the "draw" if it's not a two-positions sport
154
+ const otherDrawOddImplied = isTwoPositionalSport
155
+ ? 0
156
+ : oddslib.from('decimal', otherDrawOdd).to('impliedProbability');
157
+
158
+ // Calculate the percentage difference for implied odds
159
+ const homeOddsDifference = calculateImpliedOddsDifference(homeOddsImplied, otherHomeOddImplied);
160
+
161
+ const awayOddsDifference = calculateImpliedOddsDifference(awayOddsImplied, otherAwayOddImplied);
162
+
163
+ // Check implied odds difference for the "draw" only if it's not a two-positions sport
164
+ const drawOddsDifference = isTwoPositionalSport
165
+ ? 0
166
+ : calculateImpliedOddsDifference(drawOddsImplied, otherDrawOddImplied);
167
+
168
+ // Check if the percentage difference exceeds the threshold
169
+ if (
170
+ (homeOddsDifference > maxImpliedPercentageDifference &&
171
+ homeOddsImplied > minOddsForDiffChecking &&
172
+ otherHomeOddImplied > minOddsForDiffChecking) ||
173
+ (awayOddsDifference > maxImpliedPercentageDifference &&
174
+ awayOddsImplied > minOddsForDiffChecking &&
175
+ otherAwayOddImplied > minOddsForDiffChecking) ||
176
+ (!isTwoPositionalSport &&
177
+ drawOddsDifference > maxImpliedPercentageDifference &&
178
+ drawOddsImplied > minOddsForDiffChecking &&
179
+ otherDrawOddImplied > minOddsForDiffChecking)
180
+ ) {
181
+ return true;
182
+ }
183
+ }
184
+ return false;
185
+ });
186
+
187
+ if (hasLargeImpliedPercentageDifference) {
188
+ return {
189
+ homeOdds: 0,
190
+ awayOdds: 0,
191
+ drawOdds: 0,
192
+ errorMessage: DIFF_BETWEEN_BOOKMAKERS_MESSAGE,
193
+ };
194
+ }
195
+
196
+ return {
197
+ homeOdds: homeOdd,
198
+ awayOdds: awayOdd,
199
+ drawOdds: isTwoPositionalSport ? 0 : drawOdd,
200
+ };
201
+ };
202
+
203
+ export const checkOddsFromBookmakersForChildMarkets = (
204
+ odds: any,
205
+ leagueInfos: LeagueConfigInfo[],
206
+ oddsProviders: string[],
207
+ lastPolledData: LastPolledArray,
208
+ maxAllowedProviderDataStaleDelay: number,
209
+ maxImpliedPercentageDifference: number
210
+ ): OddsWithLeagueInfo => {
211
+ const formattedOdds = Object.entries(odds as any).reduce((acc: any, [key, value]: [string, any]) => {
212
+ const [sportsBookName, marketName, points, selection, selectionLine] = key.split('_');
213
+ const info = leagueInfos.find((leagueInfo) => leagueInfo.marketName.toLowerCase() === marketName.toLowerCase());
214
+ if (info) {
215
+ const bookmakers = getPrimaryAndSecondaryBookmakerForTypeId(
216
+ oddsProviders,
217
+ leagueInfos,
218
+ Number(info.typeId)
219
+ );
220
+
221
+ const isValidLastPolled = isLastPolledForBookmakersValid(
222
+ lastPolledData,
223
+ maxAllowedProviderDataStaleDelay,
224
+ bookmakers
225
+ );
226
+
227
+ if (isValidLastPolled) {
228
+ const primaryBookmaker = bookmakers[0];
229
+ const secondaryBookmaker = bookmakers[1];
230
+ if (primaryBookmaker && !secondaryBookmaker) {
231
+ if (sportsBookName.toLowerCase() === primaryBookmaker.toLowerCase()) {
232
+ acc.push(value);
233
+ }
234
+ } else {
235
+ if (sportsBookName.toLowerCase() === primaryBookmaker) {
236
+ const secondaryBookmakerObject =
237
+ odds[
238
+ `${secondaryBookmaker}_${marketName.toLowerCase()}_${points}_${selection}_${selectionLine}`
239
+ ];
240
+ if (secondaryBookmakerObject) {
241
+ const primaryOdds = oddslib.from('decimal', value.price).to('impliedProbability');
242
+ const secondaryOdds = oddslib
243
+ .from('decimal', secondaryBookmakerObject.price)
244
+ .to('impliedProbability');
245
+ if (
246
+ primaryOdds >= MIN_ODDS_FOR_DIFF_CHECKING &&
247
+ secondaryOdds >= MIN_ODDS_FOR_DIFF_CHECKING
248
+ ) {
249
+ const homeOddsDifference = calculateImpliedOddsDifference(primaryOdds, secondaryOdds);
250
+ if (Number(homeOddsDifference) <= Number(maxImpliedPercentageDifference)) {
251
+ acc.push(value);
252
+ }
253
+ } else {
254
+ acc.push(value);
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ return acc;
263
+ }, []);
264
+ return formattedOdds;
265
+ };
266
+
267
+ export const getPrimaryAndSecondaryBookmakerForTypeId = (
268
+ defaultProviders: string[],
269
+ leagueInfos: LeagueConfigInfo[], // LeagueConfigInfo for specific sport, not the entire list from csv
270
+ typeId: number
271
+ ): string[] => {
272
+ const info = leagueInfos.find((leagueInfo) => Number(leagueInfo.typeId) === typeId);
273
+ let primaryBookmaker = defaultProviders[0].toLowerCase();
274
+ let secondaryBookmaker = defaultProviders[1] ? defaultProviders[1].toLowerCase() : undefined;
275
+ if (info) {
276
+ if (info.primaryBookmaker) {
277
+ primaryBookmaker = info.primaryBookmaker.toLowerCase();
278
+ secondaryBookmaker = info.secondaryBookmaker ? info.secondaryBookmaker.toLowerCase() : undefined;
279
+ }
280
+ }
281
+ return secondaryBookmaker ? [primaryBookmaker, secondaryBookmaker] : [primaryBookmaker];
282
+ };
283
+
284
+ export const isLastPolledForBookmakersValid = (
285
+ lastPolledData: LastPolledArray,
286
+ maxAllowedProviderDataStaleDelay: number,
287
+ bookmakers: string[]
288
+ ): boolean => {
289
+ const now = new Date();
290
+ const isNotValid = bookmakers.some((bookmakerId) => {
291
+ const lastPolledTime = lastPolledData.find(
292
+ (entry) => entry.sportsbook.toLowerCase() === bookmakerId.toLowerCase()
293
+ )?.timestamp;
294
+ if (typeof lastPolledTime !== 'number') {
295
+ return true;
296
+ }
297
+ const oddsDate = new Date(lastPolledTime * 1000);
298
+ const timeDiff = now.getTime() - oddsDate.getTime();
299
+ if (timeDiff > maxAllowedProviderDataStaleDelay) {
300
+ return true;
301
+ }
302
+ });
303
+ return !isNotValid;
304
+ };
305
+
306
+ export const calculateImpliedOddsDifference = (impliedOddsA: number, impliedOddsB: number): number => {
307
+ const percentageDifference = (Math.abs(impliedOddsA - impliedOddsB) / impliedOddsA) * 100;
308
+ return percentageDifference;
309
+ };