overtime-live-trading-utils 3.0.2-rc.2 → 3.0.3
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 +2 -2
- package/src/tests/unit/bookmakers.test.ts +14 -25
- package/src/tests/unit/markets.test.ts +14 -51
- package/src/tests/unit/odds.test.ts +10 -19
- package/src/tests/unit/spread.test.ts +12 -22
- package/src/tests/utils/helper.ts +0 -8
- package/src/types/odds.ts +16 -19
- package/src/utils/bookmakers.ts +64 -73
- package/src/utils/markets.ts +7 -11
- package/src/utils/odds.ts +20 -43
- package/src/constants/odds.ts +0 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overtime-live-trading-utils",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"scripts": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@types/node": "^20.8.10",
|
|
16
16
|
"oddslib": "^2.1.1",
|
|
17
|
-
"overtime-utils": "^0.1.
|
|
17
|
+
"overtime-utils": "^0.1.59",
|
|
18
18
|
"react": "^17.0.1"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { MAX_IMPLIED_PERCENTAGE_DIFF } from '../../constants/common';
|
|
1
2
|
import { DIFF_BETWEEN_BOOKMAKERS_MESSAGE, ZERO_ODDS_MESSAGE } from '../../constants/errors';
|
|
2
|
-
import { ODDS_THRESHOLD_ANCHORS } from '../../constants/odds';
|
|
3
3
|
import { processMarket } from '../../utils/markets';
|
|
4
4
|
import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
|
|
5
5
|
import { LeagueMocks } from '../mock/MockLeagueMap';
|
|
@@ -10,14 +10,9 @@ import {
|
|
|
10
10
|
MockOnlyMoneylineWithDifferentSportsbook,
|
|
11
11
|
} from '../mock/MockOpticSoccer';
|
|
12
12
|
import { mockSoccer } from '../mock/MockSoccerRedis';
|
|
13
|
-
import {
|
|
14
|
-
getLastPolledDataForBookmakers,
|
|
15
|
-
getPlayersMap,
|
|
16
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
17
|
-
} from '../utils/helper';
|
|
13
|
+
import { getLastPolledDataForBookmakers, MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST } from '../utils/helper';
|
|
18
14
|
|
|
19
15
|
const lastPolledData = getLastPolledDataForBookmakers();
|
|
20
|
-
const playersMap = getPlayersMap();
|
|
21
16
|
|
|
22
17
|
describe('Bookmakers', () => {
|
|
23
18
|
it('Should return zero odds for moneyline when one of the bookmakers has no odds', () => {
|
|
@@ -30,11 +25,10 @@ describe('Bookmakers', () => {
|
|
|
30
25
|
[],
|
|
31
26
|
true,
|
|
32
27
|
undefined,
|
|
33
|
-
|
|
28
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
34
29
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
35
30
|
lastPolledData,
|
|
36
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
37
|
-
playersMap
|
|
31
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
38
32
|
);
|
|
39
33
|
|
|
40
34
|
const hasOdds = market.odds.some(
|
|
@@ -56,11 +50,10 @@ describe('Bookmakers', () => {
|
|
|
56
50
|
[],
|
|
57
51
|
true,
|
|
58
52
|
undefined,
|
|
59
|
-
|
|
53
|
+
5,
|
|
60
54
|
LeagueMocks.leagueInfoOnlyParent,
|
|
61
55
|
lastPolledData,
|
|
62
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
63
|
-
playersMap
|
|
56
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
64
57
|
);
|
|
65
58
|
|
|
66
59
|
const hasOdds = market.odds.some(
|
|
@@ -82,11 +75,10 @@ describe('Bookmakers', () => {
|
|
|
82
75
|
[],
|
|
83
76
|
true,
|
|
84
77
|
undefined,
|
|
85
|
-
|
|
78
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
86
79
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
87
80
|
lastPolledData,
|
|
88
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
89
|
-
playersMap
|
|
81
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
90
82
|
);
|
|
91
83
|
|
|
92
84
|
const hasOdds = market.odds.some(
|
|
@@ -108,11 +100,10 @@ describe('Bookmakers', () => {
|
|
|
108
100
|
[],
|
|
109
101
|
true,
|
|
110
102
|
undefined,
|
|
111
|
-
|
|
103
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
112
104
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
113
105
|
lastPolledData,
|
|
114
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
115
|
-
playersMap
|
|
106
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
116
107
|
);
|
|
117
108
|
|
|
118
109
|
expect(market.childMarkets.length).toBe(2);
|
|
@@ -128,11 +119,10 @@ describe('Bookmakers', () => {
|
|
|
128
119
|
[],
|
|
129
120
|
true,
|
|
130
121
|
undefined,
|
|
131
|
-
|
|
122
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
132
123
|
LeagueMocks.leaguInfoDifferentPrimaryBookmaker,
|
|
133
124
|
lastPolledData,
|
|
134
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
135
|
-
playersMap
|
|
125
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
136
126
|
);
|
|
137
127
|
|
|
138
128
|
expect(market.childMarkets.length).toBe(3);
|
|
@@ -148,11 +138,10 @@ describe('Bookmakers', () => {
|
|
|
148
138
|
[],
|
|
149
139
|
true,
|
|
150
140
|
undefined,
|
|
151
|
-
|
|
141
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
152
142
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
153
143
|
lastPolledData,
|
|
154
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
155
|
-
playersMap
|
|
144
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
156
145
|
);
|
|
157
146
|
|
|
158
147
|
expect(market.childMarkets.length).toBe(1);
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
+
import { MAX_IMPLIED_PERCENTAGE_DIFF } from '../../constants/common';
|
|
1
2
|
import { NO_MARKETS_FOR_LEAGUE_ID } from '../../constants/errors';
|
|
2
|
-
import { ODDS_THRESHOLD_ANCHORS } from '../../constants/odds';
|
|
3
3
|
import { processMarket } from '../../utils/markets';
|
|
4
4
|
import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
|
|
5
5
|
import { LeagueMocks } from '../mock/MockLeagueMap';
|
|
6
6
|
import { MockOnlyMoneyline, MockOpticSoccer } from '../mock/MockOpticSoccer';
|
|
7
7
|
import { mockSoccer } from '../mock/MockSoccerRedis';
|
|
8
|
-
import {
|
|
9
|
-
import { MockRedisNba } from '../mock/OpticOddsMock/MockRedisNba';
|
|
10
|
-
import {
|
|
11
|
-
getLastPolledDataForBookmakers,
|
|
12
|
-
getPlayersMap,
|
|
13
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
14
|
-
} from '../utils/helper';
|
|
8
|
+
import { getLastPolledDataForBookmakers, MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST } from '../utils/helper';
|
|
15
9
|
|
|
16
10
|
const lastPolledData = getLastPolledDataForBookmakers();
|
|
17
|
-
const playersMap = getPlayersMap();
|
|
18
11
|
|
|
19
12
|
describe('Markets', () => {
|
|
20
13
|
describe('LeagueMap configuration', () => {
|
|
@@ -29,11 +22,10 @@ describe('Markets', () => {
|
|
|
29
22
|
[],
|
|
30
23
|
true,
|
|
31
24
|
undefined,
|
|
32
|
-
|
|
25
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
33
26
|
LeagueMocks.leagueInfoOnlyParent,
|
|
34
27
|
lastPolledData,
|
|
35
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
36
|
-
playersMap
|
|
28
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
37
29
|
);
|
|
38
30
|
|
|
39
31
|
expect(market.childMarkets).toHaveLength(0);
|
|
@@ -49,11 +41,10 @@ describe('Markets', () => {
|
|
|
49
41
|
[],
|
|
50
42
|
true,
|
|
51
43
|
undefined,
|
|
52
|
-
|
|
44
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
53
45
|
LeagueMocks.leagueInfoMockDisabledChilds,
|
|
54
46
|
lastPolledData,
|
|
55
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
56
|
-
playersMap
|
|
47
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
57
48
|
);
|
|
58
49
|
|
|
59
50
|
expect(market.childMarkets).toHaveLength(0);
|
|
@@ -69,11 +60,10 @@ describe('Markets', () => {
|
|
|
69
60
|
[],
|
|
70
61
|
true,
|
|
71
62
|
undefined,
|
|
72
|
-
|
|
63
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
73
64
|
LeagueMocks.leagueInfoEnabledSpreadDisabledTotals,
|
|
74
65
|
lastPolledData,
|
|
75
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
76
|
-
playersMap
|
|
66
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
77
67
|
);
|
|
78
68
|
|
|
79
69
|
const containsSpread = market.childMarkets.some((child: any) => child.type === 'spread');
|
|
@@ -93,11 +83,10 @@ describe('Markets', () => {
|
|
|
93
83
|
[],
|
|
94
84
|
true,
|
|
95
85
|
undefined,
|
|
96
|
-
|
|
86
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
97
87
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
98
88
|
lastPolledData,
|
|
99
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
100
|
-
playersMap
|
|
89
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
101
90
|
);
|
|
102
91
|
|
|
103
92
|
const containsSpread = market.childMarkets.some((child: any) => child.type === 'spread');
|
|
@@ -117,11 +106,10 @@ describe('Markets', () => {
|
|
|
117
106
|
[],
|
|
118
107
|
true,
|
|
119
108
|
undefined,
|
|
120
|
-
|
|
109
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
121
110
|
LeagueMocks.leagueInfoEnabledAll,
|
|
122
111
|
lastPolledData,
|
|
123
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
124
|
-
playersMap
|
|
112
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
125
113
|
);
|
|
126
114
|
|
|
127
115
|
const containsSpread = market.childMarkets.some((child: any) => child.type === 'spread');
|
|
@@ -173,11 +161,10 @@ describe('Markets', () => {
|
|
|
173
161
|
[],
|
|
174
162
|
true,
|
|
175
163
|
undefined,
|
|
176
|
-
|
|
164
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
177
165
|
LeagueMocks.leagueInfoOnlyParentDiffSportId,
|
|
178
166
|
lastPolledData,
|
|
179
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
180
|
-
playersMap
|
|
167
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
181
168
|
);
|
|
182
169
|
|
|
183
170
|
expect(warnSpy).toHaveBeenCalled();
|
|
@@ -186,29 +173,5 @@ describe('Markets', () => {
|
|
|
186
173
|
// Restore the original implementation
|
|
187
174
|
warnSpy.mockRestore();
|
|
188
175
|
});
|
|
189
|
-
|
|
190
|
-
it('Should return child markets with player props', () => {
|
|
191
|
-
const freshMockSoccer = JSON.parse(JSON.stringify(MockRedisNba));
|
|
192
|
-
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockNbaData));
|
|
193
|
-
const market = processMarket(
|
|
194
|
-
freshMockSoccer,
|
|
195
|
-
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
196
|
-
['bovada', 'draftkings'], // this will be ignored as primaryBookmaker is defined in LeagueMap
|
|
197
|
-
[],
|
|
198
|
-
true,
|
|
199
|
-
undefined,
|
|
200
|
-
ODDS_THRESHOLD_ANCHORS,
|
|
201
|
-
LeagueMocks.PlayerAssist, // league map with player props configured
|
|
202
|
-
lastPolledData,
|
|
203
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
204
|
-
playersMap
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
market.childMarkets.forEach((child: any) => {
|
|
208
|
-
expect(child.playerProps).toBeDefined();
|
|
209
|
-
expect(child.playerProps.playerId).toBeDefined();
|
|
210
|
-
expect(child.playerProps.playerName).toBeDefined();
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
176
|
});
|
|
214
177
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { MAX_IMPLIED_PERCENTAGE_DIFF } from '../../constants/common';
|
|
1
2
|
import { ZERO_ODDS_MESSAGE_SINGLE_BOOKMAKER } from '../../constants/errors';
|
|
2
|
-
import { ODDS_THRESHOLD_ANCHORS } from '../../constants/odds';
|
|
3
3
|
import { processMarket } from '../../utils/markets';
|
|
4
4
|
import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
|
|
5
5
|
import { LeagueMocks } from '../mock/MockLeagueMap';
|
|
@@ -10,14 +10,9 @@ import {
|
|
|
10
10
|
MockZeroOdds,
|
|
11
11
|
} from '../mock/MockOpticSoccer';
|
|
12
12
|
import { mockSoccer } from '../mock/MockSoccerRedis';
|
|
13
|
-
import {
|
|
14
|
-
getLastPolledDataForBookmakers,
|
|
15
|
-
getPlayersMap,
|
|
16
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
17
|
-
} from '../utils/helper';
|
|
13
|
+
import { getLastPolledDataForBookmakers, MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST } from '../utils/helper';
|
|
18
14
|
|
|
19
15
|
const lastPolledData = getLastPolledDataForBookmakers();
|
|
20
|
-
const playersMap = getPlayersMap();
|
|
21
16
|
|
|
22
17
|
describe('Odds', () => {
|
|
23
18
|
it('Should return odds for moneyline', () => {
|
|
@@ -30,11 +25,10 @@ describe('Odds', () => {
|
|
|
30
25
|
[],
|
|
31
26
|
true,
|
|
32
27
|
undefined,
|
|
33
|
-
|
|
28
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
34
29
|
LeagueMocks.leagueInfoOnlyParent,
|
|
35
30
|
lastPolledData,
|
|
36
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
37
|
-
playersMap
|
|
31
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
38
32
|
);
|
|
39
33
|
|
|
40
34
|
const hasOdds = market.odds.some(
|
|
@@ -54,11 +48,10 @@ describe('Odds', () => {
|
|
|
54
48
|
[],
|
|
55
49
|
true,
|
|
56
50
|
undefined,
|
|
57
|
-
|
|
51
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
58
52
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
59
53
|
lastPolledData,
|
|
60
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
61
|
-
playersMap
|
|
54
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
62
55
|
);
|
|
63
56
|
|
|
64
57
|
const hasOdds = market.odds.some(
|
|
@@ -80,11 +73,10 @@ describe('Odds', () => {
|
|
|
80
73
|
[],
|
|
81
74
|
true,
|
|
82
75
|
undefined,
|
|
83
|
-
|
|
76
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
84
77
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
85
78
|
lastPolledData,
|
|
86
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
87
|
-
playersMap
|
|
79
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
88
80
|
);
|
|
89
81
|
|
|
90
82
|
const hasChildMarkets = market.childMarkets.length > 0;
|
|
@@ -101,11 +93,10 @@ describe('Odds', () => {
|
|
|
101
93
|
[],
|
|
102
94
|
true,
|
|
103
95
|
undefined,
|
|
104
|
-
|
|
96
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
105
97
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
106
98
|
lastPolledData,
|
|
107
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
108
|
-
playersMap
|
|
99
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
109
100
|
);
|
|
110
101
|
|
|
111
102
|
expect(market.childMarkets).toHaveLength(0);
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
+
import { MAX_IMPLIED_PERCENTAGE_DIFF } from '../../constants/common';
|
|
1
2
|
import { ZERO_ODDS_AFTER_SPREAD_ADJUSTMENT } from '../../constants/errors';
|
|
2
|
-
import { ODDS_THRESHOLD_ANCHORS } from '../../constants/odds';
|
|
3
3
|
import { processMarket } from '../../utils/markets';
|
|
4
4
|
import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
|
|
5
5
|
import { LeagueMocks } from '../mock/MockLeagueMap';
|
|
6
6
|
import { MockAfterSpreadZeroOdds1, MockOnlyMoneylineFavorite, MockOpticSoccer } from '../mock/MockOpticSoccer';
|
|
7
7
|
import { mockSoccer } from '../mock/MockSoccerRedis';
|
|
8
|
-
import {
|
|
9
|
-
getLastPolledDataForBookmakers,
|
|
10
|
-
getPlayersMap,
|
|
11
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
12
|
-
} from '../utils/helper';
|
|
8
|
+
import { getLastPolledDataForBookmakers, MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST } from '../utils/helper';
|
|
13
9
|
|
|
14
10
|
const lastPolledData = getLastPolledDataForBookmakers();
|
|
15
|
-
const playersMap = getPlayersMap();
|
|
16
11
|
|
|
17
12
|
describe('Spread configuration', () => {
|
|
18
13
|
it('Should return zero odds for quotes that sum up total probability above 1', () => {
|
|
@@ -25,11 +20,10 @@ describe('Spread configuration', () => {
|
|
|
25
20
|
[],
|
|
26
21
|
false,
|
|
27
22
|
undefined,
|
|
28
|
-
|
|
23
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
29
24
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
30
25
|
lastPolledData,
|
|
31
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
32
|
-
playersMap
|
|
26
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
33
27
|
);
|
|
34
28
|
|
|
35
29
|
const hasOdds = market.odds.some(
|
|
@@ -53,11 +47,10 @@ describe('Spread configuration', () => {
|
|
|
53
47
|
[],
|
|
54
48
|
true,
|
|
55
49
|
undefined,
|
|
56
|
-
|
|
50
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
57
51
|
LeagueMocks.leagueInfoOnlyParent,
|
|
58
52
|
lastPolledData,
|
|
59
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
60
|
-
playersMap
|
|
53
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
61
54
|
)
|
|
62
55
|
)
|
|
63
56
|
);
|
|
@@ -71,11 +64,10 @@ describe('Spread configuration', () => {
|
|
|
71
64
|
[],
|
|
72
65
|
true,
|
|
73
66
|
undefined,
|
|
74
|
-
|
|
67
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
75
68
|
LeagueMocks.leagueInfoOnlyParentWithSpreadAdded,
|
|
76
69
|
lastPolledData,
|
|
77
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
78
|
-
playersMap
|
|
70
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
79
71
|
)
|
|
80
72
|
)
|
|
81
73
|
);
|
|
@@ -109,11 +101,10 @@ describe('Spread configuration', () => {
|
|
|
109
101
|
[],
|
|
110
102
|
true,
|
|
111
103
|
undefined,
|
|
112
|
-
|
|
104
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
113
105
|
LeagueMocks.leagueInfoOnlyParent,
|
|
114
106
|
lastPolledData,
|
|
115
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
116
|
-
playersMap
|
|
107
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
117
108
|
)
|
|
118
109
|
)
|
|
119
110
|
);
|
|
@@ -127,11 +118,10 @@ describe('Spread configuration', () => {
|
|
|
127
118
|
[],
|
|
128
119
|
true,
|
|
129
120
|
undefined,
|
|
130
|
-
|
|
121
|
+
MAX_IMPLIED_PERCENTAGE_DIFF,
|
|
131
122
|
LeagueMocks.leagueInfoOnlyParentWithSpreadAdded,
|
|
132
123
|
lastPolledData,
|
|
133
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
134
|
-
playersMap
|
|
124
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
135
125
|
)
|
|
136
126
|
)
|
|
137
127
|
);
|
|
@@ -8,12 +8,4 @@ export const getLastPolledDataForBookmakers = () => {
|
|
|
8
8
|
return lastPolledData;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
export const getPlayersMap = () => {
|
|
12
|
-
const playersMap: Map<string, number> = new Map<string, number>();
|
|
13
|
-
playersMap.set('0C07D14CC5DC', 13234);
|
|
14
|
-
playersMap.set('AD91EA260284', 56789);
|
|
15
|
-
playersMap.set('674851E026BC', 98765);
|
|
16
|
-
return playersMap;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
11
|
export const MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST = 30000; // 30 seconds
|
package/src/types/odds.ts
CHANGED
|
@@ -7,20 +7,22 @@ export type Fixture = {
|
|
|
7
7
|
awayTeam: string;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
export type Odds =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
export type Odds = [
|
|
11
|
+
{
|
|
12
|
+
id: string;
|
|
13
|
+
sportsBookName: string;
|
|
14
|
+
name: string;
|
|
15
|
+
price: number;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
points: number;
|
|
18
|
+
isMain: boolean;
|
|
19
|
+
isLive: boolean;
|
|
20
|
+
marketName: string;
|
|
21
|
+
playerId: string;
|
|
22
|
+
selection: string;
|
|
23
|
+
selectionLine: string;
|
|
24
|
+
}
|
|
25
|
+
];
|
|
24
26
|
|
|
25
27
|
export type OddsObject = {
|
|
26
28
|
gameId: string;
|
|
@@ -76,8 +78,3 @@ export type ScoresObject = {
|
|
|
76
78
|
};
|
|
77
79
|
|
|
78
80
|
export type HomeAwayTeams = { homeTeam: string; awayTeam: string };
|
|
79
|
-
|
|
80
|
-
export type Anchor = {
|
|
81
|
-
our: number;
|
|
82
|
-
otherMin: number;
|
|
83
|
-
};
|
package/src/utils/bookmakers.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as oddslib from 'oddslib';
|
|
2
|
+
import { MIN_ODDS_FOR_DIFF_CHECKING } from '../constants/common';
|
|
1
3
|
import {
|
|
2
4
|
DIFF_BETWEEN_BOOKMAKERS_MESSAGE,
|
|
3
5
|
NO_MATCHING_BOOKMAKERS_MESSAGE,
|
|
@@ -5,7 +7,7 @@ import {
|
|
|
5
7
|
ZERO_ODDS_MESSAGE_SINGLE_BOOKMAKER,
|
|
6
8
|
} from '../constants/errors';
|
|
7
9
|
import { BookmakersConfig } from '../types/bookmakers';
|
|
8
|
-
import {
|
|
10
|
+
import { OddsWithLeagueInfo } from '../types/odds';
|
|
9
11
|
import { LastPolledArray, LeagueConfigInfo } from '../types/sports';
|
|
10
12
|
|
|
11
13
|
export const getBookmakersArray = (
|
|
@@ -30,23 +32,12 @@ export const getBookmakersArray = (
|
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
export const getBookmakersFromLeagueConfig = (sportId: string | number, leagueInfoArray: LeagueConfigInfo[]) => {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const primary = leagueInfo.primaryBookmaker?.toLowerCase();
|
|
38
|
-
const secondary = leagueInfo.secondaryBookmaker?.toLowerCase();
|
|
39
|
-
if (primary) {
|
|
40
|
-
uniqueBookmakers.push(primary);
|
|
41
|
-
}
|
|
42
|
-
if (secondary && secondary !== primary) {
|
|
43
|
-
uniqueBookmakers.push(secondary);
|
|
44
|
-
}
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
35
|
+
const leagueInfoArrayFiltered: string[] = leagueInfoArray
|
|
36
|
+
.filter((leagueInfo) => Number(leagueInfo.sportId) === Number(sportId) && leagueInfo.enabled === 'true')
|
|
37
|
+
.flatMap((item) => [item.primaryBookmaker?.toLowerCase(), item.secondaryBookmaker?.toLowerCase()])
|
|
38
|
+
.filter((item): item is string => !!item && item.length > 0);
|
|
48
39
|
|
|
49
|
-
return
|
|
40
|
+
return [...new Set(leagueInfoArrayFiltered)];
|
|
50
41
|
};
|
|
51
42
|
|
|
52
43
|
export const getBookmakersForLeague = (
|
|
@@ -72,7 +63,8 @@ export const checkOddsFromBookmakers = (
|
|
|
72
63
|
oddsMap: Map<string, any>,
|
|
73
64
|
arrayOfBookmakers: string[],
|
|
74
65
|
isTwoPositionalSport: boolean,
|
|
75
|
-
|
|
66
|
+
maxImpliedPercentageDifference: number,
|
|
67
|
+
minOddsForDiffChecking: number
|
|
76
68
|
) => {
|
|
77
69
|
// Main bookmaker odds
|
|
78
70
|
const firstBookmakerOdds = oddsMap.get(arrayOfBookmakers[0].toLowerCase());
|
|
@@ -134,10 +126,46 @@ export const checkOddsFromBookmakers = (
|
|
|
134
126
|
const otherAwayOdd = line.awayOdds;
|
|
135
127
|
const otherDrawOdd = line.drawOdds;
|
|
136
128
|
|
|
129
|
+
const homeOddsImplied = oddslib.from('decimal', homeOdd).to('impliedProbability');
|
|
130
|
+
|
|
131
|
+
const awayOddsImplied = oddslib.from('decimal', awayOdd).to('impliedProbability');
|
|
132
|
+
|
|
133
|
+
// Calculate implied odds for the "draw" if it's not a two-positions sport
|
|
134
|
+
const drawOddsImplied = isTwoPositionalSport
|
|
135
|
+
? 0
|
|
136
|
+
: oddslib.from('decimal', drawOdd).to('impliedProbability');
|
|
137
|
+
|
|
138
|
+
const otherHomeOddImplied = oddslib.from('decimal', otherHomeOdd).to('impliedProbability');
|
|
139
|
+
|
|
140
|
+
const otherAwayOddImplied = oddslib.from('decimal', otherAwayOdd).to('impliedProbability');
|
|
141
|
+
|
|
142
|
+
// Calculate implied odds for the "draw" if it's not a two-positions sport
|
|
143
|
+
const otherDrawOddImplied = isTwoPositionalSport
|
|
144
|
+
? 0
|
|
145
|
+
: oddslib.from('decimal', otherDrawOdd).to('impliedProbability');
|
|
146
|
+
|
|
147
|
+
// Calculate the percentage difference for implied odds
|
|
148
|
+
const homeOddsDifference = calculateImpliedOddsDifference(homeOddsImplied, otherHomeOddImplied);
|
|
149
|
+
|
|
150
|
+
const awayOddsDifference = calculateImpliedOddsDifference(awayOddsImplied, otherAwayOddImplied);
|
|
151
|
+
|
|
152
|
+
// Check implied odds difference for the "draw" only if it's not a two-positions sport
|
|
153
|
+
const drawOddsDifference = isTwoPositionalSport
|
|
154
|
+
? 0
|
|
155
|
+
: calculateImpliedOddsDifference(drawOddsImplied, otherDrawOddImplied);
|
|
156
|
+
|
|
157
|
+
// Check if the percentage difference exceeds the threshold
|
|
137
158
|
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
159
|
+
(homeOddsDifference > maxImpliedPercentageDifference &&
|
|
160
|
+
homeOddsImplied > minOddsForDiffChecking &&
|
|
161
|
+
otherHomeOddImplied > minOddsForDiffChecking) ||
|
|
162
|
+
(awayOddsDifference > maxImpliedPercentageDifference &&
|
|
163
|
+
awayOddsImplied > minOddsForDiffChecking &&
|
|
164
|
+
otherAwayOddImplied > minOddsForDiffChecking) ||
|
|
165
|
+
(!isTwoPositionalSport &&
|
|
166
|
+
drawOddsDifference > maxImpliedPercentageDifference &&
|
|
167
|
+
drawOddsImplied > minOddsForDiffChecking &&
|
|
168
|
+
otherDrawOddImplied > minOddsForDiffChecking)
|
|
141
169
|
) {
|
|
142
170
|
return true;
|
|
143
171
|
}
|
|
@@ -167,7 +195,7 @@ export const checkOddsFromBookmakersForChildMarkets = (
|
|
|
167
195
|
oddsProviders: string[],
|
|
168
196
|
lastPolledData: LastPolledArray,
|
|
169
197
|
maxAllowedProviderDataStaleDelay: number,
|
|
170
|
-
|
|
198
|
+
maxImpliedPercentageDifference: number
|
|
171
199
|
): OddsWithLeagueInfo => {
|
|
172
200
|
const formattedOdds = Object.entries(odds as any).reduce((acc: any, [key, value]: [string, any]) => {
|
|
173
201
|
const [sportsBookName, marketName, points, selection, selectionLine] = key.split('_');
|
|
@@ -199,12 +227,21 @@ export const checkOddsFromBookmakersForChildMarkets = (
|
|
|
199
227
|
`${secondaryBookmaker}_${marketName.toLowerCase()}_${points}_${selection}_${selectionLine}`
|
|
200
228
|
];
|
|
201
229
|
if (secondaryBookmakerObject) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
230
|
+
const primaryOdds = oddslib.from('decimal', value.price).to('impliedProbability');
|
|
231
|
+
const secondaryOdds = oddslib
|
|
232
|
+
.from('decimal', secondaryBookmakerObject.price)
|
|
233
|
+
.to('impliedProbability');
|
|
234
|
+
if (
|
|
235
|
+
primaryOdds >= MIN_ODDS_FOR_DIFF_CHECKING &&
|
|
236
|
+
secondaryOdds >= MIN_ODDS_FOR_DIFF_CHECKING
|
|
237
|
+
) {
|
|
238
|
+
const homeOddsDifference = calculateImpliedOddsDifference(primaryOdds, secondaryOdds);
|
|
239
|
+
if (Number(homeOddsDifference) <= Number(maxImpliedPercentageDifference)) {
|
|
240
|
+
acc.push(value);
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
acc.push(value);
|
|
205
244
|
}
|
|
206
|
-
|
|
207
|
-
acc.push(value);
|
|
208
245
|
}
|
|
209
246
|
}
|
|
210
247
|
}
|
|
@@ -260,49 +297,3 @@ export const calculateImpliedOddsDifference = (impliedOddsA: number, impliedOdds
|
|
|
260
297
|
const percentageDifference = (Math.abs(impliedOddsA - impliedOddsB) / impliedOddsA) * 100;
|
|
261
298
|
return percentageDifference;
|
|
262
299
|
};
|
|
263
|
-
|
|
264
|
-
const getRequiredOtherOdds = (odds: number, anchors: Anchor[]) => {
|
|
265
|
-
// If below the first anchor, extrapolate using first segment
|
|
266
|
-
if (odds <= anchors[0].our) {
|
|
267
|
-
const a = anchors[0];
|
|
268
|
-
const b = anchors[1];
|
|
269
|
-
const t = (odds - a.our) / (b.our - a.our);
|
|
270
|
-
return a.otherMin + t * (b.otherMin - a.otherMin);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// If above the last anchor, extrapolate using last segment
|
|
274
|
-
const last = anchors[anchors.length - 1];
|
|
275
|
-
const prev = anchors[anchors.length - 2];
|
|
276
|
-
if (odds >= last.our) {
|
|
277
|
-
const t = (odds - prev.our) / (last.our - prev.our);
|
|
278
|
-
return prev.otherMin + t * (last.otherMin - prev.otherMin);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Otherwise, find the segment we fall into and interpolate
|
|
282
|
-
for (let i = 1; i < anchors.length; i++) {
|
|
283
|
-
const a = anchors[i - 1];
|
|
284
|
-
const b = anchors[i];
|
|
285
|
-
|
|
286
|
-
if (odds <= b.our) {
|
|
287
|
-
const t = (odds - a.our) / (b.our - a.our);
|
|
288
|
-
return a.otherMin + t * (b.otherMin - a.otherMin);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Fallback (should never hit)
|
|
293
|
-
return last.otherMin;
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
const shouldBlockOdds = (ourOdds: number, otherOdds: number, anchors: Anchor[]) => {
|
|
297
|
-
// basic sanity check
|
|
298
|
-
if (ourOdds <= 1 || otherOdds <= 1) return true;
|
|
299
|
-
|
|
300
|
-
// If we are equal or shorter than the other book,
|
|
301
|
-
// we are not at risk.
|
|
302
|
-
if (ourOdds <= otherOdds) return false;
|
|
303
|
-
|
|
304
|
-
const requiredOther = getRequiredOtherOdds(ourOdds, anchors);
|
|
305
|
-
|
|
306
|
-
// Block if the other book is below the required threshold
|
|
307
|
-
return otherOdds < requiredOther;
|
|
308
|
-
};
|