overtime-live-trading-utils 3.0.2 → 4.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/main.js +1 -1
- package/package.json +1 -1
- package/src/constants/odds.ts +19 -0
- package/src/tests/unit/bookmakers.test.ts +25 -14
- package/src/tests/unit/markets.test.ts +51 -14
- package/src/tests/unit/odds.test.ts +19 -10
- package/src/tests/unit/spread.test.ts +22 -12
- package/src/tests/utils/helper.ts +8 -0
- package/src/types/odds.ts +19 -16
- package/src/utils/bookmakers.ts +57 -59
- package/src/utils/markets.ts +11 -7
- package/src/utils/odds.ts +43 -20
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// For each ourOdds, this is the minimum otherOdds we require to NOT block
|
|
2
|
+
// USED FOR TESTS ONLY, real anchors are passed from risk repo
|
|
3
|
+
export const ODDS_THRESHOLD_ANCHORS = [
|
|
4
|
+
{ our: 1.05, otherMin: 1.03 },
|
|
5
|
+
{ our: 1.1, otherMin: 1.06 },
|
|
6
|
+
{ our: 1.2, otherMin: 1.15 },
|
|
7
|
+
{ our: 1.3, otherMin: 1.25 },
|
|
8
|
+
{ our: 1.4, otherMin: 1.33 },
|
|
9
|
+
{ our: 1.5, otherMin: 1.4 },
|
|
10
|
+
|
|
11
|
+
{ our: 2.0, otherMin: 1.85 },
|
|
12
|
+
{ our: 2.5, otherMin: 2.25 },
|
|
13
|
+
{ our: 3.0, otherMin: 2.5 },
|
|
14
|
+
{ our: 4.0, otherMin: 3.5 },
|
|
15
|
+
|
|
16
|
+
{ our: 8.0, otherMin: 6.5 },
|
|
17
|
+
{ our: 10.0, otherMin: 8.0 },
|
|
18
|
+
{ our: 100.0, otherMin: 70.0 },
|
|
19
|
+
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { MAX_IMPLIED_PERCENTAGE_DIFF } from '../../constants/common';
|
|
2
1
|
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,9 +10,14 @@ import {
|
|
|
10
10
|
MockOnlyMoneylineWithDifferentSportsbook,
|
|
11
11
|
} from '../mock/MockOpticSoccer';
|
|
12
12
|
import { mockSoccer } from '../mock/MockSoccerRedis';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getLastPolledDataForBookmakers,
|
|
15
|
+
getPlayersMap,
|
|
16
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
17
|
+
} from '../utils/helper';
|
|
14
18
|
|
|
15
19
|
const lastPolledData = getLastPolledDataForBookmakers();
|
|
20
|
+
const playersMap = getPlayersMap();
|
|
16
21
|
|
|
17
22
|
describe('Bookmakers', () => {
|
|
18
23
|
it('Should return zero odds for moneyline when one of the bookmakers has no odds', () => {
|
|
@@ -25,10 +30,11 @@ describe('Bookmakers', () => {
|
|
|
25
30
|
[],
|
|
26
31
|
true,
|
|
27
32
|
undefined,
|
|
28
|
-
|
|
33
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
29
34
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
30
35
|
lastPolledData,
|
|
31
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
36
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
37
|
+
playersMap
|
|
32
38
|
);
|
|
33
39
|
|
|
34
40
|
const hasOdds = market.odds.some(
|
|
@@ -50,10 +56,11 @@ describe('Bookmakers', () => {
|
|
|
50
56
|
[],
|
|
51
57
|
true,
|
|
52
58
|
undefined,
|
|
53
|
-
|
|
59
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
54
60
|
LeagueMocks.leagueInfoOnlyParent,
|
|
55
61
|
lastPolledData,
|
|
56
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
62
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
63
|
+
playersMap
|
|
57
64
|
);
|
|
58
65
|
|
|
59
66
|
const hasOdds = market.odds.some(
|
|
@@ -75,10 +82,11 @@ describe('Bookmakers', () => {
|
|
|
75
82
|
[],
|
|
76
83
|
true,
|
|
77
84
|
undefined,
|
|
78
|
-
|
|
85
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
79
86
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
80
87
|
lastPolledData,
|
|
81
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
88
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
89
|
+
playersMap
|
|
82
90
|
);
|
|
83
91
|
|
|
84
92
|
const hasOdds = market.odds.some(
|
|
@@ -100,10 +108,11 @@ describe('Bookmakers', () => {
|
|
|
100
108
|
[],
|
|
101
109
|
true,
|
|
102
110
|
undefined,
|
|
103
|
-
|
|
111
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
104
112
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
105
113
|
lastPolledData,
|
|
106
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
114
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
115
|
+
playersMap
|
|
107
116
|
);
|
|
108
117
|
|
|
109
118
|
expect(market.childMarkets.length).toBe(2);
|
|
@@ -119,10 +128,11 @@ describe('Bookmakers', () => {
|
|
|
119
128
|
[],
|
|
120
129
|
true,
|
|
121
130
|
undefined,
|
|
122
|
-
|
|
131
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
123
132
|
LeagueMocks.leaguInfoDifferentPrimaryBookmaker,
|
|
124
133
|
lastPolledData,
|
|
125
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
134
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
135
|
+
playersMap
|
|
126
136
|
);
|
|
127
137
|
|
|
128
138
|
expect(market.childMarkets.length).toBe(3);
|
|
@@ -138,10 +148,11 @@ describe('Bookmakers', () => {
|
|
|
138
148
|
[],
|
|
139
149
|
true,
|
|
140
150
|
undefined,
|
|
141
|
-
|
|
151
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
142
152
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
143
153
|
lastPolledData,
|
|
144
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
154
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
155
|
+
playersMap
|
|
145
156
|
);
|
|
146
157
|
|
|
147
158
|
expect(market.childMarkets.length).toBe(1);
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import { MAX_IMPLIED_PERCENTAGE_DIFF } from '../../constants/common';
|
|
2
1
|
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 {
|
|
8
|
+
import { MockNbaData } from '../mock/OpticOddsMock/MockNBA';
|
|
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';
|
|
9
15
|
|
|
10
16
|
const lastPolledData = getLastPolledDataForBookmakers();
|
|
17
|
+
const playersMap = getPlayersMap();
|
|
11
18
|
|
|
12
19
|
describe('Markets', () => {
|
|
13
20
|
describe('LeagueMap configuration', () => {
|
|
@@ -22,10 +29,11 @@ describe('Markets', () => {
|
|
|
22
29
|
[],
|
|
23
30
|
true,
|
|
24
31
|
undefined,
|
|
25
|
-
|
|
32
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
26
33
|
LeagueMocks.leagueInfoOnlyParent,
|
|
27
34
|
lastPolledData,
|
|
28
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
35
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
36
|
+
playersMap
|
|
29
37
|
);
|
|
30
38
|
|
|
31
39
|
expect(market.childMarkets).toHaveLength(0);
|
|
@@ -41,10 +49,11 @@ describe('Markets', () => {
|
|
|
41
49
|
[],
|
|
42
50
|
true,
|
|
43
51
|
undefined,
|
|
44
|
-
|
|
52
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
45
53
|
LeagueMocks.leagueInfoMockDisabledChilds,
|
|
46
54
|
lastPolledData,
|
|
47
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
55
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
56
|
+
playersMap
|
|
48
57
|
);
|
|
49
58
|
|
|
50
59
|
expect(market.childMarkets).toHaveLength(0);
|
|
@@ -60,10 +69,11 @@ describe('Markets', () => {
|
|
|
60
69
|
[],
|
|
61
70
|
true,
|
|
62
71
|
undefined,
|
|
63
|
-
|
|
72
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
64
73
|
LeagueMocks.leagueInfoEnabledSpreadDisabledTotals,
|
|
65
74
|
lastPolledData,
|
|
66
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
75
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
76
|
+
playersMap
|
|
67
77
|
);
|
|
68
78
|
|
|
69
79
|
const containsSpread = market.childMarkets.some((child: any) => child.type === 'spread');
|
|
@@ -83,10 +93,11 @@ describe('Markets', () => {
|
|
|
83
93
|
[],
|
|
84
94
|
true,
|
|
85
95
|
undefined,
|
|
86
|
-
|
|
96
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
87
97
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
88
98
|
lastPolledData,
|
|
89
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
99
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
100
|
+
playersMap
|
|
90
101
|
);
|
|
91
102
|
|
|
92
103
|
const containsSpread = market.childMarkets.some((child: any) => child.type === 'spread');
|
|
@@ -106,10 +117,11 @@ describe('Markets', () => {
|
|
|
106
117
|
[],
|
|
107
118
|
true,
|
|
108
119
|
undefined,
|
|
109
|
-
|
|
120
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
110
121
|
LeagueMocks.leagueInfoEnabledAll,
|
|
111
122
|
lastPolledData,
|
|
112
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
123
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
124
|
+
playersMap
|
|
113
125
|
);
|
|
114
126
|
|
|
115
127
|
const containsSpread = market.childMarkets.some((child: any) => child.type === 'spread');
|
|
@@ -161,10 +173,11 @@ describe('Markets', () => {
|
|
|
161
173
|
[],
|
|
162
174
|
true,
|
|
163
175
|
undefined,
|
|
164
|
-
|
|
176
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
165
177
|
LeagueMocks.leagueInfoOnlyParentDiffSportId,
|
|
166
178
|
lastPolledData,
|
|
167
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
179
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
180
|
+
playersMap
|
|
168
181
|
);
|
|
169
182
|
|
|
170
183
|
expect(warnSpy).toHaveBeenCalled();
|
|
@@ -173,5 +186,29 @@ describe('Markets', () => {
|
|
|
173
186
|
// Restore the original implementation
|
|
174
187
|
warnSpy.mockRestore();
|
|
175
188
|
});
|
|
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
|
+
});
|
|
176
213
|
});
|
|
177
214
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { MAX_IMPLIED_PERCENTAGE_DIFF } from '../../constants/common';
|
|
2
1
|
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,9 +10,14 @@ import {
|
|
|
10
10
|
MockZeroOdds,
|
|
11
11
|
} from '../mock/MockOpticSoccer';
|
|
12
12
|
import { mockSoccer } from '../mock/MockSoccerRedis';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getLastPolledDataForBookmakers,
|
|
15
|
+
getPlayersMap,
|
|
16
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
17
|
+
} from '../utils/helper';
|
|
14
18
|
|
|
15
19
|
const lastPolledData = getLastPolledDataForBookmakers();
|
|
20
|
+
const playersMap = getPlayersMap();
|
|
16
21
|
|
|
17
22
|
describe('Odds', () => {
|
|
18
23
|
it('Should return odds for moneyline', () => {
|
|
@@ -25,10 +30,11 @@ describe('Odds', () => {
|
|
|
25
30
|
[],
|
|
26
31
|
true,
|
|
27
32
|
undefined,
|
|
28
|
-
|
|
33
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
29
34
|
LeagueMocks.leagueInfoOnlyParent,
|
|
30
35
|
lastPolledData,
|
|
31
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
36
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
37
|
+
playersMap
|
|
32
38
|
);
|
|
33
39
|
|
|
34
40
|
const hasOdds = market.odds.some(
|
|
@@ -48,10 +54,11 @@ describe('Odds', () => {
|
|
|
48
54
|
[],
|
|
49
55
|
true,
|
|
50
56
|
undefined,
|
|
51
|
-
|
|
57
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
52
58
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
53
59
|
lastPolledData,
|
|
54
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
60
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
61
|
+
playersMap
|
|
55
62
|
);
|
|
56
63
|
|
|
57
64
|
const hasOdds = market.odds.some(
|
|
@@ -73,10 +80,11 @@ describe('Odds', () => {
|
|
|
73
80
|
[],
|
|
74
81
|
true,
|
|
75
82
|
undefined,
|
|
76
|
-
|
|
83
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
77
84
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
78
85
|
lastPolledData,
|
|
79
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
86
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
87
|
+
playersMap
|
|
80
88
|
);
|
|
81
89
|
|
|
82
90
|
const hasChildMarkets = market.childMarkets.length > 0;
|
|
@@ -93,10 +101,11 @@ describe('Odds', () => {
|
|
|
93
101
|
[],
|
|
94
102
|
true,
|
|
95
103
|
undefined,
|
|
96
|
-
|
|
104
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
97
105
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
98
106
|
lastPolledData,
|
|
99
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
107
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
108
|
+
playersMap
|
|
100
109
|
);
|
|
101
110
|
|
|
102
111
|
expect(market.childMarkets).toHaveLength(0);
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import { MAX_IMPLIED_PERCENTAGE_DIFF } from '../../constants/common';
|
|
2
1
|
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 {
|
|
8
|
+
import {
|
|
9
|
+
getLastPolledDataForBookmakers,
|
|
10
|
+
getPlayersMap,
|
|
11
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
12
|
+
} from '../utils/helper';
|
|
9
13
|
|
|
10
14
|
const lastPolledData = getLastPolledDataForBookmakers();
|
|
15
|
+
const playersMap = getPlayersMap();
|
|
11
16
|
|
|
12
17
|
describe('Spread configuration', () => {
|
|
13
18
|
it('Should return zero odds for quotes that sum up total probability above 1', () => {
|
|
@@ -20,10 +25,11 @@ describe('Spread configuration', () => {
|
|
|
20
25
|
[],
|
|
21
26
|
false,
|
|
22
27
|
undefined,
|
|
23
|
-
|
|
28
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
24
29
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
25
30
|
lastPolledData,
|
|
26
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
31
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
32
|
+
playersMap
|
|
27
33
|
);
|
|
28
34
|
|
|
29
35
|
const hasOdds = market.odds.some(
|
|
@@ -47,10 +53,11 @@ describe('Spread configuration', () => {
|
|
|
47
53
|
[],
|
|
48
54
|
true,
|
|
49
55
|
undefined,
|
|
50
|
-
|
|
56
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
51
57
|
LeagueMocks.leagueInfoOnlyParent,
|
|
52
58
|
lastPolledData,
|
|
53
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
59
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
60
|
+
playersMap
|
|
54
61
|
)
|
|
55
62
|
)
|
|
56
63
|
);
|
|
@@ -64,10 +71,11 @@ describe('Spread configuration', () => {
|
|
|
64
71
|
[],
|
|
65
72
|
true,
|
|
66
73
|
undefined,
|
|
67
|
-
|
|
74
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
68
75
|
LeagueMocks.leagueInfoOnlyParentWithSpreadAdded,
|
|
69
76
|
lastPolledData,
|
|
70
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
77
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
78
|
+
playersMap
|
|
71
79
|
)
|
|
72
80
|
)
|
|
73
81
|
);
|
|
@@ -101,10 +109,11 @@ describe('Spread configuration', () => {
|
|
|
101
109
|
[],
|
|
102
110
|
true,
|
|
103
111
|
undefined,
|
|
104
|
-
|
|
112
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
105
113
|
LeagueMocks.leagueInfoOnlyParent,
|
|
106
114
|
lastPolledData,
|
|
107
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
115
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
116
|
+
playersMap
|
|
108
117
|
)
|
|
109
118
|
)
|
|
110
119
|
);
|
|
@@ -118,10 +127,11 @@ describe('Spread configuration', () => {
|
|
|
118
127
|
[],
|
|
119
128
|
true,
|
|
120
129
|
undefined,
|
|
121
|
-
|
|
130
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
122
131
|
LeagueMocks.leagueInfoOnlyParentWithSpreadAdded,
|
|
123
132
|
lastPolledData,
|
|
124
|
-
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST
|
|
133
|
+
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
134
|
+
playersMap
|
|
125
135
|
)
|
|
126
136
|
)
|
|
127
137
|
);
|
|
@@ -8,4 +8,12 @@ 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
|
+
|
|
11
19
|
export const MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST = 30000; // 30 seconds
|
package/src/types/odds.ts
CHANGED
|
@@ -7,22 +7,20 @@ 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
|
-
|
|
24
|
-
}
|
|
25
|
-
];
|
|
10
|
+
export type Odds = {
|
|
11
|
+
id: string;
|
|
12
|
+
sportsBookName: string;
|
|
13
|
+
name: string;
|
|
14
|
+
price: number;
|
|
15
|
+
timestamp: number;
|
|
16
|
+
points: number;
|
|
17
|
+
isMain: boolean;
|
|
18
|
+
isLive: boolean;
|
|
19
|
+
marketName: string;
|
|
20
|
+
playerId: string;
|
|
21
|
+
selection: string;
|
|
22
|
+
selectionLine: string;
|
|
23
|
+
}[];
|
|
26
24
|
|
|
27
25
|
export type OddsObject = {
|
|
28
26
|
gameId: string;
|
|
@@ -78,3 +76,8 @@ export type ScoresObject = {
|
|
|
78
76
|
};
|
|
79
77
|
|
|
80
78
|
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,5 +1,3 @@
|
|
|
1
|
-
import * as oddslib from 'oddslib';
|
|
2
|
-
import { MIN_ODDS_FOR_DIFF_CHECKING } from '../constants/common';
|
|
3
1
|
import {
|
|
4
2
|
DIFF_BETWEEN_BOOKMAKERS_MESSAGE,
|
|
5
3
|
NO_MATCHING_BOOKMAKERS_MESSAGE,
|
|
@@ -7,7 +5,7 @@ import {
|
|
|
7
5
|
ZERO_ODDS_MESSAGE_SINGLE_BOOKMAKER,
|
|
8
6
|
} from '../constants/errors';
|
|
9
7
|
import { BookmakersConfig } from '../types/bookmakers';
|
|
10
|
-
import { OddsWithLeagueInfo } from '../types/odds';
|
|
8
|
+
import { Anchor, OddsWithLeagueInfo } from '../types/odds';
|
|
11
9
|
import { LastPolledArray, LeagueConfigInfo } from '../types/sports';
|
|
12
10
|
|
|
13
11
|
export const getBookmakersArray = (
|
|
@@ -63,8 +61,7 @@ export const checkOddsFromBookmakers = (
|
|
|
63
61
|
oddsMap: Map<string, any>,
|
|
64
62
|
arrayOfBookmakers: string[],
|
|
65
63
|
isTwoPositionalSport: boolean,
|
|
66
|
-
|
|
67
|
-
minOddsForDiffChecking: number
|
|
64
|
+
anchors: Anchor[]
|
|
68
65
|
) => {
|
|
69
66
|
// Main bookmaker odds
|
|
70
67
|
const firstBookmakerOdds = oddsMap.get(arrayOfBookmakers[0].toLowerCase());
|
|
@@ -126,46 +123,10 @@ export const checkOddsFromBookmakers = (
|
|
|
126
123
|
const otherAwayOdd = line.awayOdds;
|
|
127
124
|
const otherDrawOdd = line.drawOdds;
|
|
128
125
|
|
|
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
|
|
158
126
|
if (
|
|
159
|
-
(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
(awayOddsDifference > maxImpliedPercentageDifference &&
|
|
163
|
-
awayOddsImplied > minOddsForDiffChecking &&
|
|
164
|
-
otherAwayOddImplied > minOddsForDiffChecking) ||
|
|
165
|
-
(!isTwoPositionalSport &&
|
|
166
|
-
drawOddsDifference > maxImpliedPercentageDifference &&
|
|
167
|
-
drawOddsImplied > minOddsForDiffChecking &&
|
|
168
|
-
otherDrawOddImplied > minOddsForDiffChecking)
|
|
127
|
+
shouldBlockOdds(homeOdd, otherHomeOdd, anchors) ||
|
|
128
|
+
shouldBlockOdds(awayOdd, otherAwayOdd, anchors) ||
|
|
129
|
+
(!isTwoPositionalSport && shouldBlockOdds(drawOdd, otherDrawOdd, anchors))
|
|
169
130
|
) {
|
|
170
131
|
return true;
|
|
171
132
|
}
|
|
@@ -195,7 +156,7 @@ export const checkOddsFromBookmakersForChildMarkets = (
|
|
|
195
156
|
oddsProviders: string[],
|
|
196
157
|
lastPolledData: LastPolledArray,
|
|
197
158
|
maxAllowedProviderDataStaleDelay: number,
|
|
198
|
-
|
|
159
|
+
anchors: Anchor[]
|
|
199
160
|
): OddsWithLeagueInfo => {
|
|
200
161
|
const formattedOdds = Object.entries(odds as any).reduce((acc: any, [key, value]: [string, any]) => {
|
|
201
162
|
const [sportsBookName, marketName, points, selection, selectionLine] = key.split('_');
|
|
@@ -227,21 +188,12 @@ export const checkOddsFromBookmakersForChildMarkets = (
|
|
|
227
188
|
`${secondaryBookmaker}_${marketName.toLowerCase()}_${points}_${selection}_${selectionLine}`
|
|
228
189
|
];
|
|
229
190
|
if (secondaryBookmakerObject) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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);
|
|
191
|
+
if (shouldBlockOdds(value.price, secondaryBookmakerObject.price, anchors)) {
|
|
192
|
+
// Block this odd
|
|
193
|
+
return acc;
|
|
244
194
|
}
|
|
195
|
+
|
|
196
|
+
acc.push(value);
|
|
245
197
|
}
|
|
246
198
|
}
|
|
247
199
|
}
|
|
@@ -297,3 +249,49 @@ export const calculateImpliedOddsDifference = (impliedOddsA: number, impliedOdds
|
|
|
297
249
|
const percentageDifference = (Math.abs(impliedOddsA - impliedOddsB) / impliedOddsA) * 100;
|
|
298
250
|
return percentageDifference;
|
|
299
251
|
};
|
|
252
|
+
|
|
253
|
+
const getRequiredOtherOdds = (odds: number, anchors: Anchor[]) => {
|
|
254
|
+
// If below the first anchor, extrapolate using first segment
|
|
255
|
+
if (odds <= anchors[0].our) {
|
|
256
|
+
const a = anchors[0];
|
|
257
|
+
const b = anchors[1];
|
|
258
|
+
const t = (odds - a.our) / (b.our - a.our);
|
|
259
|
+
return a.otherMin + t * (b.otherMin - a.otherMin);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// If above the last anchor, extrapolate using last segment
|
|
263
|
+
const last = anchors[anchors.length - 1];
|
|
264
|
+
const prev = anchors[anchors.length - 2];
|
|
265
|
+
if (odds >= last.our) {
|
|
266
|
+
const t = (odds - prev.our) / (last.our - prev.our);
|
|
267
|
+
return prev.otherMin + t * (last.otherMin - prev.otherMin);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Otherwise, find the segment we fall into and interpolate
|
|
271
|
+
for (let i = 1; i < anchors.length; i++) {
|
|
272
|
+
const a = anchors[i - 1];
|
|
273
|
+
const b = anchors[i];
|
|
274
|
+
|
|
275
|
+
if (odds <= b.our) {
|
|
276
|
+
const t = (odds - a.our) / (b.our - a.our);
|
|
277
|
+
return a.otherMin + t * (b.otherMin - a.otherMin);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Fallback (should never hit)
|
|
282
|
+
return last.otherMin;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const shouldBlockOdds = (ourOdds: number, otherOdds: number, anchors: Anchor[]) => {
|
|
286
|
+
// basic sanity check
|
|
287
|
+
if (ourOdds <= 1 || otherOdds <= 1) return true;
|
|
288
|
+
|
|
289
|
+
// If we are equal or shorter than the other book,
|
|
290
|
+
// we are not at risk.
|
|
291
|
+
if (ourOdds <= otherOdds) return false;
|
|
292
|
+
|
|
293
|
+
const requiredOther = getRequiredOtherOdds(ourOdds, anchors);
|
|
294
|
+
|
|
295
|
+
// Block if the other book is below the required threshold
|
|
296
|
+
return otherOdds < requiredOther;
|
|
297
|
+
};
|