overtime-live-trading-utils 3.0.2-rc.0 → 3.0.2-rc.2
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 +7 -7
- package/src/tests/unit/markets.test.ts +8 -8
- package/src/tests/unit/odds.test.ts +5 -5
- package/src/tests/unit/spread.test.ts +6 -6
- package/src/types/odds.ts +19 -16
- package/src/utils/bookmakers.ts +57 -59
- package/src/utils/markets.ts +8 -6
- package/src/utils/odds.ts +24 -22
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';
|
|
@@ -30,7 +30,7 @@ describe('Bookmakers', () => {
|
|
|
30
30
|
[],
|
|
31
31
|
true,
|
|
32
32
|
undefined,
|
|
33
|
-
|
|
33
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
34
34
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
35
35
|
lastPolledData,
|
|
36
36
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -56,7 +56,7 @@ describe('Bookmakers', () => {
|
|
|
56
56
|
[],
|
|
57
57
|
true,
|
|
58
58
|
undefined,
|
|
59
|
-
|
|
59
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
60
60
|
LeagueMocks.leagueInfoOnlyParent,
|
|
61
61
|
lastPolledData,
|
|
62
62
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -82,7 +82,7 @@ describe('Bookmakers', () => {
|
|
|
82
82
|
[],
|
|
83
83
|
true,
|
|
84
84
|
undefined,
|
|
85
|
-
|
|
85
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
86
86
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
87
87
|
lastPolledData,
|
|
88
88
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -108,7 +108,7 @@ describe('Bookmakers', () => {
|
|
|
108
108
|
[],
|
|
109
109
|
true,
|
|
110
110
|
undefined,
|
|
111
|
-
|
|
111
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
112
112
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
113
113
|
lastPolledData,
|
|
114
114
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -128,7 +128,7 @@ describe('Bookmakers', () => {
|
|
|
128
128
|
[],
|
|
129
129
|
true,
|
|
130
130
|
undefined,
|
|
131
|
-
|
|
131
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
132
132
|
LeagueMocks.leaguInfoDifferentPrimaryBookmaker,
|
|
133
133
|
lastPolledData,
|
|
134
134
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -148,7 +148,7 @@ describe('Bookmakers', () => {
|
|
|
148
148
|
[],
|
|
149
149
|
true,
|
|
150
150
|
undefined,
|
|
151
|
-
|
|
151
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
152
152
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
153
153
|
lastPolledData,
|
|
154
154
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -1,5 +1,5 @@
|
|
|
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';
|
|
@@ -29,7 +29,7 @@ describe('Markets', () => {
|
|
|
29
29
|
[],
|
|
30
30
|
true,
|
|
31
31
|
undefined,
|
|
32
|
-
|
|
32
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
33
33
|
LeagueMocks.leagueInfoOnlyParent,
|
|
34
34
|
lastPolledData,
|
|
35
35
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -49,7 +49,7 @@ describe('Markets', () => {
|
|
|
49
49
|
[],
|
|
50
50
|
true,
|
|
51
51
|
undefined,
|
|
52
|
-
|
|
52
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
53
53
|
LeagueMocks.leagueInfoMockDisabledChilds,
|
|
54
54
|
lastPolledData,
|
|
55
55
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -69,7 +69,7 @@ describe('Markets', () => {
|
|
|
69
69
|
[],
|
|
70
70
|
true,
|
|
71
71
|
undefined,
|
|
72
|
-
|
|
72
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
73
73
|
LeagueMocks.leagueInfoEnabledSpreadDisabledTotals,
|
|
74
74
|
lastPolledData,
|
|
75
75
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -93,7 +93,7 @@ describe('Markets', () => {
|
|
|
93
93
|
[],
|
|
94
94
|
true,
|
|
95
95
|
undefined,
|
|
96
|
-
|
|
96
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
97
97
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
98
98
|
lastPolledData,
|
|
99
99
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -117,7 +117,7 @@ describe('Markets', () => {
|
|
|
117
117
|
[],
|
|
118
118
|
true,
|
|
119
119
|
undefined,
|
|
120
|
-
|
|
120
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
121
121
|
LeagueMocks.leagueInfoEnabledAll,
|
|
122
122
|
lastPolledData,
|
|
123
123
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -173,7 +173,7 @@ describe('Markets', () => {
|
|
|
173
173
|
[],
|
|
174
174
|
true,
|
|
175
175
|
undefined,
|
|
176
|
-
|
|
176
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
177
177
|
LeagueMocks.leagueInfoOnlyParentDiffSportId,
|
|
178
178
|
lastPolledData,
|
|
179
179
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -197,7 +197,7 @@ describe('Markets', () => {
|
|
|
197
197
|
[],
|
|
198
198
|
true,
|
|
199
199
|
undefined,
|
|
200
|
-
|
|
200
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
201
201
|
LeagueMocks.PlayerAssist, // league map with player props configured
|
|
202
202
|
lastPolledData,
|
|
203
203
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -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';
|
|
@@ -30,7 +30,7 @@ describe('Odds', () => {
|
|
|
30
30
|
[],
|
|
31
31
|
true,
|
|
32
32
|
undefined,
|
|
33
|
-
|
|
33
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
34
34
|
LeagueMocks.leagueInfoOnlyParent,
|
|
35
35
|
lastPolledData,
|
|
36
36
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -54,7 +54,7 @@ describe('Odds', () => {
|
|
|
54
54
|
[],
|
|
55
55
|
true,
|
|
56
56
|
undefined,
|
|
57
|
-
|
|
57
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
58
58
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
59
59
|
lastPolledData,
|
|
60
60
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -80,7 +80,7 @@ describe('Odds', () => {
|
|
|
80
80
|
[],
|
|
81
81
|
true,
|
|
82
82
|
undefined,
|
|
83
|
-
|
|
83
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
84
84
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
85
85
|
lastPolledData,
|
|
86
86
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -101,7 +101,7 @@ describe('Odds', () => {
|
|
|
101
101
|
[],
|
|
102
102
|
true,
|
|
103
103
|
undefined,
|
|
104
|
-
|
|
104
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
105
105
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
106
106
|
lastPolledData,
|
|
107
107
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -1,5 +1,5 @@
|
|
|
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';
|
|
@@ -25,7 +25,7 @@ describe('Spread configuration', () => {
|
|
|
25
25
|
[],
|
|
26
26
|
false,
|
|
27
27
|
undefined,
|
|
28
|
-
|
|
28
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
29
29
|
LeagueMocks.leagueInfoEnabledSpeadAndTotals,
|
|
30
30
|
lastPolledData,
|
|
31
31
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -53,7 +53,7 @@ describe('Spread configuration', () => {
|
|
|
53
53
|
[],
|
|
54
54
|
true,
|
|
55
55
|
undefined,
|
|
56
|
-
|
|
56
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
57
57
|
LeagueMocks.leagueInfoOnlyParent,
|
|
58
58
|
lastPolledData,
|
|
59
59
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -71,7 +71,7 @@ describe('Spread configuration', () => {
|
|
|
71
71
|
[],
|
|
72
72
|
true,
|
|
73
73
|
undefined,
|
|
74
|
-
|
|
74
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
75
75
|
LeagueMocks.leagueInfoOnlyParentWithSpreadAdded,
|
|
76
76
|
lastPolledData,
|
|
77
77
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -109,7 +109,7 @@ describe('Spread configuration', () => {
|
|
|
109
109
|
[],
|
|
110
110
|
true,
|
|
111
111
|
undefined,
|
|
112
|
-
|
|
112
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
113
113
|
LeagueMocks.leagueInfoOnlyParent,
|
|
114
114
|
lastPolledData,
|
|
115
115
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
|
@@ -127,7 +127,7 @@ describe('Spread configuration', () => {
|
|
|
127
127
|
[],
|
|
128
128
|
true,
|
|
129
129
|
undefined,
|
|
130
|
-
|
|
130
|
+
ODDS_THRESHOLD_ANCHORS,
|
|
131
131
|
LeagueMocks.leagueInfoOnlyParentWithSpreadAdded,
|
|
132
132
|
lastPolledData,
|
|
133
133
|
MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST,
|
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 = (
|
|
@@ -74,8 +72,7 @@ export const checkOddsFromBookmakers = (
|
|
|
74
72
|
oddsMap: Map<string, any>,
|
|
75
73
|
arrayOfBookmakers: string[],
|
|
76
74
|
isTwoPositionalSport: boolean,
|
|
77
|
-
|
|
78
|
-
minOddsForDiffChecking: number
|
|
75
|
+
anchors: Anchor[]
|
|
79
76
|
) => {
|
|
80
77
|
// Main bookmaker odds
|
|
81
78
|
const firstBookmakerOdds = oddsMap.get(arrayOfBookmakers[0].toLowerCase());
|
|
@@ -137,46 +134,10 @@ export const checkOddsFromBookmakers = (
|
|
|
137
134
|
const otherAwayOdd = line.awayOdds;
|
|
138
135
|
const otherDrawOdd = line.drawOdds;
|
|
139
136
|
|
|
140
|
-
const homeOddsImplied = oddslib.from('decimal', homeOdd).to('impliedProbability');
|
|
141
|
-
|
|
142
|
-
const awayOddsImplied = oddslib.from('decimal', awayOdd).to('impliedProbability');
|
|
143
|
-
|
|
144
|
-
// Calculate implied odds for the "draw" if it's not a two-positions sport
|
|
145
|
-
const drawOddsImplied = isTwoPositionalSport
|
|
146
|
-
? 0
|
|
147
|
-
: oddslib.from('decimal', drawOdd).to('impliedProbability');
|
|
148
|
-
|
|
149
|
-
const otherHomeOddImplied = oddslib.from('decimal', otherHomeOdd).to('impliedProbability');
|
|
150
|
-
|
|
151
|
-
const otherAwayOddImplied = oddslib.from('decimal', otherAwayOdd).to('impliedProbability');
|
|
152
|
-
|
|
153
|
-
// Calculate implied odds for the "draw" if it's not a two-positions sport
|
|
154
|
-
const otherDrawOddImplied = isTwoPositionalSport
|
|
155
|
-
? 0
|
|
156
|
-
: oddslib.from('decimal', otherDrawOdd).to('impliedProbability');
|
|
157
|
-
|
|
158
|
-
// Calculate the percentage difference for implied odds
|
|
159
|
-
const homeOddsDifference = calculateImpliedOddsDifference(homeOddsImplied, otherHomeOddImplied);
|
|
160
|
-
|
|
161
|
-
const awayOddsDifference = calculateImpliedOddsDifference(awayOddsImplied, otherAwayOddImplied);
|
|
162
|
-
|
|
163
|
-
// Check implied odds difference for the "draw" only if it's not a two-positions sport
|
|
164
|
-
const drawOddsDifference = isTwoPositionalSport
|
|
165
|
-
? 0
|
|
166
|
-
: calculateImpliedOddsDifference(drawOddsImplied, otherDrawOddImplied);
|
|
167
|
-
|
|
168
|
-
// Check if the percentage difference exceeds the threshold
|
|
169
137
|
if (
|
|
170
|
-
(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
(awayOddsDifference > maxImpliedPercentageDifference &&
|
|
174
|
-
awayOddsImplied > minOddsForDiffChecking &&
|
|
175
|
-
otherAwayOddImplied > minOddsForDiffChecking) ||
|
|
176
|
-
(!isTwoPositionalSport &&
|
|
177
|
-
drawOddsDifference > maxImpliedPercentageDifference &&
|
|
178
|
-
drawOddsImplied > minOddsForDiffChecking &&
|
|
179
|
-
otherDrawOddImplied > minOddsForDiffChecking)
|
|
138
|
+
shouldBlockOdds(homeOdd, otherHomeOdd, anchors) ||
|
|
139
|
+
shouldBlockOdds(awayOdd, otherAwayOdd, anchors) ||
|
|
140
|
+
shouldBlockOdds(drawOdd, otherDrawOdd, anchors)
|
|
180
141
|
) {
|
|
181
142
|
return true;
|
|
182
143
|
}
|
|
@@ -206,7 +167,7 @@ export const checkOddsFromBookmakersForChildMarkets = (
|
|
|
206
167
|
oddsProviders: string[],
|
|
207
168
|
lastPolledData: LastPolledArray,
|
|
208
169
|
maxAllowedProviderDataStaleDelay: number,
|
|
209
|
-
|
|
170
|
+
anchors: Anchor[]
|
|
210
171
|
): OddsWithLeagueInfo => {
|
|
211
172
|
const formattedOdds = Object.entries(odds as any).reduce((acc: any, [key, value]: [string, any]) => {
|
|
212
173
|
const [sportsBookName, marketName, points, selection, selectionLine] = key.split('_');
|
|
@@ -238,21 +199,12 @@ export const checkOddsFromBookmakersForChildMarkets = (
|
|
|
238
199
|
`${secondaryBookmaker}_${marketName.toLowerCase()}_${points}_${selection}_${selectionLine}`
|
|
239
200
|
];
|
|
240
201
|
if (secondaryBookmakerObject) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
.to('impliedProbability');
|
|
245
|
-
if (
|
|
246
|
-
primaryOdds >= MIN_ODDS_FOR_DIFF_CHECKING &&
|
|
247
|
-
secondaryOdds >= MIN_ODDS_FOR_DIFF_CHECKING
|
|
248
|
-
) {
|
|
249
|
-
const homeOddsDifference = calculateImpliedOddsDifference(primaryOdds, secondaryOdds);
|
|
250
|
-
if (Number(homeOddsDifference) <= Number(maxImpliedPercentageDifference)) {
|
|
251
|
-
acc.push(value);
|
|
252
|
-
}
|
|
253
|
-
} else {
|
|
254
|
-
acc.push(value);
|
|
202
|
+
if (shouldBlockOdds(value.price, secondaryBookmakerObject.price, anchors)) {
|
|
203
|
+
// Block this odd
|
|
204
|
+
return acc;
|
|
255
205
|
}
|
|
206
|
+
|
|
207
|
+
acc.push(value);
|
|
256
208
|
}
|
|
257
209
|
}
|
|
258
210
|
}
|
|
@@ -308,3 +260,49 @@ export const calculateImpliedOddsDifference = (impliedOddsA: number, impliedOdds
|
|
|
308
260
|
const percentageDifference = (Math.abs(impliedOddsA - impliedOddsB) / impliedOddsA) * 100;
|
|
309
261
|
return percentageDifference;
|
|
310
262
|
};
|
|
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
|
+
};
|
package/src/utils/markets.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as oddslib from 'oddslib';
|
|
2
2
|
import { ZERO_ODDS_AFTER_SPREAD_ADJUSTMENT } from '../constants/errors';
|
|
3
|
-
import { OddsObject } from '../types/odds';
|
|
3
|
+
import { Anchor, OddsObject } from '../types/odds';
|
|
4
4
|
import { LastPolledArray } from '../types/sports';
|
|
5
5
|
import { createChildMarkets, getParentOdds } from './odds';
|
|
6
6
|
import { getLeagueInfo } from './sports';
|
|
@@ -16,8 +16,10 @@ import { adjustAddedSpread } from './spread';
|
|
|
16
16
|
* @param {Array} spreadData - Spread data for odds.
|
|
17
17
|
* @param {Boolean} isDrawAvailable - Is it two or three-positional sport
|
|
18
18
|
* @param {Number} defaultSpreadForLiveMarkets - Default spread for live markets
|
|
19
|
-
* @param {
|
|
20
|
-
* @param {
|
|
19
|
+
* @param {Object} leagueMap - League map for additional league information
|
|
20
|
+
* @param {LastPolledArray} lastPolledData - Array containing last polled timestamps for bookmakers
|
|
21
|
+
* @param {Number} maxAllowedProviderDataStaleDelay - Maximum allowed delay for provider data to be considered fresh
|
|
22
|
+
* @param {Map<string, number>} playersMap - Map of player OO IDs to our internal player ID
|
|
21
23
|
* @returns {Promise<Object|null>} A promise that resolves to the processed event object or null if the event is invalid or mapping fails.
|
|
22
24
|
*/
|
|
23
25
|
export const processMarket = (
|
|
@@ -27,7 +29,7 @@ export const processMarket = (
|
|
|
27
29
|
spreadData: any,
|
|
28
30
|
isDrawAvailable: any,
|
|
29
31
|
defaultSpreadForLiveMarkets: any,
|
|
30
|
-
|
|
32
|
+
anchors: Anchor[],
|
|
31
33
|
leagueMap: any,
|
|
32
34
|
lastPolledData: LastPolledArray,
|
|
33
35
|
maxAllowedProviderDataStaleDelay: number,
|
|
@@ -43,7 +45,7 @@ export const processMarket = (
|
|
|
43
45
|
apiResponseWithOdds,
|
|
44
46
|
market.leagueId,
|
|
45
47
|
defaultSpreadForLiveMarkets,
|
|
46
|
-
|
|
48
|
+
anchors,
|
|
47
49
|
leagueInfo,
|
|
48
50
|
lastPolledData,
|
|
49
51
|
maxAllowedProviderDataStaleDelay
|
|
@@ -90,7 +92,7 @@ export const processMarket = (
|
|
|
90
92
|
leagueMap,
|
|
91
93
|
lastPolledData,
|
|
92
94
|
maxAllowedProviderDataStaleDelay,
|
|
93
|
-
|
|
95
|
+
anchors,
|
|
94
96
|
playersMap
|
|
95
97
|
);
|
|
96
98
|
|
package/src/utils/odds.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as oddslib from 'oddslib';
|
|
2
2
|
import { MarketType, MarketTypeMap } from 'overtime-utils';
|
|
3
|
-
import { DRAW,
|
|
3
|
+
import { DRAW, MONEYLINE_TYPE_ID, ZERO } from '../constants/common';
|
|
4
4
|
import { LAST_POLLED_TOO_OLD, NO_MARKETS_FOR_LEAGUE_ID } from '../constants/errors';
|
|
5
5
|
import { MoneylineTypes } from '../enums/sports';
|
|
6
|
-
import { HomeAwayTeams, Odds, OddsObject } from '../types/odds';
|
|
6
|
+
import { Anchor, HomeAwayTeams, Odds, OddsObject } from '../types/odds';
|
|
7
7
|
import { ChildMarket, LastPolledArray, LeagueConfigInfo } from '../types/sports';
|
|
8
8
|
import {
|
|
9
9
|
checkOddsFromBookmakers,
|
|
@@ -128,7 +128,7 @@ export const getParentOdds = (
|
|
|
128
128
|
oddsApiObject: OddsObject,
|
|
129
129
|
sportId: string,
|
|
130
130
|
defaultSpreadForLiveMarkets: number,
|
|
131
|
-
|
|
131
|
+
anchors: Anchor[],
|
|
132
132
|
leagueInfo: LeagueConfigInfo[],
|
|
133
133
|
lastPolledData: LastPolledArray,
|
|
134
134
|
maxAllowedProviderDataStaleDelay: number
|
|
@@ -164,13 +164,7 @@ export const getParentOdds = (
|
|
|
164
164
|
);
|
|
165
165
|
|
|
166
166
|
// CHECKING AND COMPARING ODDS FOR THE GIVEN BOOKMAKERS
|
|
167
|
-
const oddsObject = checkOddsFromBookmakers(
|
|
168
|
-
moneylineOddsMap,
|
|
169
|
-
bookmakers,
|
|
170
|
-
isTwoPositionalSport,
|
|
171
|
-
maxPercentageDiffBetwenOdds,
|
|
172
|
-
MIN_ODDS_FOR_DIFF_CHECKING
|
|
173
|
-
);
|
|
167
|
+
const oddsObject = checkOddsFromBookmakers(moneylineOddsMap, bookmakers, isTwoPositionalSport, anchors);
|
|
174
168
|
|
|
175
169
|
if (oddsObject.errorMessage) {
|
|
176
170
|
return {
|
|
@@ -214,7 +208,7 @@ export const createChildMarkets: (
|
|
|
214
208
|
leagueMap: any,
|
|
215
209
|
lastPolledData: LastPolledArray,
|
|
216
210
|
maxAllowedProviderDataStaleDelay: number,
|
|
217
|
-
|
|
211
|
+
anchors: Anchor[],
|
|
218
212
|
playersMap: Map<string, number>
|
|
219
213
|
) => ChildMarket[] = (
|
|
220
214
|
apiResponseWithOdds,
|
|
@@ -225,7 +219,7 @@ export const createChildMarkets: (
|
|
|
225
219
|
leagueMap,
|
|
226
220
|
lastPolledData,
|
|
227
221
|
maxAllowedProviderDataStaleDelay,
|
|
228
|
-
|
|
222
|
+
anchors,
|
|
229
223
|
playersMap
|
|
230
224
|
) => {
|
|
231
225
|
const [spreadOdds, totalOdds, moneylineOdds, correctScoreOdds, doubleChanceOdds, ggOdds, childMarkets]: any[] = [
|
|
@@ -251,7 +245,7 @@ export const createChildMarkets: (
|
|
|
251
245
|
liveOddsProviders,
|
|
252
246
|
lastPolledData,
|
|
253
247
|
maxAllowedProviderDataStaleDelay,
|
|
254
|
-
|
|
248
|
+
anchors
|
|
255
249
|
);
|
|
256
250
|
checkedChildOdds.forEach((odd) => {
|
|
257
251
|
if (odd.type === 'Total') {
|
|
@@ -286,21 +280,29 @@ export const createChildMarkets: (
|
|
|
286
280
|
);
|
|
287
281
|
|
|
288
282
|
homeAwayOddsWithSpreadAdjusted.forEach((data) => {
|
|
289
|
-
let
|
|
290
|
-
if (data.playerProps) {
|
|
291
|
-
playerProps = {
|
|
292
|
-
playerId: playersMap.get(data.playerProps.playerId.toString()), // convert from opticOdds playerId to our internal playerId
|
|
293
|
-
playerName: data.playerProps.playerName,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
const childMarket = {
|
|
283
|
+
let childMarket = {
|
|
297
284
|
leagueId: Number(data.sportId),
|
|
298
285
|
typeId: Number(data.typeId),
|
|
299
286
|
type: MarketTypeMap[data.typeId as MarketType]?.key || '',
|
|
300
287
|
line: Number(data.line || 0),
|
|
301
288
|
odds: data.odds,
|
|
302
|
-
playerProps
|
|
289
|
+
playerProps: {
|
|
290
|
+
playerId: 0,
|
|
291
|
+
playerName: '',
|
|
292
|
+
},
|
|
293
|
+
isPlayerPropsMarket: false,
|
|
303
294
|
};
|
|
295
|
+
if (data.playerProps) {
|
|
296
|
+
childMarket = {
|
|
297
|
+
...childMarket,
|
|
298
|
+
playerProps: {
|
|
299
|
+
playerId: playersMap.get(data.playerProps.playerId.toString()) || 0, // convert from opticOdds playerId to our internal playerId
|
|
300
|
+
playerName: data.playerProps.playerName,
|
|
301
|
+
},
|
|
302
|
+
isPlayerPropsMarket: true,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
304
306
|
const leagueInfoByTypeId = leagueInfo.find((league) => Number(league.typeId) === Number(data.typeId));
|
|
305
307
|
const minOdds = leagueInfoByTypeId?.minOdds; // minimum odds configured for child market (e.g. 0.95 implied probability)
|
|
306
308
|
const maxOdds = leagueInfoByTypeId?.maxOdds; // maximum odds configured for child market (e.g. 0.05 implied probability)
|