overtime-live-trading-utils 0.2.10 → 0.3.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.
@@ -16,6 +16,7 @@ export enum Sport {
16
16
  GOLF = 'Golf',
17
17
  TABLE_TENNIS = 'TableTennis',
18
18
  POLITICS = 'Politics',
19
+ FUTURES = 'Futures',
19
20
  EMPTY = '',
20
21
  }
21
22
 
@@ -128,6 +129,9 @@ export enum League {
128
129
  ENGLAND_LEGAUE_1 = 20126,
129
130
  URUGUAY_PRIMERA_DIVISION = 20127,
130
131
  EUROCUP = 20200,
132
+ NFL_FUTURES = 30002,
133
+ NBA_FUTURES = 30004,
134
+ EPL_FUTURES = 30011,
131
135
  GOLF_H2H = 100021,
132
136
  GOLF_WINNER = 100121,
133
137
  TENNIS_GRAND_SLAM_LIVE_MAPPING_V2_1 = 15312,
@@ -0,0 +1,17 @@
1
+ export type LeagueInfo = {
2
+ sportId: number;
3
+ typeId: number;
4
+ marketName: string;
5
+ type: string;
6
+ enabled: string;
7
+ minOdds: number;
8
+ maxOdds: number;
9
+ };
10
+
11
+ export type ChildMarket = {
12
+ leagueId: number;
13
+ typeId: number;
14
+ type: string;
15
+ line: number;
16
+ odds: Array<number>;
17
+ };
@@ -1,7 +1,5 @@
1
1
  import * as oddslib from 'oddslib';
2
- import { TAG_CHILD_SPREAD, TAG_CHILD_TOTALS } from '../constants/common';
3
- import { filterOddsByMarketNameBookmaker, formatSpreadOdds, getParentOdds, processTotalOdds } from './odds';
4
- import { getLeagueSpreadType, getLeagueTotalType } from './sports';
2
+ import { createChildMarkets, getParentOdds } from './odds';
5
3
  /**
6
4
  * Processes a single sports event. This function maps event data to a specific format,
7
5
  * filters invalid events, and optionally fetches player properties if the sport supports it.
@@ -25,7 +23,7 @@ export const processMarket = (
25
23
  isDrawAvailable,
26
24
  defaultSpreadForLiveMarkets,
27
25
  maxPercentageDiffBetwenOdds,
28
- isTestnet
26
+ leagueMap
29
27
  ) => {
30
28
  const sportSpreadData = spreadData.filter((data) => data.sportId === String(market.leagueId));
31
29
 
@@ -62,13 +60,13 @@ export const processMarket = (
62
60
  });
63
61
  }
64
62
 
65
- const childMarkets = getChildMarkets(
66
- market.leagueId,
67
- sportSpreadData,
63
+ const childMarkets = createChildMarkets(
68
64
  apiResponseWithOdds,
65
+ sportSpreadData,
66
+ market.leagueId,
69
67
  liveOddsProviders,
70
68
  defaultSpreadForLiveMarkets,
71
- isTestnet
69
+ leagueMap
72
70
  );
73
71
 
74
72
  const packedChildMarkets = childMarkets.map((childMarket: any) => {
@@ -96,144 +94,3 @@ export const processMarket = (
96
94
 
97
95
  return market;
98
96
  };
99
-
100
- /**
101
- * Retrieves the child markets for the given event.
102
- *
103
- * @param {Object} market - The market object from the API
104
- * @param {Array} spreadDataForSport - Spread data for sport.
105
- * @param {Object} apiResponseWithOdds - API response from the provider
106
- * @param {Number} defaultSpreadForLiveMarkets - Default spread for live markets
107
- * @param {Boolean} isTestnet - Flag showing should we process for testnet or mainnet
108
- * @returns {Array} The child markets for the event.
109
- */
110
- const getChildMarkets = (
111
- leagueId,
112
- spreadDataForSport,
113
- apiResponseWithOdds,
114
- liveOddsProviders,
115
- defaultSpreadForLiveMarkets,
116
- isTestnet
117
- ) => {
118
- let childMarkets = [];
119
-
120
- // Create Spread Child Markets
121
- childMarkets = childMarkets.concat(
122
- createSpreadChildMarkets(
123
- apiResponseWithOdds,
124
- leagueId,
125
- spreadDataForSport,
126
- liveOddsProviders,
127
- defaultSpreadForLiveMarkets,
128
- isTestnet
129
- )
130
- );
131
-
132
- // Create Total Child Markets
133
- childMarkets = childMarkets.concat(
134
- createTotalChildMarkets(
135
- apiResponseWithOdds,
136
- leagueId,
137
- spreadDataForSport,
138
- liveOddsProviders,
139
- defaultSpreadForLiveMarkets,
140
- isTestnet
141
- )
142
- );
143
-
144
- return childMarkets;
145
- };
146
-
147
- /**
148
- * Creates spread child markets based on the given parameters.
149
- *
150
- * @param {Object} market - The market object from the API
151
- * @param {Array} spreadDataForSport - Spread data for sport.
152
- * @param {Object} apiResponseWithOdds - API response from the provider
153
- * @param {Array} liveOddsProviders - Odds providers for live odds
154
- * @param {Number} defaultSpreadForLiveMarkets - Default spread for live markets
155
- * @param {Boolean} isTestnet - Flag showing should we process for testnet or mainnet
156
- * @returns {Array} The spread child markets.
157
- */
158
- export const createSpreadChildMarkets = (
159
- apiResponseWithOdds,
160
- leagueId,
161
- spreadDataForSport,
162
- liveOddsProviders,
163
- defaultSpreadForLiveMarkets,
164
- isTestnet
165
- ) => {
166
- const childMarkets = [] as any;
167
- const spreadType = getLeagueSpreadType(leagueId, isTestnet);
168
- const commonData = {
169
- homeTeam: apiResponseWithOdds.home_team,
170
- awayTeam: apiResponseWithOdds.away_team,
171
- };
172
- if (spreadType) {
173
- // TODO ADD ODDS COMPARISON BETWEEN BOOKMAKERS
174
- const allSpreadOdds = filterOddsByMarketNameBookmaker(
175
- apiResponseWithOdds.odds,
176
- spreadType,
177
- liveOddsProviders[0]
178
- );
179
-
180
- if (allSpreadOdds.length > 0) {
181
- const formattedSpreadOdds = formatSpreadOdds(
182
- allSpreadOdds,
183
- commonData,
184
- leagueId,
185
- spreadDataForSport,
186
- TAG_CHILD_SPREAD,
187
- defaultSpreadForLiveMarkets
188
- );
189
-
190
- childMarkets.push(...formattedSpreadOdds);
191
- }
192
- } else {
193
- console.warn(`Spread type for sport ID ${leagueId} not found.`);
194
- }
195
- return childMarkets;
196
- };
197
-
198
- /**
199
- * Creates total child markets based on the given parameters.
200
- *
201
- * @param {Object} market - The market object from the API
202
- * @param {Array} spreadDataForSport - Spread data for sport.
203
- * @param {Object} apiResponseWithOdds - API response from the provider
204
- * @param {Array} liveOddsProviders - Odds providers for live odds
205
- * @param {Number} defaultSpreadForLiveMarkets - Default spread for live markets
206
- * @param {Boolean} isTestnet - Flag showing should we process for testnet or mainnet
207
- * @returns {Array} The total child markets.
208
- */
209
- export const createTotalChildMarkets = (
210
- apiResponseWithOdds,
211
- leagueId,
212
- spreadDataForSport,
213
- liveOddsProviders,
214
- defaultSpreadForLiveMarkets,
215
- isTestnet
216
- ) => {
217
- const childMarkets = [] as any;
218
- const totalType = getLeagueTotalType(leagueId, isTestnet);
219
-
220
- if (totalType) {
221
- // TODO ADD ODDS COMPARISON BETWEEN BOOKMAKERS
222
- const totalOdds = filterOddsByMarketNameBookmaker(apiResponseWithOdds.odds, totalType, liveOddsProviders[0]);
223
-
224
- if (totalOdds.length > 0) {
225
- childMarkets.push(
226
- ...processTotalOdds(
227
- totalOdds,
228
- leagueId,
229
- spreadDataForSport,
230
- TAG_CHILD_TOTALS,
231
- defaultSpreadForLiveMarkets
232
- )
233
- );
234
- }
235
- } else {
236
- console.warn(`Configuration (totals) for sport ID ${leagueId} not found.`);
237
- }
238
- return childMarkets;
239
- };
package/src/utils/odds.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import * as oddslib from 'oddslib';
2
- import { DRAW, LIVE_TYPE_ID_BASE, MIN_ODDS_FOR_DIFF_CHECKING, ZERO } from '../constants/common';
3
- import { statusCodes } from '../enums/statuses';
2
+ import { DRAW, MIN_ODDS_FOR_DIFF_CHECKING, MONEYLINE_TYPE_ID, ZERO } from '../constants/common';
4
3
  import { checkOddsFromBookmakers } from './bookmakers';
5
4
  import { adjustSpreadOnOdds, getSpreadData } from './spread';
6
5
  import { MoneylineTypes } from '../enums/sports';
6
+ import { ChildMarket, LeagueInfo } from '../types/sports';
7
+ import { getLeagueInfo } from './sports';
7
8
 
8
9
  /**
9
10
  * Converts a given odds value from one format to another.
@@ -152,7 +153,7 @@ export const getParentOdds = (
152
153
  : [oddsObject.homeOdds, oddsObject.awayOdds, oddsObject.drawOdds];
153
154
 
154
155
  let parentOdds = primaryBookmakerOdds.map((odd) => convertOddsToImpl(odd));
155
- const spreadData = getSpreadData(sportSpreadData, sportId, LIVE_TYPE_ID_BASE, defaultSpreadForLiveMarkets);
156
+ const spreadData = getSpreadData(sportSpreadData, sportId, MONEYLINE_TYPE_ID, defaultSpreadForLiveMarkets);
156
157
 
157
158
  if (spreadData !== null) {
158
159
  parentOdds = adjustSpreadOnOdds(parentOdds, spreadData.minSpread, spreadData.targetSpread);
@@ -163,102 +164,123 @@ export const getParentOdds = (
163
164
  return { odds: parentOdds };
164
165
  };
165
166
 
166
- // TODO: Expand this method to support multiple marketNames
167
167
  /**
168
- * Filters the odds array to find entries matching the specified market name.
169
- *
170
- * @param {Array} oddsArray - The array of odds objects.
171
- * @param {string} marketName - The market name to filter by.
172
- * @param {string} oddsProvider - The main odds provider to filter by.
173
- * @returns {Array} The filtered odds array.
174
- */
175
- export const filterOddsByMarketNameBookmaker = (oddsArray, marketName, oddsProvider) => {
176
- return oddsArray.filter(
177
- (odd) =>
178
- odd.market_name.toLowerCase() === marketName.toLowerCase() &&
179
- odd.sports_book_name.toLowerCase() == oddsProvider.toLowerCase()
180
- );
181
- };
182
-
183
- // TODO: Unify the same code from formatSpreadOdds method and from processTotalOdds method
184
- /**
185
- * Formats the spread odds and creates market objects.
168
+ * Creates child markets based on the given parameters.
186
169
  *
187
- * @param {Array} spreadOdds - The spread odds array.
188
- * @param {Object} commonData - The common data object.
189
- * @param {Object} market - The sport ID from the API.
190
- * @param {Array} spreadDataForSport - Spread data for the sport.
191
- * @param {Number} typeId - typeID
192
- * @param {Number} defaultSpreadForLiveMarkets - Default spread for live markets,
193
- * @returns {Array} The formatted spread markets.
170
+ * @param {Object} leagueId - leagueId AKA sportId
171
+ * @param {Array} spreadDataForSport - Spread data for sport.
172
+ * @param {Object} apiResponseWithOdds - API response from the provider
173
+ * @param {Array} liveOddsProviders - Odds providers for live odds
174
+ * @param {Number} defaultSpreadForLiveMarkets - Default spread for live markets
175
+ * @param {Boolean} leagueMap - League Map info
176
+ * @returns {Array} The child markets.
194
177
  */
195
- export const formatSpreadOdds = (
196
- spreadOdds,
197
- commonData,
198
- leagueId,
178
+ export const createChildMarkets: (
179
+ apiResponseWithOdds: any,
180
+ spreadDataForSport: any,
181
+ leagueId: number,
182
+ liveOddsProviders: any,
183
+ defaultSpreadForLiveMarkets: any,
184
+ leagueMap: any
185
+ ) => ChildMarket[] = (
186
+ apiResponseWithOdds,
199
187
  spreadDataForSport,
200
- typeId,
201
- defaultSpreadForLiveMarkets
188
+ leagueId,
189
+ liveOddsProviders,
190
+ defaultSpreadForLiveMarkets,
191
+ leagueMap
202
192
  ) => {
203
- const childMarkets = [] as any;
204
- const validSpreadOdds = spreadOdds.filter((odd) => odd && Math.abs(odd.selection_points % 1) === 0.5) as any;
205
- const formattedSpreadOdds = groupAndFormatSpreadOdds(validSpreadOdds, commonData);
193
+ const [spreadOdds, totalOdds, childMarkets]: any[] = [[], [], []];
194
+ const leagueInfo = getLeagueInfo(leagueId, leagueMap);
195
+ const commonData = {
196
+ homeTeam: apiResponseWithOdds.home_team,
197
+ awayTeam: apiResponseWithOdds.away_team,
198
+ };
199
+ if (leagueInfo.length > 0) {
200
+ // TODO ADD ODDS COMPARISON BETWEEN BOOKMAKERS
201
+ const allChildOdds = filterOddsByMarketNameBookmaker(
202
+ apiResponseWithOdds.odds,
203
+ leagueInfo,
204
+ liveOddsProviders[0]
205
+ );
206
206
 
207
- formattedSpreadOdds.forEach(({ line, odds }) => {
208
- let homeTeamOdds = convertOddsToImpl(odds[0]) || ZERO;
209
- let awayTeamOdds = convertOddsToImpl(odds[1]) || ZERO;
210
- let isZeroOddsChild = homeTeamOdds === ZERO || awayTeamOdds === ZERO;
207
+ const allValidOdds = allChildOdds.filter((odd) => odd && Math.abs(odd.selection_points % 1) === 0.5) as any;
211
208
 
212
- if (!isZeroOddsChild) {
213
- const spreadData = getSpreadData(spreadDataForSport, leagueId, typeId, defaultSpreadForLiveMarkets);
214
- if (spreadData !== null) {
215
- let adjustedOdds = adjustSpreadOnOdds(
216
- [homeTeamOdds, awayTeamOdds],
217
- spreadData.minSpread,
218
- spreadData.targetSpread
219
- );
220
- if (adjustedOdds.some((prob) => prob === ZERO)) {
221
- isZeroOddsChild = true;
222
- } else {
223
- [homeTeamOdds, awayTeamOdds] = adjustedOdds;
224
- }
225
- } else {
226
- let adjustedOdds = adjustSpreadOnOdds([homeTeamOdds, awayTeamOdds], defaultSpreadForLiveMarkets, 0);
227
- if (adjustedOdds.some((prob) => prob === ZERO)) {
228
- isZeroOddsChild = true;
229
- } else {
230
- [homeTeamOdds, awayTeamOdds] = adjustedOdds;
231
- }
209
+ allValidOdds.forEach((odd) => {
210
+ if (odd.type === 'Total') {
211
+ totalOdds.push(odd);
212
+ } else if (odd.type === 'Spread') {
213
+ spreadOdds.push(odd);
232
214
  }
233
- }
215
+ });
234
216
 
235
- const minOdds = process.env.MIN_ODDS_FOR_CHILD_MARKETS_FOR_LIVE;
236
- const maxOdds = process.env.MAX_ODDS_FOR_CHILD_MARKETS_FOR_LIVE;
237
-
238
- if (
239
- !(
240
- minOdds &&
241
- maxOdds &&
242
- (homeTeamOdds >= minOdds ||
243
- homeTeamOdds <= maxOdds ||
244
- awayTeamOdds >= minOdds ||
245
- awayTeamOdds <= maxOdds)
246
- )
247
- ) {
248
- childMarkets.push({
249
- leagueId,
250
- typeId: typeId,
251
- type: 'spread',
252
- results: [],
253
- status: isZeroOddsChild ? statusCodes.PAUSED : statusCodes.OPEN,
254
- line: line,
255
- odds: [homeTeamOdds, awayTeamOdds],
256
- });
257
- }
258
- });
217
+ const formattedOdds = [
218
+ ...groupAndFormatSpreadOdds(spreadOdds, commonData),
219
+ ...groupAndFormatTotalOdds(totalOdds, commonData),
220
+ ];
221
+
222
+ const oddsWithSpreadAdjusted = adjustSpreadOnChildOdds(
223
+ formattedOdds,
224
+ spreadDataForSport,
225
+ defaultSpreadForLiveMarkets
226
+ );
227
+
228
+ oddsWithSpreadAdjusted.forEach((data) => {
229
+ const childMarket = {
230
+ leagueId: Number(data.sportId),
231
+ typeId: Number(data.typeId),
232
+ type: data.type.toLowerCase(),
233
+ line: Number(data.line),
234
+ odds: data.odds,
235
+ };
236
+ const leagueInfoByTypeId = leagueInfo.find((league) => Number(league.typeId) === Number(data.typeId));
237
+ const minOdds = leagueInfoByTypeId?.minOdds;
238
+ const maxOdds = leagueInfoByTypeId?.maxOdds;
239
+ if (
240
+ !(
241
+ minOdds &&
242
+ maxOdds &&
243
+ (data.odds[0] >= minOdds ||
244
+ data.odds[0] <= maxOdds ||
245
+ data.odds[1] >= minOdds ||
246
+ data.odds[1] <= maxOdds)
247
+ )
248
+ ) {
249
+ childMarkets.push(childMarket);
250
+ }
251
+ });
252
+ } else {
253
+ console.warn(`No child markets for leagueID: ${Number(leagueId)}`);
254
+ }
259
255
  return childMarkets;
260
256
  };
261
257
 
258
+ /**
259
+ * Filters the odds array to find entries matching the specified market name.
260
+ *
261
+ * @param {Array} oddsArray - The array of odds objects.
262
+ * @param {string} leagueInfos - The market names to filter by.
263
+ * @param {string} oddsProvider - The main odds provider to filter by.
264
+ * @returns {Array} The filtered odds array.
265
+ */
266
+ export const filterOddsByMarketNameBookmaker = (oddsArray, leagueInfos: LeagueInfo[], oddsProvider) => {
267
+ const allChildMarketsTypes = leagueInfos
268
+ .filter((leagueInfo) => leagueInfo.marketName.toLowerCase() !== MoneylineTypes.MONEYLINE.toLowerCase())
269
+ .map((leagueInfo) => leagueInfo.marketName.toLowerCase());
270
+ return oddsArray
271
+ .filter(
272
+ (odd) =>
273
+ allChildMarketsTypes.includes(odd.market_name.toLowerCase()) &&
274
+ odd.sports_book_name.toLowerCase() == oddsProvider.toLowerCase()
275
+ )
276
+ .map((odd) => {
277
+ return {
278
+ ...odd,
279
+ ...leagueInfos.find((leagueInfo) => leagueInfo.marketName === odd.market_name), // using .find() for team totals means that we will always assign 10017 as typeID at this point
280
+ };
281
+ });
282
+ };
283
+
262
284
  /**
263
285
  * Groups spread odds by their lines and formats the result.
264
286
  *
@@ -269,13 +291,13 @@ export const formatSpreadOdds = (
269
291
  export const groupAndFormatSpreadOdds = (oddsArray, commonData) => {
270
292
  // Group odds by their selection points and selection
271
293
  const groupedOdds = oddsArray.reduce((acc: any, odd: any) => {
272
- const { selection_points, price, selection } = odd;
294
+ const { selection_points, price, selection, typeId, sportId, type } = odd;
273
295
  const isHomeTeam = selection === commonData.homeTeam;
274
296
 
275
297
  const key = isHomeTeam ? selection_points : -selection_points;
276
298
 
277
299
  if (!acc[key]) {
278
- acc[key] = { home: null, away: null };
300
+ acc[key] = { home: null, away: null, typeId: null, sportId: null };
279
301
  }
280
302
 
281
303
  if (isHomeTeam) {
@@ -284,6 +306,10 @@ export const groupAndFormatSpreadOdds = (oddsArray, commonData) => {
284
306
  acc[key].away = price;
285
307
  }
286
308
 
309
+ acc[key].typeId = typeId;
310
+ acc[key].type = type;
311
+ acc[key].sportId = sportId;
312
+
287
313
  return acc;
288
314
  }, {}) as any;
289
315
  // Format the grouped odds into the desired output
@@ -293,6 +319,9 @@ export const groupAndFormatSpreadOdds = (oddsArray, commonData) => {
293
319
  acc.push({
294
320
  line: line as any,
295
321
  odds: [(value as any).home, (value as any).away],
322
+ typeId: value.typeId,
323
+ sportId: value.sportId,
324
+ type: value.type,
296
325
  });
297
326
  }
298
327
  return acc;
@@ -301,89 +330,15 @@ export const groupAndFormatSpreadOdds = (oddsArray, commonData) => {
301
330
  return formattedOdds;
302
331
  };
303
332
 
304
- /**
305
- * Processes total odds to create market objects.
306
- *
307
- * @param {Array} totalOdds - The total odds array.
308
- * @param {Object} commonData - The common data object.
309
- * @param {Object} market - The sport ID from the API.
310
- * @param {Array} spreadDataForSport - Spread data for the sport.
311
- * @param {Number} typeId - typeID
312
- * @param {Number} defaultSpreadForLiveMarkets - Default spread for live markets,
313
- * @returns {Array} The processed total odds market objects.
314
- */
315
- export const processTotalOdds = (totalOdds, leagueId, spreadDataForSport, typeId, defaultSpreadForLiveMarkets) => {
316
- const childMarkets = [] as any;
317
- const validTotalOdds = totalOdds.filter((odd) => odd && Math.abs(odd.selection_points % 1) === 0.5);
318
- const groupedOdds = groupOddsBySelectionAndPoints(validTotalOdds);
319
- const iterableGroupedOdds = Object.entries(groupedOdds) as any;
320
-
321
- iterableGroupedOdds.forEach(([key, { over, under }]) => {
322
- const [_, selection_points] = key.split('_');
323
-
324
- let overOdds = convertOddsToImpl(over) || ZERO;
325
- let underOdds = convertOddsToImpl(under) || ZERO;
326
- let isZeroOddsChild = overOdds === ZERO || underOdds === ZERO;
327
-
328
- if (!isZeroOddsChild) {
329
- const spreadData = getSpreadData(spreadDataForSport, leagueId, typeId, defaultSpreadForLiveMarkets);
330
- if (spreadData !== null) {
331
- let adjustedOdds = adjustSpreadOnOdds(
332
- [overOdds, underOdds],
333
- spreadData.minSpread,
334
- spreadData.targetSpread
335
- );
336
- if (adjustedOdds.some((prob) => prob === ZERO)) {
337
- isZeroOddsChild = true;
338
- } else {
339
- [overOdds, underOdds] = adjustedOdds;
340
- }
341
- } else {
342
- // Use min spread by sport if available, otherwise use default min spread
343
- let adjustedOdds = adjustSpreadOnOdds([overOdds, underOdds], defaultSpreadForLiveMarkets, 0);
344
- if (adjustedOdds.some((prob) => prob === ZERO)) {
345
- isZeroOddsChild = true;
346
- } else {
347
- [overOdds, underOdds] = adjustedOdds;
348
- }
349
- }
350
- }
351
-
352
- const minOdds = process.env.MIN_ODDS_FOR_CHILD_MARKETS_FOR_LIVE;
353
- const maxOdds = process.env.MAX_ODDS_FOR_CHILD_MARKETS_FOR_LIVE;
354
-
355
- const childMarket = {
356
- leagueId,
357
- typeId: typeId,
358
- type: 'total',
359
- results: [],
360
- status: isZeroOddsChild ? statusCodes.PAUSED : statusCodes.OPEN,
361
- line: parseFloat(selection_points),
362
- odds: [overOdds, underOdds],
363
- };
364
-
365
- if (
366
- !(
367
- minOdds &&
368
- maxOdds &&
369
- (overOdds >= minOdds || overOdds <= maxOdds || underOdds >= minOdds || underOdds <= maxOdds)
370
- )
371
- ) {
372
- childMarkets.push(childMarket);
373
- }
374
- });
375
-
376
- return childMarkets;
377
- };
378
-
379
333
  /**
380
334
  * Groups odds by selection and points over/under.
381
335
  *
382
336
  * @param {Array} oddsArray - The array of odds objects.
383
337
  * @returns {Object} The grouped odds.
384
338
  */
385
- export const groupOddsBySelectionAndPoints = (oddsArray) => {
386
- return oddsArray.reduce((acc, odd) => {
339
+ export const groupAndFormatTotalOdds = (oddsArray, commonData) => {
340
+ // Group odds by their selection points and selection
341
+ const groupedOdds = oddsArray.reduce((acc, odd) => {
387
342
  if (odd) {
388
343
  const key = `${odd.selection}_${odd.selection_points}`;
389
344
  if (!acc[key]) {
@@ -394,8 +349,67 @@ export const groupOddsBySelectionAndPoints = (oddsArray) => {
394
349
  } else if (odd.selection_line === 'under') {
395
350
  acc[key].under = odd.price;
396
351
  }
352
+
353
+ acc[key].typeId = odd.typeId;
354
+ acc[key].type = odd.type;
355
+ acc[key].sportId = odd.sportId;
397
356
  }
398
357
 
399
358
  return acc;
400
359
  }, {});
360
+
361
+ // Format the grouped odds into the desired output
362
+ const formattedOdds = (Object.entries(groupedOdds as any) as any).reduce((acc, [key, value]) => {
363
+ const [selection, selectionLine] = key.split('_');
364
+ const line = parseFloat(selectionLine);
365
+
366
+ // if we have away team in total odds we know the market is team total and we need to increase typeId by one.
367
+ // if this is false typeId is already mapped correctly
368
+ const isAwayTeam = selection === commonData.awayTeam;
369
+ if ((value as any).over !== null && (value as any).under !== null) {
370
+ acc.push({
371
+ line: line as any,
372
+ odds: [(value as any).over, (value as any).under],
373
+ typeId: !isAwayTeam ? value.typeId : Number(value.typeId) + 1,
374
+ sportId: value.sportId,
375
+ type: value.type,
376
+ });
377
+ }
378
+ return acc;
379
+ }, []);
380
+
381
+ return formattedOdds;
382
+ };
383
+
384
+ export const adjustSpreadOnChildOdds = (iterableGroupedOdds, spreadDataForSport, defaultSpreadForLiveMarkets) => {
385
+ const result: any[] = [];
386
+ iterableGroupedOdds.forEach((data) => {
387
+ let homeTeamOdds = convertOddsToImpl(data.odds[0]) || ZERO;
388
+ let awayTeamOdds = convertOddsToImpl(data.odds[1]) || ZERO;
389
+ let isZeroOddsChild = homeTeamOdds === ZERO || awayTeamOdds === ZERO;
390
+ if (!isZeroOddsChild) {
391
+ const spreadData = getSpreadData(
392
+ spreadDataForSport,
393
+ data.sportId,
394
+ data.typeId,
395
+ defaultSpreadForLiveMarkets
396
+ );
397
+ let adjustedOdds;
398
+ if (spreadData !== null) {
399
+ adjustedOdds = adjustSpreadOnOdds(
400
+ [homeTeamOdds, awayTeamOdds],
401
+ spreadData.minSpread,
402
+ spreadData.targetSpread
403
+ );
404
+ } else {
405
+ adjustedOdds = adjustSpreadOnOdds([homeTeamOdds, awayTeamOdds], defaultSpreadForLiveMarkets, 0);
406
+ }
407
+ [homeTeamOdds, awayTeamOdds] = adjustedOdds;
408
+ result.push({
409
+ ...data,
410
+ odds: adjustedOdds,
411
+ });
412
+ }
413
+ });
414
+ return result;
401
415
  };