overtime-live-trading-utils 1.0.8 → 1.1.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.
@@ -0,0 +1,77 @@
1
+ import { DIFF_BETWEEN_BOOKMAKERS_MESSAGE, ZERO_ODDS_MESSAGE } from '../../constants/errors';
2
+ import { processMarket } from '../../utils/markets';
3
+ import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
4
+ import { LeagueMocks } from '../mock/MockLeagueMap';
5
+ import { MockOnlyMoneyline, MockOnlyMoneylineWithDifferentSportsbook } from '../mock/MockOpticSoccer';
6
+ import { mockSoccer } from '../mock/MockSoccerRedis';
7
+
8
+ describe('Bookmakers', () => {
9
+ it('Should return zero odds for moneyline when one of the bookmakers has no odds', () => {
10
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
11
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOnlyMoneyline));
12
+ const market = processMarket(
13
+ freshMockSoccer,
14
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
15
+ ['draftkings', 'bovada'],
16
+ [],
17
+ true,
18
+ undefined,
19
+ undefined,
20
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals
21
+ );
22
+
23
+ const hasOdds = market.odds.some(
24
+ (odd) => odd.american !== 0 || odd.decimal !== 0 || odd.normalizedImplied !== 0
25
+ );
26
+
27
+ expect(hasOdds).toBe(false);
28
+ expect(market).toHaveProperty('errorMessage');
29
+ expect(market.errorMessage).toBe(ZERO_ODDS_MESSAGE);
30
+ });
31
+
32
+ it('Should return zero odds for moneyline when there is quote diff between bookmakers', () => {
33
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
34
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOnlyMoneylineWithDifferentSportsbook));
35
+ const market = processMarket(
36
+ freshMockSoccer,
37
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
38
+ ['draftkings', 'bovada'],
39
+ [],
40
+ true,
41
+ undefined,
42
+ 5,
43
+ LeagueMocks.leagueInfoOnlyParent
44
+ );
45
+
46
+ const hasOdds = market.odds.some(
47
+ (odd) => odd.american !== 0 || odd.decimal !== 0 || odd.normalizedImplied !== 0
48
+ );
49
+
50
+ expect(hasOdds).toBe(false);
51
+ expect(market).toHaveProperty('errorMessage');
52
+ expect(market.errorMessage).toBe(DIFF_BETWEEN_BOOKMAKERS_MESSAGE);
53
+ });
54
+
55
+ it('Should return zero odds for moneyline as no matching bookmaker was provided', () => {
56
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
57
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOnlyMoneyline));
58
+ const market = processMarket(
59
+ freshMockSoccer,
60
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
61
+ ['bovada', 'draftkings'],
62
+ [],
63
+ true,
64
+ undefined,
65
+ undefined,
66
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals
67
+ );
68
+
69
+ const hasOdds = market.odds.some(
70
+ (odd) => odd.american !== 0 || odd.decimal !== 0 || odd.normalizedImplied !== 0
71
+ );
72
+
73
+ expect(hasOdds).toBe(false);
74
+ expect(market).toHaveProperty('errorMessage');
75
+ expect(market.errorMessage).toBe(ZERO_ODDS_MESSAGE); // should be no matching bookmakers mesage
76
+ });
77
+ });
@@ -0,0 +1,133 @@
1
+ import { processMarket } from '../../utils/markets';
2
+ import { mockSoccer } from '../mock/MockSoccerRedis';
3
+ import { MockOnlyMoneyline, MockOpticSoccer } from '../mock/MockOpticSoccer';
4
+ import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
5
+ import { LeagueMocks } from '../mock/MockLeagueMap';
6
+ import { NO_MARKETS_FOR_LEAGUE_ID } from '../../constants/errors';
7
+
8
+ describe('Markets', () => {
9
+ describe('LeagueMap configuration', () => {
10
+ it('Should return an empty array for child markets when they are not added to list', () => {
11
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
12
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOpticSoccer));
13
+
14
+ const market = processMarket(
15
+ freshMockSoccer,
16
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
17
+ ['draftkings'],
18
+ [],
19
+ true,
20
+ undefined,
21
+ undefined,
22
+ LeagueMocks.leagueInfoOnlyParent
23
+ );
24
+
25
+ expect(market.childMarkets).toHaveLength(0);
26
+ });
27
+
28
+ it('Should return an empty array for child markets when they are disabled', () => {
29
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
30
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOpticSoccer));
31
+ const market = processMarket(
32
+ freshMockSoccer,
33
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
34
+ ['draftkings'],
35
+ [],
36
+ true,
37
+ undefined,
38
+ undefined,
39
+ LeagueMocks.leagueInfoMockDisabledChilds
40
+ );
41
+
42
+ expect(market.childMarkets).toHaveLength(0);
43
+ });
44
+
45
+ it('Should return only spread child markets without total child markets', () => {
46
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
47
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOpticSoccer));
48
+ const market = processMarket(
49
+ freshMockSoccer,
50
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
51
+ ['draftkings'],
52
+ [],
53
+ true,
54
+ undefined,
55
+ undefined,
56
+ LeagueMocks.leagueInfoEnabledSpreadDisabledTotals
57
+ );
58
+
59
+ const containsSpread = market.childMarkets.some((child) => child.type === 'spread');
60
+ const containsTotal = market.childMarkets.some((child) => child.type === 'total');
61
+
62
+ expect(containsSpread).toBe(true);
63
+ expect(containsTotal).toBe(false);
64
+ });
65
+
66
+ it('Should return both totals and spread child markets', () => {
67
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
68
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOpticSoccer));
69
+ const market = processMarket(
70
+ freshMockSoccer,
71
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
72
+ ['draftkings'],
73
+ [],
74
+ true,
75
+ undefined,
76
+ undefined,
77
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals
78
+ );
79
+
80
+ const containsSpread = market.childMarkets.some((child) => child.type === 'spread');
81
+ const containsTotal = market.childMarkets.some((child) => child.type === 'total');
82
+
83
+ expect(containsSpread).toBe(true);
84
+ expect(containsTotal).toBe(true);
85
+ });
86
+
87
+ it('Should return all child markets', () => {
88
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
89
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOpticSoccer));
90
+ const market = processMarket(
91
+ freshMockSoccer,
92
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
93
+ ['draftkings'],
94
+ [],
95
+ true,
96
+ undefined,
97
+ undefined,
98
+ LeagueMocks.leagueInfoEnabledAll
99
+ );
100
+
101
+ const containsSpread = market.childMarkets.some((child) => child.type === 'spread');
102
+ const containsTotal = market.childMarkets.some((child) => child.type === 'total');
103
+ const containsChildMoneyline = market.childMarkets.some((child) => child.type === 'moneyline');
104
+
105
+ expect(containsSpread).toBe(true);
106
+ expect(containsTotal).toBe(true);
107
+ expect(containsChildMoneyline).toBe(true);
108
+ });
109
+
110
+ it('Should return warning message that there are is no configuration available in league map csv', () => {
111
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
112
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOnlyMoneyline));
113
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
114
+
115
+ processMarket(
116
+ freshMockSoccer,
117
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
118
+ ['draftkings'],
119
+ [],
120
+ true,
121
+ undefined,
122
+ undefined,
123
+ LeagueMocks.leagueInfoOnlyParentDiffSportId
124
+ );
125
+
126
+ expect(warnSpy).toHaveBeenCalled();
127
+ expect(warnSpy).toHaveBeenCalledWith(NO_MARKETS_FOR_LEAGUE_ID);
128
+
129
+ // Restore the original implementation
130
+ warnSpy.mockRestore();
131
+ });
132
+ });
133
+ });
@@ -0,0 +1,92 @@
1
+ import { ZERO_ODDS_MESSAGE } from '../../constants/errors';
2
+ import { processMarket } from '../../utils/markets';
3
+ import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
4
+ import { LeagueMocks } from '../mock/MockLeagueMap';
5
+ import {
6
+ MockOnlyMoneyline,
7
+ MockZeroOdds,
8
+ MockOddsChildMarketsGoodOdds,
9
+ MockOddsChildMarketsOddsCut,
10
+ } from '../mock/MockOpticSoccer';
11
+ import { mockSoccer } from '../mock/MockSoccerRedis';
12
+
13
+ describe('Odds', () => {
14
+ it('Should return odds for moneyline', () => {
15
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
16
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOnlyMoneyline));
17
+ const market = processMarket(
18
+ freshMockSoccer,
19
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
20
+ ['draftkings'],
21
+ [],
22
+ true,
23
+ undefined,
24
+ undefined,
25
+ LeagueMocks.leagueInfoOnlyParent
26
+ );
27
+
28
+ const hasOdds = market.odds.some(
29
+ (odd) => odd.american !== 0 || odd.decimal !== 0 || odd.normalizedImplied !== 0
30
+ );
31
+
32
+ expect(hasOdds).toBe(true);
33
+ });
34
+
35
+ it('Should return zero odds for moneyline', () => {
36
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
37
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockZeroOdds));
38
+ const market = processMarket(
39
+ freshMockSoccer,
40
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
41
+ ['draftkings'],
42
+ [],
43
+ true,
44
+ undefined,
45
+ undefined,
46
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals
47
+ );
48
+
49
+ const hasOdds = market.odds.some(
50
+ (odd) => odd.american !== 0 || odd.decimal !== 0 || odd.normalizedImplied !== 0
51
+ );
52
+
53
+ expect(hasOdds).toBe(false);
54
+ expect(market).toHaveProperty('errorMessage');
55
+ expect(market.errorMessage).toBe(ZERO_ODDS_MESSAGE);
56
+ });
57
+
58
+ it('Should contain child markets for good odds', () => {
59
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
60
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOddsChildMarketsGoodOdds));
61
+ const market = processMarket(
62
+ freshMockSoccer,
63
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
64
+ ['draftkings'],
65
+ [],
66
+ true,
67
+ undefined,
68
+ undefined,
69
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals
70
+ );
71
+
72
+ const hasChildMarkets = market.childMarkets.length > 0;
73
+ expect(hasChildMarkets).toBe(true);
74
+ });
75
+
76
+ it('Should return empty array for child child markets after odds cut', () => {
77
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
78
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOddsChildMarketsOddsCut));
79
+ const market = processMarket(
80
+ freshMockSoccer,
81
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
82
+ ['draftkings'],
83
+ [],
84
+ true,
85
+ undefined,
86
+ undefined,
87
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals
88
+ );
89
+
90
+ expect(market.childMarkets).toHaveLength(0);
91
+ });
92
+ });
@@ -0,0 +1,47 @@
1
+ import {
2
+ getBetTypesForLeague,
3
+ getLeagueSpreadTypes,
4
+ getLeagueTotalTypes,
5
+ getLiveSupportedLeagues,
6
+ } from '../../utils/sports';
7
+ import { LeagueMocks } from '../mock/MockLeagueMap';
8
+
9
+ describe('Sports', () => {
10
+ it('Should return all enabled leagues for LIVE', () => {
11
+ const supportedLeageus = getLiveSupportedLeagues(LeagueMocks.leagueInfoEnabledSpeadAndTotals);
12
+
13
+ expect(supportedLeageus).toContain(9806);
14
+ });
15
+
16
+ it('Should return all enabled bet types for league', () => {
17
+ const betTypes = getBetTypesForLeague(9806, LeagueMocks.leagueInfoEnabledSpeadAndTotals);
18
+
19
+ expect(betTypes).toContain('Moneyline');
20
+ expect(betTypes).toContain('Goal Spread');
21
+ expect(betTypes).toContain('Total Goals');
22
+ });
23
+
24
+ it('Should return all enabled bet types for league, and not contain disabled ones', () => {
25
+ const betTypes = getBetTypesForLeague(9806, LeagueMocks.leagueInfoEnabledSpreadDisabledTotals);
26
+
27
+ expect(betTypes).toContain('Moneyline');
28
+ expect(betTypes).toContain('Goal Spread');
29
+ expect(betTypes).not.toContain('Total Goals');
30
+ });
31
+
32
+ it('Should return all enabled spread bet types for league', () => {
33
+ const betTypes = getLeagueSpreadTypes(9806, LeagueMocks.leagueInfoEnabledSpeadAndTotals);
34
+
35
+ expect(betTypes).not.toContain('moneyline');
36
+ expect(betTypes).toContain('goal spread');
37
+ expect(betTypes).not.toContain('total goals');
38
+ });
39
+
40
+ it('Should return all enabled total bet types for league', () => {
41
+ const betTypes = getLeagueTotalTypes(9806, LeagueMocks.leagueInfoEnabledSpeadAndTotals);
42
+
43
+ expect(betTypes).not.toContain('moneyline');
44
+ expect(betTypes).not.toContain('goal spread');
45
+ expect(betTypes).toContain('total goals');
46
+ });
47
+ });
@@ -0,0 +1,31 @@
1
+ import { ZERO_ODDS_AFTER_SPREAD_ADJUSTMENT } from '../../constants/errors';
2
+ import { processMarket } from '../../utils/markets';
3
+ import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
4
+ import { LeagueMocks } from '../mock/MockLeagueMap';
5
+ import { MockAfterSpreadZeroOdds1 } from '../mock/MockOpticSoccer';
6
+ import { mockSoccer } from '../mock/MockSoccerRedis';
7
+
8
+ describe('Spread configuration', () => {
9
+ it('Should return zero odds for quotes that sum up total probability above 1', () => {
10
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
11
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockAfterSpreadZeroOdds1));
12
+ const market = processMarket(
13
+ freshMockSoccer,
14
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
15
+ ['draftkings'],
16
+ [],
17
+ false,
18
+ undefined,
19
+ undefined,
20
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals
21
+ );
22
+
23
+ const hasOdds = market.odds.some(
24
+ (odd) => odd.american !== 0 || odd.decimal !== 0 || odd.normalizedImplied !== 0
25
+ );
26
+
27
+ expect(hasOdds).toBe(false);
28
+ expect(market).toHaveProperty('errorMessage');
29
+ expect(market.errorMessage).toBe(ZERO_ODDS_AFTER_SPREAD_ADJUSTMENT); // should be no matching bookmakers mesage
30
+ });
31
+ });
@@ -54,7 +54,7 @@ export const checkOddsFromBookmakers = (
54
54
  (!isTwoPositionalSport && line.drawOdds === 1)
55
55
  );
