overtime-live-trading-utils 2.1.44 → 2.1.45-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.
@@ -1,9 +1,18 @@
1
+ import { MAX_IMPLIED_PERCENTAGE_DIFF } from '../../constants/common';
1
2
  import { DIFF_BETWEEN_BOOKMAKERS_MESSAGE, ZERO_ODDS_MESSAGE } from '../../constants/errors';
2
3
  import { processMarket } from '../../utils/markets';
3
4
  import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
4
5
  import { LeagueMocks } from '../mock/MockLeagueMap';
5
- import { MockOnlyMoneyline, MockOnlyMoneylineWithDifferentSportsbook } from '../mock/MockOpticSoccer';
6
+ import {
7
+ MockOddsChildMarketsDifferentBookmakers,
8
+ MockOddsChildMarketsDifferentBookmakersPercentageDiff,
9
+ MockOnlyMoneyline,
10
+ MockOnlyMoneylineWithDifferentSportsbook,
11
+ } from '../mock/MockOpticSoccer';
6
12
  import { mockSoccer } from '../mock/MockSoccerRedis';
13
+ import { getLastPolledMapForBookmakers, MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST } from '../utils/helper';
14
+
15
+ const lastPolledMap = getLastPolledMapForBookmakers();
7
16
 
8
17
  describe('Bookmakers', () => {
9
18
  it('Should return zero odds for moneyline when one of the bookmakers has no odds', () => {
@@ -16,8 +25,10 @@ describe('Bookmakers', () => {
16
25
  [],
17
26
  true,
18
27
  undefined,
19
- undefined,
20
- LeagueMocks.leagueInfoEnabledSpeadAndTotals
28
+ MAX_IMPLIED_PERCENTAGE_DIFF,
29
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals,
30
+ lastPolledMap,
31
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
21
32
  );
22
33
 
23
34
  const hasOdds = market.odds.some(
@@ -27,7 +38,6 @@ describe('Bookmakers', () => {
27
38
  expect(hasOdds).toBe(false);
28
39
  expect(market).toHaveProperty('errorMessage');
29
40
  expect(market.errorMessage).toBe(ZERO_ODDS_MESSAGE);
30
- expect(market.childMarkets.length).toBe(0);
31
41
  });
32
42
 
33
43
  it('Should return zero odds for moneyline when there is quote diff between bookmakers', () => {
@@ -41,7 +51,9 @@ describe('Bookmakers', () => {
41
51
  true,
42
52
  undefined,
43
53
  5,
44
- LeagueMocks.leagueInfoOnlyParent
54
+ LeagueMocks.leagueInfoOnlyParent,
55
+ lastPolledMap,
56
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
45
57
  );
46
58
 
47
59
  const hasOdds = market.odds.some(
@@ -51,7 +63,6 @@ describe('Bookmakers', () => {
51
63
  expect(hasOdds).toBe(false);
52
64
  expect(market).toHaveProperty('errorMessage');
53
65
  expect(market.errorMessage).toBe(DIFF_BETWEEN_BOOKMAKERS_MESSAGE);
54
- expect(market.childMarkets.length).toBe(0);
55
66
  });
56
67
 
57
68
  it('Should return zero odds for moneyline as no matching bookmaker was provided', () => {
@@ -64,8 +75,10 @@ describe('Bookmakers', () => {
64
75
  [],
65
76
  true,
66
77
  undefined,
67
- undefined,
68
- LeagueMocks.leagueInfoEnabledSpeadAndTotals
78
+ MAX_IMPLIED_PERCENTAGE_DIFF,
79
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals,
80
+ lastPolledMap,
81
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
69
82
  );
70
83
 
71
84
  const hasOdds = market.odds.some(
@@ -76,4 +89,61 @@ describe('Bookmakers', () => {
76
89
  expect(market).toHaveProperty('errorMessage');
77
90
  expect(market.errorMessage).toBe(ZERO_ODDS_MESSAGE); // should be no matching bookmakers mesage
78
91
  });
92
+
93
+ it('Should return odds that have both bookmakers', () => {
94
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
95
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOddsChildMarketsDifferentBookmakers));
96
+ const market = processMarket(
97
+ freshMockSoccer,
98
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
99
+ ['bovada', 'draftkings'],
100
+ [],
101
+ true,
102
+ undefined,
103
+ MAX_IMPLIED_PERCENTAGE_DIFF,
104
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals,
105
+ lastPolledMap,
106
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
107
+ );
108
+
109
+ expect(market.childMarkets.length).toBe(2);
110
+ });
111
+
112
+ it('Should return all odds from draftkings', () => {
113
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
114
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOddsChildMarketsDifferentBookmakers));
115
+ const market = processMarket(
116
+ freshMockSoccer,
117
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
118
+ ['bovada', 'draftkings'],
119
+ [],
120
+ true,
121
+ undefined,
122
+ MAX_IMPLIED_PERCENTAGE_DIFF,
123
+ LeagueMocks.leaguInfoDifferentPrimaryBookmaker,
124
+ lastPolledMap,
125
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
126
+ );
127
+
128
+ expect(market.childMarkets.length).toBe(3);
129
+ });
130
+
131
+ it('Should cut odds that are different between bookmakers', () => {
132
+ const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
133
+ const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOddsChildMarketsDifferentBookmakersPercentageDiff));
134
+ const market = processMarket(
135
+ freshMockSoccer,
136
+ mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
137
+ ['bovada', 'draftkings'],
138
+ [],
139
+ true,
140
+ undefined,
141
+ MAX_IMPLIED_PERCENTAGE_DIFF,
142
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals,
143
+ lastPolledMap,
144
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
145
+ );
146
+
147
+ expect(market.childMarkets.length).toBe(1);
148
+ });
79
149
  });
@@ -1,9 +1,13 @@
1
+ import { MAX_IMPLIED_PERCENTAGE_DIFF } from '../../constants/common';
1
2
  import { NO_MARKETS_FOR_LEAGUE_ID } from '../../constants/errors';
2
3
  import { processMarket } from '../../utils/markets';
3
4
  import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
4
5
  import { LeagueMocks } from '../mock/MockLeagueMap';
5
6
  import { MockOnlyMoneyline, MockOpticSoccer } from '../mock/MockOpticSoccer';
6
7
  import { mockSoccer } from '../mock/MockSoccerRedis';
8
+ import { getLastPolledMapForBookmakers, MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST } from '../utils/helper';
9
+
10
+ const lastPolledMap = getLastPolledMapForBookmakers();
7
11
 
8
12
  describe('Markets', () => {
9
13
  describe('LeagueMap configuration', () => {
@@ -18,8 +22,10 @@ describe('Markets', () => {
18
22
  [],
19
23
  true,
20
24
  undefined,
21
- undefined,
22
- LeagueMocks.leagueInfoOnlyParent
25
+ MAX_IMPLIED_PERCENTAGE_DIFF,
26
+ LeagueMocks.leagueInfoOnlyParent,
27
+ lastPolledMap,
28
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
23
29
  );
24
30
 
25
31
  expect(market.childMarkets).toHaveLength(0);
@@ -35,8 +41,10 @@ describe('Markets', () => {
35
41
  [],
36
42
  true,
37
43
  undefined,
38
- undefined,
39
- LeagueMocks.leagueInfoMockDisabledChilds
44
+ MAX_IMPLIED_PERCENTAGE_DIFF,
45
+ LeagueMocks.leagueInfoMockDisabledChilds,
46
+ lastPolledMap,
47
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
40
48
  );
41
49
 
42
50
  expect(market.childMarkets).toHaveLength(0);
@@ -52,8 +60,10 @@ describe('Markets', () => {
52
60
  [],
53
61
  true,
54
62
  undefined,
55
- undefined,
56
- LeagueMocks.leagueInfoEnabledSpreadDisabledTotals
63
+ MAX_IMPLIED_PERCENTAGE_DIFF,
64
+ LeagueMocks.leagueInfoEnabledSpreadDisabledTotals,
65
+ lastPolledMap,
66
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
57
67
  );
58
68
 
59
69
  const containsSpread = market.childMarkets.some((child: any) => child.type === 'spread');
@@ -73,8 +83,10 @@ describe('Markets', () => {
73
83
  [],
74
84
  true,
75
85
  undefined,
76
- undefined,
77
- LeagueMocks.leagueInfoEnabledSpeadAndTotals
86
+ MAX_IMPLIED_PERCENTAGE_DIFF,
87
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals,
88
+ lastPolledMap,
89
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
78
90
  );
79
91
 
80
92
  const containsSpread = market.childMarkets.some((child: any) => child.type === 'spread');
@@ -94,8 +106,10 @@ describe('Markets', () => {
94
106
  [],
95
107
  true,
96
108
  undefined,
97
- undefined,
98
- LeagueMocks.leagueInfoEnabledAll
109
+ MAX_IMPLIED_PERCENTAGE_DIFF,
110
+ LeagueMocks.leagueInfoEnabledAll,
111
+ lastPolledMap,
112
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
99
113
  );
100
114
 
101
115
  const containsSpread = market.childMarkets.some((child: any) => child.type === 'spread');
@@ -117,6 +131,9 @@ describe('Markets', () => {
117
131
  (child: any) => child.type === 'willThereBeOvertime'
118
132
  );
119
133
 
134
+ const containsTeamTotalHome = market.childMarkets.some((child: any) => child.type === 'totalHomeTeam');
135
+ const containsTeamTotalAway = market.childMarkets.some((child: any) => child.type === 'totalAwayTeam');
136
+
120
137
  expect(containsChildGG).toBe(true);
121
138
  expect(containsChildGG1stHalf).toBe(true);
122
139
  expect(containsChildGG2ndHalf).toBe(true);
@@ -128,6 +145,8 @@ describe('Markets', () => {
128
145
  expect(containsChildGG).toBe(true);
129
146
  expect(containsChildDrawNoBet).toBe(true);
130
147
  expect(containsWillThereBeOvertime).toBe(true);
148
+ expect(containsTeamTotalHome).toBe(true);
149
+ expect(containsTeamTotalAway).toBe(true);
131
150
  });
132
151
 
133
152
  it('Should return warning message that there are is no configuration available in league map csv', () => {
@@ -142,8 +161,10 @@ describe('Markets', () => {
142
161
  [],
143
162
  true,
144
163
  undefined,
145
- undefined,
146
- LeagueMocks.leagueInfoOnlyParentDiffSportId
164
+ MAX_IMPLIED_PERCENTAGE_DIFF,
165
+ LeagueMocks.leagueInfoOnlyParentDiffSportId,
166
+ lastPolledMap,
167
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
147
168
  );
148
169
 
149
170
  expect(warnSpy).toHaveBeenCalled();
@@ -1,3 +1,4 @@
1
+ import { MAX_IMPLIED_PERCENTAGE_DIFF } from '../../constants/common';
1
2
  import { ZERO_ODDS_MESSAGE_SINGLE_BOOKMAKER } from '../../constants/errors';
2
3
  import { processMarket } from '../../utils/markets';
3
4
  import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
@@ -9,6 +10,9 @@ import {
9
10
  MockZeroOdds,
10
11
  } from '../mock/MockOpticSoccer';
11
12
  import { mockSoccer } from '../mock/MockSoccerRedis';
13
+ import { getLastPolledMapForBookmakers, MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST } from '../utils/helper';
14
+
15
+ const lastPolledMap = getLastPolledMapForBookmakers();
12
16
 
13
17
  describe('Odds', () => {
14
18
  it('Should return odds for moneyline', () => {
@@ -21,8 +25,10 @@ describe('Odds', () => {
21
25
  [],
22
26
  true,
23
27
  undefined,
24
- undefined,
25
- LeagueMocks.leagueInfoOnlyParent
28
+ MAX_IMPLIED_PERCENTAGE_DIFF,
29
+ LeagueMocks.leagueInfoOnlyParent,
30
+ lastPolledMap,
31
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
26
32
  );
27
33
 
28
34
  const hasOdds = market.odds.some(
@@ -42,8 +48,10 @@ describe('Odds', () => {
42
48
  [],
43
49
  true,
44
50
  undefined,
45
- undefined,
46
- LeagueMocks.leagueInfoEnabledSpeadAndTotals
51
+ MAX_IMPLIED_PERCENTAGE_DIFF,
52
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals,
53
+ lastPolledMap,
54
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
47
55
  );
48
56
 
49
57
  const hasOdds = market.odds.some(
@@ -65,8 +73,10 @@ describe('Odds', () => {
65
73
  [],
66
74
  true,
67
75
  undefined,
68
- undefined,
69
- LeagueMocks.leagueInfoEnabledSpeadAndTotals
76
+ MAX_IMPLIED_PERCENTAGE_DIFF,
77
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals,
78
+ lastPolledMap,
79
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
70
80
  );
71
81
 
72
82
  const hasChildMarkets = market.childMarkets.length > 0;
@@ -83,8 +93,10 @@ describe('Odds', () => {
83
93
  [],
84
94
  true,
85
95
  undefined,
86
- undefined,
87
- LeagueMocks.leagueInfoEnabledSpeadAndTotals
96
+ MAX_IMPLIED_PERCENTAGE_DIFF,
97
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals,
98
+ lastPolledMap,
99
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
88
100
  );
89
101
 
90
102
  expect(market.childMarkets).toHaveLength(0);
@@ -1,9 +1,13 @@
1
+ import { MAX_IMPLIED_PERCENTAGE_DIFF } from '../../constants/common';
1
2
  import { ZERO_ODDS_AFTER_SPREAD_ADJUSTMENT } from '../../constants/errors';
2
3
  import { processMarket } from '../../utils/markets';
3
4
  import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
4
5
  import { LeagueMocks } from '../mock/MockLeagueMap';
5
6
  import { MockAfterSpreadZeroOdds1, MockOnlyMoneylineFavorite, MockOpticSoccer } from '../mock/MockOpticSoccer';
6
7
  import { mockSoccer } from '../mock/MockSoccerRedis';
8
+ import { getLastPolledMapForBookmakers, MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST } from '../utils/helper';
9
+
10
+ const lastPolledMap = getLastPolledMapForBookmakers();
7
11
 
8
12
  describe('Spread configuration', () => {
9
13
  it('Should return zero odds for quotes that sum up total probability above 1', () => {
@@ -16,8 +20,10 @@ describe('Spread configuration', () => {
16
20
  [],
17
21
  false,
18
22
  undefined,
19
- undefined,
20
- LeagueMocks.leagueInfoEnabledSpeadAndTotals
23
+ MAX_IMPLIED_PERCENTAGE_DIFF,
24
+ LeagueMocks.leagueInfoEnabledSpeadAndTotals,
25
+ lastPolledMap,
26
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
21
27
  );
22
28
 
23
29
  const hasOdds = market.odds.some(
@@ -41,8 +47,10 @@ describe('Spread configuration', () => {
41
47
  [],
42
48
  true,
43
49
  undefined,
44
- undefined,
45
- LeagueMocks.leagueInfoOnlyParent
50
+ MAX_IMPLIED_PERCENTAGE_DIFF,
51
+ LeagueMocks.leagueInfoOnlyParent,
52
+ lastPolledMap,
53
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
46
54
  )
47
55
  )
48
56
  );
@@ -56,8 +64,10 @@ describe('Spread configuration', () => {
56
64
  [],
57
65
  true,
58
66
  undefined,
59
- undefined,
60
- LeagueMocks.leagueInfoOnlyParentWithSpreadAdded
67
+ MAX_IMPLIED_PERCENTAGE_DIFF,
68
+ LeagueMocks.leagueInfoOnlyParentWithSpreadAdded,
69
+ lastPolledMap,
70
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
61
71
  )
62
72
  )
63
73
  );
@@ -91,8 +101,10 @@ describe('Spread configuration', () => {
91
101
  [],
92
102
  true,
93
103
  undefined,
94
- undefined,
95
- LeagueMocks.leagueInfoOnlyParent
104
+ MAX_IMPLIED_PERCENTAGE_DIFF,
105
+ LeagueMocks.leagueInfoOnlyParent,
106
+ lastPolledMap,
107
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
96
108
  )
97
109
  )
98
110
  );
@@ -106,8 +118,10 @@ describe('Spread configuration', () => {
106
118
  [],
107
119
  true,
108
120
  undefined,
109
- undefined,
110
- LeagueMocks.leagueInfoOnlyParentWithSpreadAdded
121
+ MAX_IMPLIED_PERCENTAGE_DIFF,
122
+ LeagueMocks.leagueInfoOnlyParentWithSpreadAdded,
123
+ lastPolledMap,
124
+ MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
111
125
  )
112
126
  )
113
127
  );
@@ -0,0 +1,10 @@
1
+ import { LastPolledArray } from '../../types/sports';
2
+
3
+ export const getLastPolledMapForBookmakers = () => {
4
+ const lastPolledMap: LastPolledArray = [];
5
+ lastPolledMap.push({ sportsbook: 'draftkings', timestamp: Date.now() });
6
+ lastPolledMap.push({ sportsbook: 'bovada', timestamp: Date.now() });
7
+ return lastPolledMap;
8
+ };
9
+
10
+ export const MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST = 30000; // 30 seconds
@@ -0,0 +1,7 @@
1
+ export type BookmakersConfig = {
2
+ sportName: string;
3
+ sportId: number;
4
+ primaryBookmaker: string;
5
+ secondaryBookmaker: string;
6
+ tertiaryBookmaker: string;
7
+ };
package/src/types/odds.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { LeagueConfigInfo } from './sports';
2
+
1
3
  export type Fixture = {
2
4
  gameId: string;
3
5
  startDate: number;
@@ -34,6 +36,23 @@ export type OddsObject = {
34
36
  odds: Odds;
35
37
  };
36
38
 
39
+ export type OddsWithLeagueInfo = [
40
+ {
41
+ id: string;
42
+ sportsBookName: string;
43
+ name: string;
44
+ price: number;
45
+ timestamp: number;
46
+ points: number;
47
+ isMain: boolean;
48
+ isLive: boolean;
49
+ marketName: string;
50
+ playerId: string;
51
+ selection: string;
52
+ selectionLine: string;
53
+ } & LeagueConfigInfo
54
+ ];
55
+
37
56
  export type ScoresObject = {
38
57
  gameId: string;
39
58
  sport: string;
@@ -1,4 +1,3 @@
1
-
2
1
  export type LeagueConfigInfo = {
3
2
  sportId: number;
4
3
  typeId: number;
@@ -8,6 +7,8 @@ export type LeagueConfigInfo = {
8
7
  minOdds: number;
9
8
  maxOdds: number;
10
9
  addedSpread?: number;
10
+ primaryBookmaker?: string;
11
+ secondaryBookmaker?: string;
11
12
  };
12
13
 
13
14
  export type ChildMarket = {
@@ -17,3 +18,5 @@ export type ChildMarket = {
17
18
  line: number;
18
19
  odds: Array<number>;
19
20
  };
21
+
22
+ export type LastPolledArray = { sportsbook: string; timestamp: number }[];
@@ -1,12 +1,20 @@
1
1
  import * as oddslib from 'oddslib';
2
+ import { MIN_ODDS_FOR_DIFF_CHECKING } from '../constants/common';
2
3
  import {
3
4
  DIFF_BETWEEN_BOOKMAKERS_MESSAGE,
4
5
  NO_MATCHING_BOOKMAKERS_MESSAGE,
5
6
  ZERO_ODDS_MESSAGE,
6
7
  ZERO_ODDS_MESSAGE_SINGLE_BOOKMAKER,
7
8
  } from '../constants/errors';
9
+ import { BookmakersConfig } from '../types/bookmakers';
10
+ import { OddsWithLeagueInfo } from '../types/odds';
11
+ import { LastPolledArray, LeagueConfigInfo } from '../types/sports';
8
12
 
9
- export const getBookmakersArray = (bookmakersData: any[], sportId: any, backupLiveOddsProviders: string[]) => {
13
+ export const getBookmakersArray = (
14
+ bookmakersData: BookmakersConfig[],
15
+ sportId: any,
16
+ backupLiveOddsProviders: string[]
17
+ ) => {
10
18
  const sportBookmakersData = bookmakersData.find((data) => Number(data.sportId) === Number(sportId));
11
19
  if (sportBookmakersData) {
12
20
  if (sportBookmakersData.primaryBookmaker == '') {
@@ -23,6 +31,45 @@ export const getBookmakersArray = (bookmakersData: any[], sportId: any, backupLi
23
31
  return backupLiveOddsProviders;
24
32
  };
25
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
+
26
73
  export const checkOddsFromBookmakers = (
27
74
  oddsMap: Map<string, any>,
28
75
  arrayOfBookmakers: string[],
@@ -153,6 +200,122 @@ export const checkOddsFromBookmakers = (
153
200
  };
154
201
  };
155
202
 
203
+ export const checkOddsFromBookmakersForChildMarkets = (
204
+ odds: any,
205
+ leagueInfos: LeagueConfigInfo[],
206
+ oddsProviders: string[],
207
+ lastPolledMap: LastPolledArray,
208
+ maxAllowedProviderDataStaleDelay: number,
209
+ maxImpliedPercentageDifference: number
210
+ ): OddsWithLeagueInfo => {
211
+ const formattedOdds = Object.entries(odds as any).reduce((acc: any, [key, value]: [string, any]) => {
212
+ const [sportsBookName, marketName, points, selection, selectionLine] = key.split('_');
213
+ const info = leagueInfos.find((leagueInfo) => leagueInfo.marketName.toLowerCase() === marketName.toLowerCase());
214
+ if (info) {
215
+ const { primaryBookmaker, secondaryBookmaker } = getPrimaryAndSecondaryBookmakerForTypeId(
216
+ oddsProviders,
217
+ leagueInfos,
218
+ Number(info.typeId)
219
+ );
220
+
221
+ const isValidLastPolled = isLastPolledForBookmakersValid(
222
+ lastPolledMap,
223
+ maxAllowedProviderDataStaleDelay,
224
+ primaryBookmaker,
225
+ secondaryBookmaker
226
+ );
227
+
228
+ if (isValidLastPolled) {
229
+ if (primaryBookmaker && !secondaryBookmaker) {
230
+ if (sportsBookName.toLowerCase() === primaryBookmaker.toLowerCase()) {
231
+ acc.push(value);
232
+ }
233
+ } else {
234
+ if (sportsBookName.toLowerCase() === primaryBookmaker) {
235
+ const secondaryBookmakerObject =
236
+ odds[
237
+ `${secondaryBookmaker}_${marketName.toLowerCase()}_${points}_${selection}_${selectionLine}`
238
+ ];
239
+ if (secondaryBookmakerObject) {
240
+ const primaryOdds = oddslib.from('decimal', value.price).to('impliedProbability');
241
+ const secondaryOdds = oddslib
242
+ .from('decimal', secondaryBookmakerObject.price)
243
+ .to('impliedProbability');
244
+ if (
245
+ primaryOdds >= MIN_ODDS_FOR_DIFF_CHECKING &&
246
+ secondaryOdds >= MIN_ODDS_FOR_DIFF_CHECKING
247
+ ) {
248
+ const homeOddsDifference = calculateImpliedOddsDifference(primaryOdds, secondaryOdds);
249
+ if (Number(homeOddsDifference) <= Number(maxImpliedPercentageDifference)) {
250
+ acc.push(value);
251
+ }
252
+ } else {
253
+ acc.push(value);
254
+ }
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ return acc;
262
+ }, []);
263
+ return formattedOdds;
264
+ };
265
+
266
+ export const getPrimaryAndSecondaryBookmakerForTypeId = (
267
+ defaultProviders: string[],
268
+ leagueInfos: LeagueConfigInfo[], // LeagueConfigInfo for specific sport, not the entire list from csv
269
+ typeId: number
270
+ ): { primaryBookmaker: string; secondaryBookmaker: string | undefined } => {
271
+ const info = leagueInfos.find((leagueInfo) => Number(leagueInfo.typeId) === typeId);
272
+ let primaryBookmaker = defaultProviders[0].toLowerCase();
273
+ let secondaryBookmaker = defaultProviders[1] ? defaultProviders[1].toLowerCase() : undefined;
274
+ if (info) {
275
+ if (info.primaryBookmaker) {
276
+ primaryBookmaker = info.primaryBookmaker.toLowerCase();
277
+ secondaryBookmaker = info.secondaryBookmaker ? info.secondaryBookmaker.toLowerCase() : undefined;
278
+ }
279
+ }
280
+ return { primaryBookmaker, secondaryBookmaker };
281
+ };
282
+
283
+ export const isLastPolledForBookmakersValid = (
284
+ lastPolledMap: LastPolledArray,
285
+ maxAllowedProviderDataStaleDelay: number,
286
+ primaryBookmaker: string,
287
+ secondaryBookmaker?: string
288
+ ): boolean => {
289
+ const lastPolledTimePrimary = lastPolledMap.find(
290
+ (entry) => entry.sportsbook.toLowerCase() === primaryBookmaker.toLowerCase()
291
+ )?.timestamp;
292
+ if (typeof lastPolledTimePrimary !== 'number') {
293
+ return false;
294
+ }
295
+
296
+ const now = new Date();
297
+ if (secondaryBookmaker) {
298
+ const lastPolledTimeSecondary = lastPolledMap.find(
299
+ (entry) => entry.sportsbook.toLowerCase() === secondaryBookmaker.toLowerCase()
300
+ )?.timestamp;
301
+
302
+ if (typeof lastPolledTimeSecondary !== 'number') {
303
+ return false;
304
+ }
305
+
306
+ const oddsDate = new Date(lastPolledTimeSecondary * 1000);
307
+ const timeDiff = now.getTime() - oddsDate.getTime();
308
+ if (timeDiff > maxAllowedProviderDataStaleDelay) {
309
+ return false;
310
+ }
311
+ }
312
+
313
+ const oddsDate = new Date(lastPolledTimePrimary * 1000);
314
+ const timeDiff = now.getTime() - oddsDate.getTime();
315
+
316
+ return timeDiff <= maxAllowedProviderDataStaleDelay;
317
+ };
318
+
156
319
  export const calculateImpliedOddsDifference = (impliedOddsA: number, impliedOddsB: number): number => {
157
320
  const percentageDifference = (Math.abs(impliedOddsA - impliedOddsB) / impliedOddsA) * 100;
158
321
  return percentageDifference;