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.
- package/main.js +1 -1
- package/package.json +1 -1
- package/src/constants/common.ts +1 -0
- package/src/constants/errors.ts +1 -0
- package/src/tests/mock/MockLeagueMap.ts +30 -0
- package/src/tests/mock/MockOpticSoccer.ts +486 -0
- package/src/tests/unit/bookmakers.test.ts +78 -8
- package/src/tests/unit/markets.test.ts +33 -12
- package/src/tests/unit/odds.test.ts +20 -8
- package/src/tests/unit/spread.test.ts +24 -10
- package/src/tests/utils/helper.ts +10 -0
- package/src/types/bookmakers.ts +7 -0
- package/src/types/odds.ts +19 -0
- package/src/types/sports.ts +4 -1
- package/src/utils/bookmakers.ts +164 -1
- package/src/utils/markets.ts +42 -41
- package/src/utils/odds.ts +73 -38
- package/tsconfig.json +1 -1
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
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;
|
package/src/types/sports.ts
CHANGED
|
@@ -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 }[];
|
package/src/utils/bookmakers.ts
CHANGED
|
@@ -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 = (
|
|
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;
|