overtime-live-trading-utils 2.1.35 → 2.1.37-rc.0
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/.circleci/config.yml +32 -32
- package/.prettierrc +9 -9
- package/codecov.yml +20 -20
- package/index.ts +26 -26
- package/jest.config.ts +16 -16
- package/main.js +1 -1
- package/package.json +30 -30
- package/src/constants/common.ts +8 -7
- package/src/constants/errors.ts +7 -6
- package/src/constants/sports.ts +78 -78
- package/src/enums/sports.ts +109 -109
- package/src/tests/mock/MockLeagueMap.ts +200 -170
- package/src/tests/mock/MockOpticOddsEvents.ts +662 -662
- package/src/tests/mock/MockOpticSoccer.ts +9864 -9378
- package/src/tests/mock/MockSoccerRedis.ts +2308 -2308
- package/src/tests/unit/bookmakers.test.ts +148 -79
- package/src/tests/unit/markets.test.ts +176 -156
- package/src/tests/unit/odds.test.ts +103 -92
- package/src/tests/unit/resolution.test.ts +1488 -1393
- package/src/tests/unit/sports.test.ts +58 -58
- package/src/tests/unit/spread.test.ts +144 -131
- package/src/tests/utils/helper.ts +10 -0
- package/src/types/bookmakers.ts +7 -0
- package/src/types/missing-types.d.ts +2 -2
- package/src/types/odds.ts +80 -61
- package/src/types/resolution.ts +96 -96
- package/src/types/sports.ts +22 -19
- package/src/utils/bookmakers.ts +315 -159
- package/src/utils/constraints.ts +210 -210
- package/src/utils/gameMatching.ts +81 -81
- package/src/utils/markets.ts +119 -119
- package/src/utils/odds.ts +947 -918
- package/src/utils/opticOdds.ts +71 -71
- package/src/utils/resolution.ts +319 -319
- package/src/utils/sportPeriodMapping.ts +36 -36
- package/src/utils/sports.ts +51 -51
- package/src/utils/spread.ts +97 -97
- package/tsconfig.json +17 -13
- package/webpack.config.js +24 -24
- package/CLAUDE.md +0 -84
- package/resolution_live_markets.md +0 -356
package/src/utils/bookmakers.ts
CHANGED
|
@@ -1,159 +1,315 @@
|
|
|
1
|
-
import * as oddslib from 'oddslib';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
1
|
+
import * as oddslib from 'oddslib';
|
|
2
|
+
import { MAX_IMPLIED_PERCENTAGE_DIFF } 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 = [];
|
|
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
|
+
lastPolledMap: LastPolledArray,
|
|
208
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY: number
|
|
209
|
+
): OddsWithLeagueInfo => {
|
|
210
|
+
const formattedOdds = Object.entries(odds as any).reduce((acc: any, [key, value]: [string, any]) => {
|
|
211
|
+
const [sportsBookName, marketName, points, selection, selectionLine] = key.split('_');
|
|
212
|
+
const info = leagueInfos.find((leagueInfo) => leagueInfo.marketName.toLowerCase() === marketName.toLowerCase());
|
|
213
|
+
if (info) {
|
|
214
|
+
const { primaryBookmaker, secondaryBookmaker } = getPrimaryAndSecondaryBookmakerForTypeId(
|
|
215
|
+
oddsProviders,
|
|
216
|
+
leagueInfos,
|
|
217
|
+
Number(info.typeId)
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const isValidLastPolled = isLastPolledForBookmakersValid(
|
|
221
|
+
lastPolledMap,
|
|
222
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY,
|
|
223
|
+
primaryBookmaker,
|
|
224
|
+
secondaryBookmaker
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
if (isValidLastPolled) {
|
|
228
|
+
if (primaryBookmaker && !secondaryBookmaker) {
|
|
229
|
+
if (sportsBookName.toLowerCase() === primaryBookmaker.toLowerCase()) {
|
|
230
|
+
acc.push(value);
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
if (sportsBookName.toLowerCase() === primaryBookmaker) {
|
|
234
|
+
const secondaryBookmakerObject =
|
|
235
|
+
odds[
|
|
236
|
+
`${secondaryBookmaker}_${marketName.toLowerCase()}_${points}_${selection}_${selectionLine}`
|
|
237
|
+
];
|
|
238
|
+
if (secondaryBookmakerObject) {
|
|
239
|
+
const primaryOdds = oddslib.from('decimal', value.price).to('impliedProbability');
|
|
240
|
+
const secondaryOdds = oddslib
|
|
241
|
+
.from('decimal', secondaryBookmakerObject.price)
|
|
242
|
+
.to('impliedProbability');
|
|
243
|
+
|
|
244
|
+
const homeOddsDifference = calculateImpliedOddsDifference(primaryOdds, secondaryOdds);
|
|
245
|
+
if (Number(homeOddsDifference) <= Number(MAX_IMPLIED_PERCENTAGE_DIFF)) {
|
|
246
|
+
acc.push(value);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return acc;
|
|
255
|
+
}, []);
|
|
256
|
+
return formattedOdds;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export const getPrimaryAndSecondaryBookmakerForTypeId = (
|
|
260
|
+
defaultProviders: string[],
|
|
261
|
+
leagueInfos: LeagueConfigInfo[], // LeagueConfigInfo for specific sport, not the entire list from csv
|
|
262
|
+
typeId: number
|
|
263
|
+
): { primaryBookmaker: string; secondaryBookmaker: string | undefined } => {
|
|
264
|
+
const info = leagueInfos.find((leagueInfo) => Number(leagueInfo.typeId) === typeId);
|
|
265
|
+
let primaryBookmaker = defaultProviders[0].toLowerCase();
|
|
266
|
+
let secondaryBookmaker = defaultProviders[1] ? defaultProviders[1].toLowerCase() : undefined;
|
|
267
|
+
if (info) {
|
|
268
|
+
if (info.primaryBookmaker) {
|
|
269
|
+
primaryBookmaker = info.primaryBookmaker.toLowerCase();
|
|
270
|
+
secondaryBookmaker = info.secondaryBookmaker ? info.secondaryBookmaker.toLowerCase() : undefined;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return { primaryBookmaker, secondaryBookmaker };
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export const isLastPolledForBookmakersValid = (
|
|
277
|
+
lastPolledMap: LastPolledArray,
|
|
278
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY: number,
|
|
279
|
+
primaryBookmaker: string,
|
|
280
|
+
secondaryBookmaker?: string
|
|
281
|
+
): boolean => {
|
|
282
|
+
const lastPolledTimePrimary = lastPolledMap.find(
|
|
283
|
+
(entry) => entry.sportsbook.toLowerCase() === primaryBookmaker.toLowerCase()
|
|
284
|
+
)?.timestamp;
|
|
285
|
+
if (typeof lastPolledTimePrimary !== 'number') {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const now = new Date();
|
|
290
|
+
if (secondaryBookmaker) {
|
|
291
|
+
const lastPolledTimeSecondary = lastPolledMap.find(
|
|
292
|
+
(entry) => entry.sportsbook.toLowerCase() === secondaryBookmaker.toLowerCase()
|
|
293
|
+
)?.timestamp;
|
|
294
|
+
|
|
295
|
+
if (typeof lastPolledTimeSecondary !== 'number') {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const oddsDate = new Date(lastPolledTimeSecondary * 1000);
|
|
300
|
+
const timeDiff = now.getTime() - oddsDate.getTime();
|
|
301
|
+
if (timeDiff > MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const oddsDate = new Date(lastPolledTimePrimary * 1000);
|
|
307
|
+
const timeDiff = now.getTime() - oddsDate.getTime();
|
|
308
|
+
|
|
309
|
+
return timeDiff < MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
export const calculateImpliedOddsDifference = (impliedOddsA: number, impliedOddsB: number): number => {
|
|
313
|
+
const percentageDifference = (Math.abs(impliedOddsA - impliedOddsB) / impliedOddsA) * 100;
|
|
314
|
+
return percentageDifference;
|
|
315
|
+
};
|