overtime-live-trading-utils 1.1.36 → 1.1.37
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 +30 -30
- package/.prettierrc +9 -9
- package/codecov.yml +20 -20
- package/index.ts +16 -16
- package/jest.config.ts +16 -16
- package/main.js +1 -1
- package/package.json +29 -29
- package/src/constants/common.ts +10 -10
- package/src/constants/errors.ts +5 -5
- package/src/constants/sports.ts +2217 -2109
- package/src/enums/sports.ts +315 -306
- package/src/tests/mock/MockLeagueMap.ts +77 -77
- package/src/tests/mock/MockOpticSoccer.ts +9342 -9342
- package/src/tests/mock/MockSoccerRedis.ts +2309 -2309
- package/src/tests/unit/bookmakers.test.ts +77 -77
- package/src/tests/unit/markets.test.ts +133 -133
- package/src/tests/unit/odds.test.ts +92 -92
- package/src/tests/unit/sports.test.ts +47 -47
- package/src/tests/unit/spread.test.ts +131 -131
- package/src/types/odds.ts +50 -50
- package/src/types/sports.ts +18 -18
- package/src/utils/bookmakers.ts +153 -153
- package/src/utils/constraints.ts +186 -186
- package/src/utils/gameMatching.ts +88 -88
- package/src/utils/markets.ts +110 -110
- package/src/utils/odds.ts +470 -470
- package/src/utils/opticOdds.ts +102 -102
- package/src/utils/sports.ts +83 -83
- package/src/utils/spread.ts +92 -92
- package/tsconfig.json +12 -12
- package/webpack.config.js +24 -24
package/src/utils/odds.ts
CHANGED
|
@@ -1,470 +1,470 @@
|
|
|
1
|
-
import * as oddslib from 'oddslib';
|
|
2
|
-
import { DRAW, MIN_ODDS_FOR_DIFF_CHECKING, MONEYLINE_TYPE_ID, ZERO } from '../constants/common';
|
|
3
|
-
import { checkOddsFromBookmakers } from './bookmakers';
|
|
4
|
-
import { adjustSpreadOnOdds, getSpreadData } from './spread';
|
|
5
|
-
import { MoneylineTypes } from '../enums/sports';
|
|
6
|
-
import { ChildMarket, LeagueInfo } from '../types/sports';
|
|
7
|
-
import { getLeagueInfo } from './sports';
|
|
8
|
-
import { OddsObject } from '../types/odds';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Converts a given odds value from one format to another.
|
|
12
|
-
* Specifically, it converts from 'moneyline' to 'impliedProbability', handling special cases.
|
|
13
|
-
*
|
|
14
|
-
* @param {Number} odds - The odds value to convert.
|
|
15
|
-
* @returns {Number} The converted odds value.
|
|
16
|
-
*/
|
|
17
|
-
export const convertOddsToImpl = (odds) => {
|
|
18
|
-
return odds === ZERO ? 0 : getOddsFromTo('decimal', 'impliedProbability', odds);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Converts odds from one format to another.
|
|
23
|
-
* @param {String} from - The original odds format.
|
|
24
|
-
* @param {String} to - The target odds format.
|
|
25
|
-
* @param {Number} input - The odds value.
|
|
26
|
-
* @returns {Number} The converted odds.
|
|
27
|
-
*/
|
|
28
|
-
export const getOddsFromTo = (from, to, input) => {
|
|
29
|
-
try {
|
|
30
|
-
return oddslib.from(from, input).to(to);
|
|
31
|
-
} catch (error) {
|
|
32
|
-
return 0;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Filters the odds array to find entries matching the specified market name and bookmaker.
|
|
38
|
-
*
|
|
39
|
-
* @param {Array} oddsArray - The array of odds objects.
|
|
40
|
-
* @param {string} marketName - The market name to filter by.
|
|
41
|
-
* @param {Array} liveOddsProviders - Odds providers for live odds
|
|
42
|
-
* @param {Object} commonData - The common data object.
|
|
43
|
-
* @param {boolean} isTwoPositionalSport - Indicates if the sport is a two positional sport.,
|
|
44
|
-
* @returns {Map} The filtered map for odds per provider.
|
|
45
|
-
*/
|
|
46
|
-
export const filterOddsByMarketNameTeamNameBookmaker = (
|
|
47
|
-
oddsArray,
|
|
48
|
-
marketName,
|
|
49
|
-
liveOddsProviders,
|
|
50
|
-
commonData,
|
|
51
|
-
isTwoPositionalSport
|
|
52
|
-
) => {
|
|
53
|
-
const linesMap = new Map<any, any>();
|
|
54
|
-
liveOddsProviders.forEach((oddsProvider) => {
|
|
55
|
-
let homeOdds = 0;
|
|
56
|
-
const homeTeamOddsObject = oddsArray.filter((odd) => {
|
|
57
|
-
return (
|
|
58
|
-
odd &&
|
|
59
|
-
odd.marketName.toLowerCase() === marketName.toLowerCase() &&
|
|
60
|
-
odd.sportsBookName.toLowerCase() == oddsProvider.toLowerCase() &&
|
|
61
|
-
odd.selection.toLowerCase() === commonData.homeTeam.toLowerCase()
|
|
62
|
-
);
|
|
63
|
-
});
|
|
64
|
-
if (homeTeamOddsObject.length !== 0) {
|
|
65
|
-
homeOdds = homeTeamOddsObject[0].price;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
let awayOdds = 0;
|
|
69
|
-
const awayTeamOddsObject = oddsArray.filter(
|
|
70
|
-
(odd) =>
|
|
71
|
-
odd &&
|
|
72
|
-
odd.marketName.toLowerCase() === marketName.toLowerCase() &&
|
|
73
|
-
odd.sportsBookName.toLowerCase() == oddsProvider.toLowerCase() &&
|
|
74
|
-
odd.selection.toLowerCase() === commonData.awayTeam.toLowerCase()
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
if (awayTeamOddsObject.length !== 0) {
|
|
78
|
-
awayOdds = awayTeamOddsObject[0].price;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
let drawOdds = 0;
|
|
82
|
-
if (!isTwoPositionalSport) {
|
|
83
|
-
const drawOddsObject = oddsArray.filter(
|
|
84
|
-
(odd) =>
|
|
85
|
-
odd &&
|
|
86
|
-
odd.marketName.toLowerCase() === marketName.toLowerCase() &&
|
|
87
|
-
odd.sportsBookName.toLowerCase() == oddsProvider.toLowerCase() &&
|
|
88
|
-
odd.selection.toLowerCase() === DRAW.toLowerCase()
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
if (drawOddsObject.length !== 0) {
|
|
92
|
-
drawOdds = drawOddsObject[0].price;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
linesMap.set(oddsProvider.toLowerCase(), {
|
|
97
|
-
homeOdds: homeOdds,
|
|
98
|
-
awayOdds: awayOdds,
|
|
99
|
-
drawOdds: drawOdds,
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
return linesMap;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Retrieves the parent odds for the given event.
|
|
107
|
-
*
|
|
108
|
-
* @param {boolean} isTwoPositionalSport - Indicates if the sport is a two positional sport.
|
|
109
|
-
* @param {Array} sportSpreadData - Spread data specific to the sport.
|
|
110
|
-
* @param {Array} liveOddsProviders - Odds providers for live odds
|
|
111
|
-
* @param {Object} oddsApiObject - Odds data from the API.
|
|
112
|
-
* @param {String} sportId - Sport ID API.
|
|
113
|
-
* @param {Number} defaultSpreadForLiveMarkets - Default spread for live markets,
|
|
114
|
-
* @param {Number} maxPercentageDiffBetwenOdds - Maximum allowed percentage difference between same position odds from different providers
|
|
115
|
-
* @returns {Array} The parent odds for the event [homeOdds, awayOdds, drawOdds].
|
|
116
|
-
*/
|
|
117
|
-
export const getParentOdds = (
|
|
118
|
-
isTwoPositionalSport,
|
|
119
|
-
sportSpreadData,
|
|
120
|
-
liveOddsProviders,
|
|
121
|
-
oddsApiObject,
|
|
122
|
-
sportId,
|
|
123
|
-
defaultSpreadForLiveMarkets,
|
|
124
|
-
maxPercentageDiffBetwenOdds
|
|
125
|
-
) => {
|
|
126
|
-
const commonData = { homeTeam: oddsApiObject.homeTeam, awayTeam: oddsApiObject.awayTeam };
|
|
127
|
-
|
|
128
|
-
// EXTRACTING ODDS FROM THE RESPONSE PER MARKET NAME AND BOOKMAKER
|
|
129
|
-
const moneylineOddsMap = filterOddsByMarketNameTeamNameBookmaker(
|
|
130
|
-
oddsApiObject.odds,
|
|
131
|
-
MoneylineTypes.MONEYLINE,
|
|
132
|
-
liveOddsProviders,
|
|
133
|
-
commonData,
|
|
134
|
-
isTwoPositionalSport
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
// CHECKING AND COMPARING ODDS FOR THE GIVEN BOOKMAKERS
|
|
138
|
-
const oddsObject = checkOddsFromBookmakers(
|
|
139
|
-
moneylineOddsMap,
|
|
140
|
-
liveOddsProviders,
|
|
141
|
-
isTwoPositionalSport,
|
|
142
|
-
maxPercentageDiffBetwenOdds,
|
|
143
|
-
MIN_ODDS_FOR_DIFF_CHECKING
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
if (oddsObject.errorMessage) {
|
|
147
|
-
return {
|
|
148
|
-
odds: isTwoPositionalSport ? [0, 0] : [0, 0, 0],
|
|
149
|
-
errorMessage: oddsObject.errorMessage,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
const primaryBookmakerOdds = isTwoPositionalSport
|
|
153
|
-
? [oddsObject.homeOdds, oddsObject.awayOdds]
|
|
154
|
-
: [oddsObject.homeOdds, oddsObject.awayOdds, oddsObject.drawOdds];
|
|
155
|
-
|
|
156
|
-
let parentOdds = primaryBookmakerOdds.map((odd) => convertOddsToImpl(odd));
|
|
157
|
-
const spreadData = getSpreadData(sportSpreadData, sportId, MONEYLINE_TYPE_ID, defaultSpreadForLiveMarkets);
|
|
158
|
-
|
|
159
|
-
if (spreadData !== null) {
|
|
160
|
-
parentOdds = adjustSpreadOnOdds(parentOdds, spreadData.minSpread, spreadData.targetSpread);
|
|
161
|
-
} else {
|
|
162
|
-
// Use min spread by sport if available, otherwise use default min spread
|
|
163
|
-
parentOdds = adjustSpreadOnOdds(parentOdds, defaultSpreadForLiveMarkets, 0);
|
|
164
|
-
}
|
|
165
|
-
return { odds: parentOdds };
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Creates child markets based on the given parameters.
|
|
170
|
-
*
|
|
171
|
-
* @param {Object} leagueId - leagueId AKA sportId
|
|
172
|
-
* @param {Array} spreadDataForSport - Spread data for sport.
|
|
173
|
-
* @param {Object} apiResponseWithOdds - API response from the provider
|
|
174
|
-
* @param {Array} liveOddsProviders - Odds providers for live odds
|
|
175
|
-
* @param {Number} defaultSpreadForLiveMarkets - Default spread for live markets
|
|
176
|
-
* @param {Boolean} leagueMap - League Map info
|
|
177
|
-
* @returns {Array} The child markets.
|
|
178
|
-
*/
|
|
179
|
-
export const createChildMarkets: (
|
|
180
|
-
apiResponseWithOdds: OddsObject,
|
|
181
|
-
spreadDataForSport: any,
|
|
182
|
-
leagueId: number,
|
|
183
|
-
liveOddsProviders: any,
|
|
184
|
-
defaultSpreadForLiveMarkets: any,
|
|
185
|
-
leagueMap: any
|
|
186
|
-
) => ChildMarket[] = (
|
|
187
|
-
apiResponseWithOdds,
|
|
188
|
-
spreadDataForSport,
|
|
189
|
-
leagueId,
|
|
190
|
-
liveOddsProviders,
|
|
191
|
-
defaultSpreadForLiveMarkets,
|
|
192
|
-
leagueMap
|
|
193
|
-
) => {
|
|
194
|
-
const [spreadOdds, totalOdds, moneylineOdds, childMarkets]: any[] = [[], [], [], []];
|
|
195
|
-
const leagueInfo = getLeagueInfo(leagueId, leagueMap);
|
|
196
|
-
const commonData = {
|
|
197
|
-
homeTeam: apiResponseWithOdds.homeTeam,
|
|
198
|
-
awayTeam: apiResponseWithOdds.awayTeam,
|
|
199
|
-
};
|
|
200
|
-
if (leagueInfo.length > 0) {
|
|
201
|
-
// TODO ADD ODDS COMPARISON BETWEEN BOOKMAKERS
|
|
202
|
-
const allChildOdds = filterOddsByMarketNameBookmaker(
|
|
203
|
-
apiResponseWithOdds.odds,
|
|
204
|
-
leagueInfo,
|
|
205
|
-
liveOddsProviders[0]
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
allChildOdds.forEach((odd) => {
|
|
209
|
-
if (odd.type === 'Total') {
|
|
210
|
-
if (Math.abs(Number(odd.points) % 1) === 0.5) totalOdds.push(odd);
|
|
211
|
-
} else if (odd.type === 'Spread') {
|
|
212
|
-
if (Math.abs(Number(odd.points) % 1) === 0.5) spreadOdds.push(odd);
|
|
213
|
-
} else if (odd.type === 'Moneyline') {
|
|
214
|
-
moneylineOdds.push(odd);
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
const formattedOdds = [
|
|
219
|
-
...groupAndFormatSpreadOdds(spreadOdds, commonData),
|
|
220
|
-
...groupAndFormatTotalOdds(totalOdds, commonData),
|
|
221
|
-
...groupAndFormatMoneylineOdds(moneylineOdds, commonData),
|
|
222
|
-
];
|
|
223
|
-
|
|
224
|
-
const oddsWithSpreadAdjusted = adjustSpreadOnChildOdds(
|
|
225
|
-
formattedOdds,
|
|
226
|
-
spreadDataForSport,
|
|
227
|
-
defaultSpreadForLiveMarkets
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
oddsWithSpreadAdjusted.forEach((data) => {
|
|
231
|
-
const childMarket = {
|
|
232
|
-
leagueId: Number(data.sportId),
|
|
233
|
-
typeId: Number(data.typeId),
|
|
234
|
-
type: data.type.toLowerCase(),
|
|
235
|
-
line: Number(data.line || 0),
|
|
236
|
-
odds: data.odds,
|
|
237
|
-
};
|
|
238
|
-
const leagueInfoByTypeId = leagueInfo.find((league) => Number(league.typeId) === Number(data.typeId));
|
|
239
|
-
const minOdds = leagueInfoByTypeId?.minOdds;
|
|
240
|
-
const maxOdds = leagueInfoByTypeId?.maxOdds;
|
|
241
|
-
if (
|
|
242
|
-
!(
|
|
243
|
-
minOdds &&
|
|
244
|
-
maxOdds &&
|
|
245
|
-
(data.odds[0] >= minOdds ||
|
|
246
|
-
data.odds[0] <= maxOdds ||
|
|
247
|
-
data.odds[1] >= minOdds ||
|
|
248
|
-
data.odds[1] <= maxOdds)
|
|
249
|
-
)
|
|
250
|
-
) {
|
|
251
|
-
childMarkets.push(childMarket);
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
} else {
|
|
255
|
-
console.warn(`No child markets for leagueID: ${Number(leagueId)}`);
|
|
256
|
-
}
|
|
257
|
-
return childMarkets;
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Filters the odds array to find entries matching the specified market name.
|
|
262
|
-
*
|
|
263
|
-
* @param {Array} oddsArray - The array of odds objects.
|
|
264
|
-
* @param {string} leagueInfos - The market names to filter by.
|
|
265
|
-
* @param {string} oddsProvider - The main odds provider to filter by.
|
|
266
|
-
* @returns {Array} The filtered odds array.
|
|
267
|
-
*/
|
|
268
|
-
export const filterOddsByMarketNameBookmaker = (oddsArray, leagueInfos: LeagueInfo[], oddsProvider) => {
|
|
269
|
-
const allChildMarketsTypes = leagueInfos
|
|
270
|
-
.filter(
|
|
271
|
-
(leagueInfo) =>
|
|
272
|
-
leagueInfo.marketName.toLowerCase() !== MoneylineTypes.MONEYLINE.toLowerCase() &&
|
|
273
|
-
leagueInfo.enabled === 'true'
|
|
274
|
-
)
|
|
275
|
-
.map((leagueInfo) => leagueInfo.marketName.toLowerCase());
|
|
276
|
-
return oddsArray
|
|
277
|
-
.filter(
|
|
278
|
-
(odd) =>
|
|
279
|
-
allChildMarketsTypes.includes(odd.marketName.toLowerCase()) &&
|
|
280
|
-
odd.sportsBookName.toLowerCase() == oddsProvider.toLowerCase()
|
|
281
|
-
)
|
|
282
|
-
.map((odd) => {
|
|
283
|
-
return {
|
|
284
|
-
...odd,
|
|
285
|
-
...leagueInfos.find(
|
|
286
|
-
(leagueInfo) => leagueInfo.marketName.toLowerCase() === odd.marketName.toLowerCase()
|
|
287
|
-
), // using .find() for team totals means that we will always assign 10017 as typeID at this point
|
|
288
|
-
};
|
|
289
|
-
});
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Groups spread odds by their lines and formats the result.
|
|
294
|
-
*
|
|
295
|
-
* @param {Array} oddsArray - The input array of odds objects.
|
|
296
|
-
* @param {Object} commonData - The common data object containing homeTeam information.
|
|
297
|
-
* @returns {Array} The grouped and formatted spread odds.
|
|
298
|
-
*/
|
|
299
|
-
export const groupAndFormatSpreadOdds = (oddsArray, commonData) => {
|
|
300
|
-
// Group odds by their selection points and selection
|
|
301
|
-
const groupedOdds = oddsArray.reduce((acc: any, odd: any) => {
|
|
302
|
-
const { points, marketName, price, selection, typeId, sportId, type } = odd;
|
|
303
|
-
const isHomeTeam = selection === commonData.homeTeam;
|
|
304
|
-
|
|
305
|
-
const key = `${marketName}_${isHomeTeam ? points : -points}`;
|
|
306
|
-
|
|
307
|
-
if (!acc[key]) {
|
|
308
|
-
acc[key] = { home: null, away: null, typeId: null, sportId: null };
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (isHomeTeam) {
|
|
312
|
-
acc[key].home = price;
|
|
313
|
-
} else {
|
|
314
|
-
acc[key].away = price;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
acc[key].typeId = typeId;
|
|
318
|
-
acc[key].type = type;
|
|
319
|
-
acc[key].sportId = sportId;
|
|
320
|
-
|
|
321
|
-
return acc;
|
|
322
|
-
}, {}) as any;
|
|
323
|
-
// Format the grouped odds into the desired output
|
|
324
|
-
const formattedOdds = (Object.entries(groupedOdds as any) as any).reduce((acc, [key, value]) => {
|
|
325
|
-
const [_marketName, lineFloat] = key.split('_');
|
|
326
|
-
const line = parseFloat(lineFloat);
|
|
327
|
-
if ((value as any).home !== null && (value as any).away !== null) {
|
|
328
|
-
acc.push({
|
|
329
|
-
line: line as any,
|
|
330
|
-
odds: [(value as any).home, (value as any).away],
|
|
331
|
-
typeId: value.typeId,
|
|
332
|
-
sportId: value.sportId,
|
|
333
|
-
type: value.type,
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
return acc;
|
|
337
|
-
}, []);
|
|
338
|
-
|
|
339
|
-
return formattedOdds;
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Groups odds by selection and points over/under.
|
|
344
|
-
*
|
|
345
|
-
* @param {Array} oddsArray - The array of odds objects.
|
|
346
|
-
* @returns {Object} The grouped odds.
|
|
347
|
-
*/
|
|
348
|
-
export const groupAndFormatTotalOdds = (oddsArray, commonData) => {
|
|
349
|
-
// Group odds by their selection points and selection
|
|
350
|
-
const groupedOdds = oddsArray.reduce((acc, odd) => {
|
|
351
|
-
if (odd) {
|
|
352
|
-
const key = `${odd.marketName}_${odd.selection}_${odd.points}`;
|
|
353
|
-
if (!acc[key]) {
|
|
354
|
-
acc[key] = { over: null, under: null };
|
|
355
|
-
}
|
|
356
|
-
if (odd.selectionLine === 'over') {
|
|
357
|
-
acc[key].over = odd.price;
|
|
358
|
-
} else if (odd.selectionLine === 'under') {
|
|
359
|
-
acc[key].under = odd.price;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
acc[key].typeId = odd.typeId;
|
|
363
|
-
acc[key].type = odd.type;
|
|
364
|
-
acc[key].sportId = odd.sportId;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return acc;
|
|
368
|
-
}, {});
|
|
369
|
-
|
|
370
|
-
// Format the grouped odds into the desired output
|
|
371
|
-
const formattedOdds = (Object.entries(groupedOdds as any) as any).reduce((acc, [key, value]) => {
|
|
372
|
-
const [_marketName, selection, selectionLine] = key.split('_');
|
|
373
|
-
const line = parseFloat(selectionLine);
|
|
374
|
-
|
|
375
|
-
// if we have away team in total odds we know the market is team total and we need to increase typeId by one.
|
|
376
|
-
// if this is false typeId is already mapped correctly
|
|
377
|
-
const isAwayTeam = selection === commonData.awayTeam;
|
|
378
|
-
if ((value as any).over !== null && (value as any).under !== null) {
|
|
379
|
-
acc.push({
|
|
380
|
-
line: line as any,
|
|
381
|
-
odds: [(value as any).over, (value as any).under],
|
|
382
|
-
typeId: !isAwayTeam ? value.typeId : Number(value.typeId) + 1,
|
|
383
|
-
sportId: value.sportId,
|
|
384
|
-
type: value.type,
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
return acc;
|
|
388
|
-
}, []);
|
|
389
|
-
|
|
390
|
-
return formattedOdds;
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Groups spread odds by their lines and formats the result.
|
|
395
|
-
*
|
|
396
|
-
* @param {Array} oddsArray - The input array of odds objects.
|
|
397
|
-
* @param {Object} commonData - The common data object containing homeTeam information.
|
|
398
|
-
* @returns {Array} The grouped and formatted spread odds.
|
|
399
|
-
*/
|
|
400
|
-
export const groupAndFormatMoneylineOdds = (oddsArray, commonData) => {
|
|
401
|
-
// Group odds by their selection points and selection
|
|
402
|
-
const groupedOdds = oddsArray.reduce((acc: any, odd: any) => {
|
|
403
|
-
const { price, selection, typeId, sportId, type } = odd;
|
|
404
|
-
const key = typeId;
|
|
405
|
-
|
|
406
|
-
if (!acc[key]) {
|
|
407
|
-
acc[key] = { home: null, away: null, draw: null, typeId: null, sportId: null };
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (selection.toLowerCase() === commonData.homeTeam.toLowerCase()) acc[key].home = price;
|
|
411
|
-
else if (selection.toLowerCase() === commonData.awayTeam.toLowerCase()) acc[key].away = price;
|
|
412
|
-
else if (selection.toLowerCase() === DRAW.toLowerCase()) acc[key].draw = price;
|
|
413
|
-
|
|
414
|
-
acc[key].typeId = typeId;
|
|
415
|
-
acc[key].type = type;
|
|
416
|
-
acc[key].sportId = sportId;
|
|
417
|
-
|
|
418
|
-
return acc;
|
|
419
|
-
}, {}) as any;
|
|
420
|
-
// Format the grouped odds into the desired output
|
|
421
|
-
const formattedOdds = (Object.entries(groupedOdds as any) as any).reduce((acc, [_key, value]) => {
|
|
422
|
-
if ((value as any).home !== null && (value as any).away !== null) {
|
|
423
|
-
acc.push({
|
|
424
|
-
odds: (value as any).draw
|
|
425
|
-
? [(value as any).home, (value as any).away, (value as any).draw]
|
|
426
|
-
: [(value as any).home, (value as any).away],
|
|
427
|
-
typeId: value.typeId,
|
|
428
|
-
sportId: value.sportId,
|
|
429
|
-
type: value.type,
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
return acc;
|
|
433
|
-
}, []);
|
|
434
|
-
|
|
435
|
-
return formattedOdds;
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
export const adjustSpreadOnChildOdds = (iterableGroupedOdds, spreadDataForSport, defaultSpreadForLiveMarkets) => {
|
|
439
|
-
const result: any[] = [];
|
|
440
|
-
iterableGroupedOdds.forEach((data) => {
|
|
441
|
-
const hasDrawOdds = data.odds.length === 3;
|
|
442
|
-
const homeTeamOdds = convertOddsToImpl(data.odds[0]) || ZERO;
|
|
443
|
-
const awayTeamOdds = convertOddsToImpl(data.odds[1]) || ZERO;
|
|
444
|
-
const drawOdds = convertOddsToImpl(data.odds[2]) || ZERO;
|
|
445
|
-
const odds = hasDrawOdds ? [homeTeamOdds, awayTeamOdds, drawOdds] : [homeTeamOdds, awayTeamOdds];
|
|
446
|
-
|
|
447
|
-
const isZeroOddsChild = homeTeamOdds === ZERO || awayTeamOdds === ZERO || (hasDrawOdds && drawOdds === ZERO);
|
|
448
|
-
if (!isZeroOddsChild) {
|
|
449
|
-
const spreadData = getSpreadData(
|
|
450
|
-
spreadDataForSport,
|
|
451
|
-
data.sportId,
|
|
452
|
-
data.typeId,
|
|
453
|
-
defaultSpreadForLiveMarkets
|
|
454
|
-
);
|
|
455
|
-
|
|
456
|
-
let adjustedOdds;
|
|
457
|
-
if (spreadData !== null) {
|
|
458
|
-
adjustedOdds = adjustSpreadOnOdds(odds, spreadData.minSpread, spreadData.targetSpread);
|
|
459
|
-
} else {
|
|
460
|
-
adjustedOdds = adjustSpreadOnOdds(odds, defaultSpreadForLiveMarkets, 0);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
result.push({
|
|
464
|
-
...data,
|
|
465
|
-
odds: adjustedOdds,
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
});
|
|
469
|
-
return result;
|
|
470
|
-
};
|
|
1
|
+
import * as oddslib from 'oddslib';
|
|
2
|
+
import { DRAW, MIN_ODDS_FOR_DIFF_CHECKING, MONEYLINE_TYPE_ID, ZERO } from '../constants/common';
|
|
3
|
+
import { checkOddsFromBookmakers } from './bookmakers';
|
|
4
|
+
import { adjustSpreadOnOdds, getSpreadData } from './spread';
|
|
5
|
+
import { MoneylineTypes } from '../enums/sports';
|
|
6
|
+
import { ChildMarket, LeagueInfo } from '../types/sports';
|
|
7
|
+
import { getLeagueInfo } from './sports';
|
|
8
|
+
import { OddsObject } from '../types/odds';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Converts a given odds value from one format to another.
|
|
12
|
+
* Specifically, it converts from 'moneyline' to 'impliedProbability', handling special cases.
|
|
13
|
+
*
|
|
14
|
+
* @param {Number} odds - The odds value to convert.
|
|
15
|
+
* @returns {Number} The converted odds value.
|
|
16
|
+
*/
|
|
17
|
+
export const convertOddsToImpl = (odds) => {
|
|
18
|
+
return odds === ZERO ? 0 : getOddsFromTo('decimal', 'impliedProbability', odds);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Converts odds from one format to another.
|
|
23
|
+
* @param {String} from - The original odds format.
|
|
24
|
+
* @param {String} to - The target odds format.
|
|
25
|
+
* @param {Number} input - The odds value.
|
|
26
|
+
* @returns {Number} The converted odds.
|
|
27
|
+
*/
|
|
28
|
+
export const getOddsFromTo = (from, to, input) => {
|
|
29
|
+
try {
|
|
30
|
+
return oddslib.from(from, input).to(to);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Filters the odds array to find entries matching the specified market name and bookmaker.
|
|
38
|
+
*
|
|
39
|
+
* @param {Array} oddsArray - The array of odds objects.
|
|
40
|
+
* @param {string} marketName - The market name to filter by.
|
|
41
|
+
* @param {Array} liveOddsProviders - Odds providers for live odds
|
|
42
|
+
* @param {Object} commonData - The common data object.
|
|
43
|
+
* @param {boolean} isTwoPositionalSport - Indicates if the sport is a two positional sport.,
|
|
44
|
+
* @returns {Map} The filtered map for odds per provider.
|
|
45
|
+
*/
|
|
46
|
+
export const filterOddsByMarketNameTeamNameBookmaker = (
|
|
47
|
+
oddsArray,
|
|
48
|
+
marketName,
|
|
49
|
+
liveOddsProviders,
|
|
50
|
+
commonData,
|
|
51
|
+
isTwoPositionalSport
|
|
52
|
+
) => {
|
|
53
|
+
const linesMap = new Map<any, any>();
|
|
54
|
+
liveOddsProviders.forEach((oddsProvider) => {
|
|
55
|
+
let homeOdds = 0;
|
|
56
|
+
const homeTeamOddsObject = oddsArray.filter((odd) => {
|
|
57
|
+
return (
|
|
58
|
+
odd &&
|
|
59
|
+
odd.marketName.toLowerCase() === marketName.toLowerCase() &&
|
|
60
|
+
odd.sportsBookName.toLowerCase() == oddsProvider.toLowerCase() &&
|
|
61
|
+
odd.selection.toLowerCase() === commonData.homeTeam.toLowerCase()
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
if (homeTeamOddsObject.length !== 0) {
|
|
65
|
+
homeOdds = homeTeamOddsObject[0].price;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let awayOdds = 0;
|
|
69
|
+
const awayTeamOddsObject = oddsArray.filter(
|
|
70
|
+
(odd) =>
|
|
71
|
+
odd &&
|
|
72
|
+
odd.marketName.toLowerCase() === marketName.toLowerCase() &&
|
|
73
|
+
odd.sportsBookName.toLowerCase() == oddsProvider.toLowerCase() &&
|
|
74
|
+
odd.selection.toLowerCase() === commonData.awayTeam.toLowerCase()
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (awayTeamOddsObject.length !== 0) {
|
|
78
|
+
awayOdds = awayTeamOddsObject[0].price;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let drawOdds = 0;
|
|
82
|
+
if (!isTwoPositionalSport) {
|
|
83
|
+
const drawOddsObject = oddsArray.filter(
|
|
84
|
+
(odd) =>
|
|
85
|
+
odd &&
|
|
86
|
+
odd.marketName.toLowerCase() === marketName.toLowerCase() &&
|
|
87
|
+
odd.sportsBookName.toLowerCase() == oddsProvider.toLowerCase() &&
|
|
88
|
+
odd.selection.toLowerCase() === DRAW.toLowerCase()
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (drawOddsObject.length !== 0) {
|
|
92
|
+
drawOdds = drawOddsObject[0].price;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
linesMap.set(oddsProvider.toLowerCase(), {
|
|
97
|
+
homeOdds: homeOdds,
|
|
98
|
+
awayOdds: awayOdds,
|
|
99
|
+
drawOdds: drawOdds,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
return linesMap;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Retrieves the parent odds for the given event.
|
|
107
|
+
*
|
|
108
|
+
* @param {boolean} isTwoPositionalSport - Indicates if the sport is a two positional sport.
|
|
109
|
+
* @param {Array} sportSpreadData - Spread data specific to the sport.
|
|
110
|
+
* @param {Array} liveOddsProviders - Odds providers for live odds
|
|
111
|
+
* @param {Object} oddsApiObject - Odds data from the API.
|
|
112
|
+
* @param {String} sportId - Sport ID API.
|
|
113
|
+
* @param {Number} defaultSpreadForLiveMarkets - Default spread for live markets,
|
|
114
|
+
* @param {Number} maxPercentageDiffBetwenOdds - Maximum allowed percentage difference between same position odds from different providers
|
|
115
|
+
* @returns {Array} The parent odds for the event [homeOdds, awayOdds, drawOdds].
|
|
116
|
+
*/
|
|
117
|
+
export const getParentOdds = (
|
|
118
|
+
isTwoPositionalSport,
|
|
119
|
+
sportSpreadData,
|
|
120
|
+
liveOddsProviders,
|
|
121
|
+
oddsApiObject,
|
|
122
|
+
sportId,
|
|
123
|
+
defaultSpreadForLiveMarkets,
|
|
124
|
+
maxPercentageDiffBetwenOdds
|
|
125
|
+
) => {
|
|
126
|
+
const commonData = { homeTeam: oddsApiObject.homeTeam, awayTeam: oddsApiObject.awayTeam };
|
|
127
|
+
|
|
128
|
+
// EXTRACTING ODDS FROM THE RESPONSE PER MARKET NAME AND BOOKMAKER
|
|
129
|
+
const moneylineOddsMap = filterOddsByMarketNameTeamNameBookmaker(
|
|
130
|
+
oddsApiObject.odds,
|
|
131
|
+
MoneylineTypes.MONEYLINE,
|
|
132
|
+
liveOddsProviders,
|
|
133
|
+
commonData,
|
|
134
|
+
isTwoPositionalSport
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// CHECKING AND COMPARING ODDS FOR THE GIVEN BOOKMAKERS
|
|
138
|
+
const oddsObject = checkOddsFromBookmakers(
|
|
139
|
+
moneylineOddsMap,
|
|
140
|
+
liveOddsProviders,
|
|
141
|
+
isTwoPositionalSport,
|
|
142
|
+
maxPercentageDiffBetwenOdds,
|
|
143
|
+
MIN_ODDS_FOR_DIFF_CHECKING
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (oddsObject.errorMessage) {
|
|
147
|
+
return {
|
|
148
|
+
odds: isTwoPositionalSport ? [0, 0] : [0, 0, 0],
|
|
149
|
+
errorMessage: oddsObject.errorMessage,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const primaryBookmakerOdds = isTwoPositionalSport
|
|
153
|
+
? [oddsObject.homeOdds, oddsObject.awayOdds]
|
|
154
|
+
: [oddsObject.homeOdds, oddsObject.awayOdds, oddsObject.drawOdds];
|
|
155
|
+
|
|
156
|
+
let parentOdds = primaryBookmakerOdds.map((odd) => convertOddsToImpl(odd));
|
|
157
|
+
const spreadData = getSpreadData(sportSpreadData, sportId, MONEYLINE_TYPE_ID, defaultSpreadForLiveMarkets);
|
|
158
|
+
|
|
159
|
+
if (spreadData !== null) {
|
|
160
|
+
parentOdds = adjustSpreadOnOdds(parentOdds, spreadData.minSpread, spreadData.targetSpread);
|
|
161
|
+
} else {
|
|
162
|
+
// Use min spread by sport if available, otherwise use default min spread
|
|
163
|
+
parentOdds = adjustSpreadOnOdds(parentOdds, defaultSpreadForLiveMarkets, 0);
|
|
164
|
+
}
|
|
165
|
+
return { odds: parentOdds };
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Creates child markets based on the given parameters.
|
|
170
|
+
*
|
|
171
|
+
* @param {Object} leagueId - leagueId AKA sportId
|
|
172
|
+
* @param {Array} spreadDataForSport - Spread data for sport.
|
|
173
|
+
* @param {Object} apiResponseWithOdds - API response from the provider
|
|
174
|
+
* @param {Array} liveOddsProviders - Odds providers for live odds
|
|
175
|
+
* @param {Number} defaultSpreadForLiveMarkets - Default spread for live markets
|
|
176
|
+
* @param {Boolean} leagueMap - League Map info
|
|
177
|
+
* @returns {Array} The child markets.
|
|
178
|
+
*/
|
|
179
|
+
export const createChildMarkets: (
|
|
180
|
+
apiResponseWithOdds: OddsObject,
|
|
181
|
+
spreadDataForSport: any,
|
|
182
|
+
leagueId: number,
|
|
183
|
+
liveOddsProviders: any,
|
|
184
|
+
defaultSpreadForLiveMarkets: any,
|
|
185
|
+
leagueMap: any
|
|
186
|
+
) => ChildMarket[] = (
|
|
187
|
+
apiResponseWithOdds,
|
|
188
|
+
spreadDataForSport,
|
|
189
|
+
leagueId,
|
|
190
|
+
liveOddsProviders,
|
|
191
|
+
defaultSpreadForLiveMarkets,
|
|
192
|
+
leagueMap
|
|
193
|
+
) => {
|
|
194
|
+
const [spreadOdds, totalOdds, moneylineOdds, childMarkets]: any[] = [[], [], [], []];
|
|
195
|
+
const leagueInfo = getLeagueInfo(leagueId, leagueMap);
|
|
196
|
+
const commonData = {
|
|
197
|
+
homeTeam: apiResponseWithOdds.homeTeam,
|
|
198
|
+
awayTeam: apiResponseWithOdds.awayTeam,
|
|
199
|
+
};
|
|
200
|
+
if (leagueInfo.length > 0) {
|
|
201
|
+
// TODO ADD ODDS COMPARISON BETWEEN BOOKMAKERS
|
|
202
|
+
const allChildOdds = filterOddsByMarketNameBookmaker(
|
|
203
|
+
apiResponseWithOdds.odds,
|
|
204
|
+
leagueInfo,
|
|
205
|
+
liveOddsProviders[0]
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
allChildOdds.forEach((odd) => {
|
|
209
|
+
if (odd.type === 'Total') {
|
|
210
|
+
if (Math.abs(Number(odd.points) % 1) === 0.5) totalOdds.push(odd);
|
|
211
|
+
} else if (odd.type === 'Spread') {
|
|
212
|
+
if (Math.abs(Number(odd.points) % 1) === 0.5) spreadOdds.push(odd);
|
|
213
|
+
} else if (odd.type === 'Moneyline') {
|
|
214
|
+
moneylineOdds.push(odd);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const formattedOdds = [
|
|
219
|
+
...groupAndFormatSpreadOdds(spreadOdds, commonData),
|
|
220
|
+
...groupAndFormatTotalOdds(totalOdds, commonData),
|
|
221
|
+
...groupAndFormatMoneylineOdds(moneylineOdds, commonData),
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
const oddsWithSpreadAdjusted = adjustSpreadOnChildOdds(
|
|
225
|
+
formattedOdds,
|
|
226
|
+
spreadDataForSport,
|
|
227
|
+
defaultSpreadForLiveMarkets
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
oddsWithSpreadAdjusted.forEach((data) => {
|
|
231
|
+
const childMarket = {
|
|
232
|
+
leagueId: Number(data.sportId),
|
|
233
|
+
typeId: Number(data.typeId),
|
|
234
|
+
type: data.type.toLowerCase(),
|
|
235
|
+
line: Number(data.line || 0),
|
|
236
|
+
odds: data.odds,
|
|
237
|
+
};
|
|
238
|
+
const leagueInfoByTypeId = leagueInfo.find((league) => Number(league.typeId) === Number(data.typeId));
|
|
239
|
+
const minOdds = leagueInfoByTypeId?.minOdds;
|
|
240
|
+
const maxOdds = leagueInfoByTypeId?.maxOdds;
|
|
241
|
+
if (
|
|
242
|
+
!(
|
|
243
|
+
minOdds &&
|
|
244
|
+
maxOdds &&
|
|
245
|
+
(data.odds[0] >= minOdds ||
|
|
246
|
+
data.odds[0] <= maxOdds ||
|
|
247
|
+
data.odds[1] >= minOdds ||
|
|
248
|
+
data.odds[1] <= maxOdds)
|
|
249
|
+
)
|
|
250
|
+
) {
|
|
251
|
+
childMarkets.push(childMarket);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
} else {
|
|
255
|
+
console.warn(`No child markets for leagueID: ${Number(leagueId)}`);
|
|
256
|
+
}
|
|
257
|
+
return childMarkets;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Filters the odds array to find entries matching the specified market name.
|
|
262
|
+
*
|
|
263
|
+
* @param {Array} oddsArray - The array of odds objects.
|
|
264
|
+
* @param {string} leagueInfos - The market names to filter by.
|
|
265
|
+
* @param {string} oddsProvider - The main odds provider to filter by.
|
|
266
|
+
* @returns {Array} The filtered odds array.
|
|
267
|
+
*/
|
|
268
|
+
export const filterOddsByMarketNameBookmaker = (oddsArray, leagueInfos: LeagueInfo[], oddsProvider) => {
|
|
269
|
+
const allChildMarketsTypes = leagueInfos
|
|
270
|
+
.filter(
|
|
271
|
+
(leagueInfo) =>
|
|
272
|
+
leagueInfo.marketName.toLowerCase() !== MoneylineTypes.MONEYLINE.toLowerCase() &&
|
|
273
|
+
leagueInfo.enabled === 'true'
|
|
274
|
+
)
|
|
275
|
+
.map((leagueInfo) => leagueInfo.marketName.toLowerCase());
|
|
276
|
+
return oddsArray
|
|
277
|
+
.filter(
|
|
278
|
+
(odd) =>
|
|
279
|
+
allChildMarketsTypes.includes(odd.marketName.toLowerCase()) &&
|
|
280
|
+
odd.sportsBookName.toLowerCase() == oddsProvider.toLowerCase()
|
|
281
|
+
)
|
|
282
|
+
.map((odd) => {
|
|
283
|
+
return {
|
|
284
|
+
...odd,
|
|
285
|
+
...leagueInfos.find(
|
|
286
|
+
(leagueInfo) => leagueInfo.marketName.toLowerCase() === odd.marketName.toLowerCase()
|
|
287
|
+
), // using .find() for team totals means that we will always assign 10017 as typeID at this point
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Groups spread odds by their lines and formats the result.
|
|
294
|
+
*
|
|
295
|
+
* @param {Array} oddsArray - The input array of odds objects.
|
|
296
|
+
* @param {Object} commonData - The common data object containing homeTeam information.
|
|
297
|
+
* @returns {Array} The grouped and formatted spread odds.
|
|
298
|
+
*/
|
|
299
|
+
export const groupAndFormatSpreadOdds = (oddsArray, commonData) => {
|
|
300
|
+
// Group odds by their selection points and selection
|
|
301
|
+
const groupedOdds = oddsArray.reduce((acc: any, odd: any) => {
|
|
302
|
+
const { points, marketName, price, selection, typeId, sportId, type } = odd;
|
|
303
|
+
const isHomeTeam = selection === commonData.homeTeam;
|
|
304
|
+
|
|
305
|
+
const key = `${marketName}_${isHomeTeam ? points : -points}`;
|
|
306
|
+
|
|
307
|
+
if (!acc[key]) {
|
|
308
|
+
acc[key] = { home: null, away: null, typeId: null, sportId: null };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (isHomeTeam) {
|
|
312
|
+
acc[key].home = price;
|
|
313
|
+
} else {
|
|
314
|
+
acc[key].away = price;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
acc[key].typeId = typeId;
|
|
318
|
+
acc[key].type = type;
|
|
319
|
+
acc[key].sportId = sportId;
|
|
320
|
+
|
|
321
|
+
return acc;
|
|
322
|
+
}, {}) as any;
|
|
323
|
+
// Format the grouped odds into the desired output
|
|
324
|
+
const formattedOdds = (Object.entries(groupedOdds as any) as any).reduce((acc, [key, value]) => {
|
|
325
|
+
const [_marketName, lineFloat] = key.split('_');
|
|
326
|
+
const line = parseFloat(lineFloat);
|
|
327
|
+
if ((value as any).home !== null && (value as any).away !== null) {
|
|
328
|
+
acc.push({
|
|
329
|
+
line: line as any,
|
|
330
|
+
odds: [(value as any).home, (value as any).away],
|
|
331
|
+
typeId: value.typeId,
|
|
332
|
+
sportId: value.sportId,
|
|
333
|
+
type: value.type,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return acc;
|
|
337
|
+
}, []);
|
|
338
|
+
|
|
339
|
+
return formattedOdds;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Groups odds by selection and points over/under.
|
|
344
|
+
*
|
|
345
|
+
* @param {Array} oddsArray - The array of odds objects.
|
|
346
|
+
* @returns {Object} The grouped odds.
|
|
347
|
+
*/
|
|
348
|
+
export const groupAndFormatTotalOdds = (oddsArray, commonData) => {
|
|
349
|
+
// Group odds by their selection points and selection
|
|
350
|
+
const groupedOdds = oddsArray.reduce((acc, odd) => {
|
|
351
|
+
if (odd) {
|
|
352
|
+
const key = `${odd.marketName}_${odd.selection}_${odd.points}`;
|
|
353
|
+
if (!acc[key]) {
|
|
354
|
+
acc[key] = { over: null, under: null };
|
|
355
|
+
}
|
|
356
|
+
if (odd.selectionLine === 'over') {
|
|
357
|
+
acc[key].over = odd.price;
|
|
358
|
+
} else if (odd.selectionLine === 'under') {
|
|
359
|
+
acc[key].under = odd.price;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
acc[key].typeId = odd.typeId;
|
|
363
|
+
acc[key].type = odd.type;
|
|
364
|
+
acc[key].sportId = odd.sportId;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return acc;
|
|
368
|
+
}, {});
|
|
369
|
+
|
|
370
|
+
// Format the grouped odds into the desired output
|
|
371
|
+
const formattedOdds = (Object.entries(groupedOdds as any) as any).reduce((acc, [key, value]) => {
|
|
372
|
+
const [_marketName, selection, selectionLine] = key.split('_');
|
|
373
|
+
const line = parseFloat(selectionLine);
|
|
374
|
+
|
|
375
|
+
// if we have away team in total odds we know the market is team total and we need to increase typeId by one.
|
|
376
|
+
// if this is false typeId is already mapped correctly
|
|
377
|
+
const isAwayTeam = selection === commonData.awayTeam;
|
|
378
|
+
if ((value as any).over !== null && (value as any).under !== null) {
|
|
379
|
+
acc.push({
|
|
380
|
+
line: line as any,
|
|
381
|
+
odds: [(value as any).over, (value as any).under],
|
|
382
|
+
typeId: !isAwayTeam ? value.typeId : Number(value.typeId) + 1,
|
|
383
|
+
sportId: value.sportId,
|
|
384
|
+
type: value.type,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
return acc;
|
|
388
|
+
}, []);
|
|
389
|
+
|
|
390
|
+
return formattedOdds;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Groups spread odds by their lines and formats the result.
|
|
395
|
+
*
|
|
396
|
+
* @param {Array} oddsArray - The input array of odds objects.
|
|
397
|
+
* @param {Object} commonData - The common data object containing homeTeam information.
|
|
398
|
+
* @returns {Array} The grouped and formatted spread odds.
|
|
399
|
+
*/
|
|
400
|
+
export const groupAndFormatMoneylineOdds = (oddsArray, commonData) => {
|
|
401
|
+
// Group odds by their selection points and selection
|
|
402
|
+
const groupedOdds = oddsArray.reduce((acc: any, odd: any) => {
|
|
403
|
+
const { price, selection, typeId, sportId, type } = odd;
|
|
404
|
+
const key = typeId;
|
|
405
|
+
|
|
406
|
+
if (!acc[key]) {
|
|
407
|
+
acc[key] = { home: null, away: null, draw: null, typeId: null, sportId: null };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (selection.toLowerCase() === commonData.homeTeam.toLowerCase()) acc[key].home = price;
|
|
411
|
+
else if (selection.toLowerCase() === commonData.awayTeam.toLowerCase()) acc[key].away = price;
|
|
412
|
+
else if (selection.toLowerCase() === DRAW.toLowerCase()) acc[key].draw = price;
|
|
413
|
+
|
|
414
|
+
acc[key].typeId = typeId;
|
|
415
|
+
acc[key].type = type;
|
|
416
|
+
acc[key].sportId = sportId;
|
|
417
|
+
|
|
418
|
+
return acc;
|
|
419
|
+
}, {}) as any;
|
|
420
|
+
// Format the grouped odds into the desired output
|
|
421
|
+
const formattedOdds = (Object.entries(groupedOdds as any) as any).reduce((acc, [_key, value]) => {
|
|
422
|
+
if ((value as any).home !== null && (value as any).away !== null) {
|
|
423
|
+
acc.push({
|
|
424
|
+
odds: (value as any).draw
|
|
425
|
+
? [(value as any).home, (value as any).away, (value as any).draw]
|
|
426
|
+
: [(value as any).home, (value as any).away],
|
|
427
|
+
typeId: value.typeId,
|
|
428
|
+
sportId: value.sportId,
|
|
429
|
+
type: value.type,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return acc;
|
|
433
|
+
}, []);
|
|
434
|
+
|
|
435
|
+
return formattedOdds;
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
export const adjustSpreadOnChildOdds = (iterableGroupedOdds, spreadDataForSport, defaultSpreadForLiveMarkets) => {
|
|
439
|
+
const result: any[] = [];
|
|
440
|
+
iterableGroupedOdds.forEach((data) => {
|
|
441
|
+
const hasDrawOdds = data.odds.length === 3;
|
|
442
|
+
const homeTeamOdds = convertOddsToImpl(data.odds[0]) || ZERO;
|
|
443
|
+
const awayTeamOdds = convertOddsToImpl(data.odds[1]) || ZERO;
|
|
444
|
+
const drawOdds = convertOddsToImpl(data.odds[2]) || ZERO;
|
|
445
|
+
const odds = hasDrawOdds ? [homeTeamOdds, awayTeamOdds, drawOdds] : [homeTeamOdds, awayTeamOdds];
|
|
446
|
+
|
|
447
|
+
const isZeroOddsChild = homeTeamOdds === ZERO || awayTeamOdds === ZERO || (hasDrawOdds && drawOdds === ZERO);
|
|
448
|
+
if (!isZeroOddsChild) {
|
|
449
|
+
const spreadData = getSpreadData(
|
|
450
|
+
spreadDataForSport,
|
|
451
|
+
data.sportId,
|
|
452
|
+
data.typeId,
|
|
453
|
+
defaultSpreadForLiveMarkets
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
let adjustedOdds;
|
|
457
|
+
if (spreadData !== null) {
|
|
458
|
+
adjustedOdds = adjustSpreadOnOdds(odds, spreadData.minSpread, spreadData.targetSpread);
|
|
459
|
+
} else {
|
|
460
|
+
adjustedOdds = adjustSpreadOnOdds(odds, defaultSpreadForLiveMarkets, 0);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
result.push({
|
|
464
|
+
...data,
|
|
465
|
+
odds: adjustedOdds,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
return result;
|
|
470
|
+
};
|