56
56
  }
57
- return false;
57
+ return false; // fix for es-lint
58
58
  });
59
59
 
60
60
  if (hasZeroOrOne) {
@@ -128,7 +128,7 @@ export const checkOddsFromBookmakers = (
128
128
  return true;
129
129
  }
130
130
  }
131
- return false;
131
+ return false; // fix for es-lint
132
132
  });
133
133
 
134
134
  if (hasLargeImpliedPercentageDifference) {
@@ -140,9 +140,6 @@ export const checkOddsFromBookmakers = (
140
140
  };
141
141
  }
142
142
 
143
- let lines: any[] = [];
144
- arrayOfBookmakers.forEach((bookmaker) => lines.push(oddsMap.get(bookmaker)));
145
-
146
143
  return {
147
144
  homeOdds: homeOdd,
148
145
  awayOdds: awayOdd,
@@ -98,7 +98,7 @@ export const allowSoccerGame = (homeTeam, awayTeam, currentClock, currentPeriod,
98
98
  const currentClockNumber = Number(currentClock);
99
99
  if (
100
100
  (!Number.isNaN(currentClockNumber) && currentClockNumber >= soccerMinuteLimitForLiveTrading) ||
101
- (Number.isNaN(currentClockNumber) && currentPeriod != 'HALF')
101
+ (Number.isNaN(currentClockNumber) && currentPeriod.toLowerCase() != 'half')
102
102
  ) {
103
103
  return { allow: false, message: `Blocking game ${homeTeam} - ${awayTeam} due to clock: ${currentClock}min` };
104
104
  }
@@ -54,7 +54,7 @@ export const processMarket = (
54
54
  if (_odd != 0) {
55
55
  return {
56
56
  american: oddslib.from('impliedProbability', _odd).to('moneyline'),
57
- decimal: oddslib.from('impliedProbability', _odd).to('decimal'),
57
+ decimal: Number(oddslib.from('impliedProbability', _odd).to('decimal').toFixed(10)),
58
58
  normalizedImplied: _odd,
59
59
  };
60
60
  } else {
@@ -91,7 +91,7 @@ export const processMarket = (
91
91
 
92
92
  return {
93
93
  american: oddslib.from('impliedProbability', _odd).to('moneyline'),
94
- decimal: oddslib.from('impliedProbability', _odd).to('decimal'),
94
+ decimal: Number(oddslib.from('impliedProbability', _odd).to('decimal').toFixed(10)),
95
95
  normalizedImplied: _odd,
96
96
  };
97
97
  });
package/src/utils/odds.ts CHANGED
@@ -191,7 +191,7 @@ export const createChildMarkets: (
191
191
  defaultSpreadForLiveMarkets,
192
192
  leagueMap
193
193
  ) => {
194
- const [spreadOdds, totalOdds, childMarkets]: any[] = [[], [], []];
194
+ const [spreadOdds, totalOdds, moneylineOdds, childMarkets]: any[] = [[], [], [], []];
195
195
  const leagueInfo = getLeagueInfo(leagueId, leagueMap);
196
196
  const commonData = {
197
197
  homeTeam: apiResponseWithOdds.homeTeam,
@@ -205,19 +205,20 @@ export const createChildMarkets: (
205
205
  liveOddsProviders[0]
206
206
  );
207
207
 
208
- const allValidOdds = allChildOdds.filter((odd) => odd && Math.abs(Number(odd.points) % 1) === 0.5) as any;
209
-
210
- allValidOdds.forEach((odd) => {
208
+ allChildOdds.forEach((odd) => {
211
209
  if (odd.type === 'Total') {
212
- totalOdds.push(odd);
210
+ if (Math.abs(Number(odd.points) % 1) === 0.5) totalOdds.push(odd);
213
211
  } else if (odd.type === 'Spread') {
214
- spreadOdds.push(odd);
212
+ if (Math.abs(Number(odd.points) % 1) === 0.5) spreadOdds.push(odd);
213
+ } else if (odd.type === 'Moneyline') {
214
+ moneylineOdds.push(odd);
215
215
  }
216
216
  });
217
217
 
218
218
  const formattedOdds = [
219
219
  ...groupAndFormatSpreadOdds(spreadOdds, commonData),
220
220
  ...groupAndFormatTotalOdds(totalOdds, commonData),
221
+ ...groupAndFormatMoneylineOdds(moneylineOdds, commonData),
221
222
  ];
222
223
 
223
224
  const oddsWithSpreadAdjusted = adjustSpreadOnChildOdds(
@@ -231,7 +232,7 @@ export const createChildMarkets: (
231
232
  leagueId: Number(data.sportId),
232
233
  typeId: Number(data.typeId),
233
234
  type: data.type.toLowerCase(),
234
- line: Number(data.line),
235
+ line: Number(data.line || 0),
235
236
  odds: data.odds,
236
237
  };
237
238
  const leagueInfoByTypeId = leagueInfo.find((league) => Number(league.typeId) === Number(data.typeId));
@@ -266,7 +267,11 @@ export const createChildMarkets: (
266
267
  */
267
268
  export const filterOddsByMarketNameBookmaker = (oddsArray, leagueInfos: LeagueInfo[], oddsProvider) => {
268
269
  const allChildMarketsTypes = leagueInfos
269
- .filter((leagueInfo) => leagueInfo.marketName.toLowerCase() !== MoneylineTypes.MONEYLINE.toLowerCase())
270
+ .filter(
271
+ (leagueInfo) =>
272
+ leagueInfo.marketName.toLowerCase() !== MoneylineTypes.MONEYLINE.toLowerCase() &&
273
+ leagueInfo.enabled === 'true'
274
+ )
270
275
  .map((leagueInfo) => leagueInfo.marketName.toLowerCase());
271
276
  return oddsArray
272
277
  .filter(
@@ -294,10 +299,10 @@ export const filterOddsByMarketNameBookmaker = (oddsArray, leagueInfos: LeagueIn
294
299
  export const groupAndFormatSpreadOdds = (oddsArray, commonData) => {
295
300
  // Group odds by their selection points and selection
296
301
  const groupedOdds = oddsArray.reduce((acc: any, odd: any) => {
297
- const { points, price, selection, typeId, sportId, type } = odd;
302
+ const { points, marketName, price, selection, typeId, sportId, type } = odd;
298
303
  const isHomeTeam = selection === commonData.homeTeam;
299
304
 
300
- const key = isHomeTeam ? points : -points;
305
+ const key = `${marketName}_${isHomeTeam ? points : -points}`;
301
306
 
302
307
  if (!acc[key]) {
303
308
  acc[key] = { home: null, away: null, typeId: null, sportId: null };
@@ -317,7 +322,8 @@ export const groupAndFormatSpreadOdds = (oddsArray, commonData) => {
317
322
  }, {}) as any;
318
323
  // Format the grouped odds into the desired output
319
324
  const formattedOdds = (Object.entries(groupedOdds as any) as any).reduce((acc, [key, value]) => {
320
- const line = parseFloat(key);
325
+ const [_marketName, lineFloat] = key.split('_');
326
+ const line = parseFloat(lineFloat);
321
327
  if ((value as any).home !== null && (value as any).away !== null) {
322
328
  acc.push({
323
329
  line: line as any,
@@ -343,7 +349,7 @@ export const groupAndFormatTotalOdds = (oddsArray, commonData) => {
343
349
  // Group odds by their selection points and selection
344
350
  const groupedOdds = oddsArray.reduce((acc, odd) => {
345
351
  if (odd) {
346
- const key = `${odd.selection}_${odd.points}`;
352
+ const key = `${odd.marketName}_${odd.selection}_${odd.points}`;
347
353
  if (!acc[key]) {
348
354
  acc[key] = { over: null, under: null };
349
355
  }
@@ -363,7 +369,7 @@ export const groupAndFormatTotalOdds = (oddsArray, commonData) => {
363
369
 
364
370
  // Format the grouped odds into the desired output
365
371
  const formattedOdds = (Object.entries(groupedOdds as any) as any).reduce((acc, [key, value]) => {
366
- const [selection, selectionLine] = key.split('_');
372
+ const [_marketName, selection, selectionLine] = key.split('_');
367
373
  const line = parseFloat(selectionLine);
368
374
 
369
375
  // if we have away team in total odds we know the market is team total and we need to increase typeId by one.
@@ -384,12 +390,61 @@ export const groupAndFormatTotalOdds = (oddsArray, commonData) => {
384
390
  return formattedOdds;
385
391
  };
386
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
+
387
438
  export const adjustSpreadOnChildOdds = (iterableGroupedOdds, spreadDataForSport, defaultSpreadForLiveMarkets) => {
388
439
  const result: any[] = [];
389
440
  iterableGroupedOdds.forEach((data) => {
390
- let homeTeamOdds = convertOddsToImpl(data.odds[0]) || ZERO;
391
- let awayTeamOdds = convertOddsToImpl(data.odds[1]) || ZERO;
392
- let isZeroOddsChild = homeTeamOdds === ZERO || awayTeamOdds === ZERO;
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);
393
448
  if (!isZeroOddsChild) {
394
449
  const spreadData = getSpreadData(
395
450
  spreadDataForSport,
@@ -397,17 +452,14 @@ export const adjustSpreadOnChildOdds = (iterableGroupedOdds, spreadDataForSport,
397
452
  data.typeId,
398
453
  defaultSpreadForLiveMarkets
399
454
  );
455
+
400
456
  let adjustedOdds;
401
457
  if (spreadData !== null) {
402
- adjustedOdds = adjustSpreadOnOdds(
403
- [homeTeamOdds, awayTeamOdds],
404
- spreadData.minSpread,
405
- spreadData.targetSpread
406
- );
458
+ adjustedOdds = adjustSpreadOnOdds(odds, spreadData.minSpread, spreadData.targetSpread);
407
459
  } else {
408
- adjustedOdds = adjustSpreadOnOdds([homeTeamOdds, awayTeamOdds], defaultSpreadForLiveMarkets, 0);
460
+ adjustedOdds = adjustSpreadOnOdds(odds, defaultSpreadForLiveMarkets, 0);
409
461
  }
410
- [homeTeamOdds, awayTeamOdds] = adjustedOdds;
462
+
411
463
  result.push({
412
464
  ...data,
413
465
  odds: adjustedOdds,