overtime-live-trading-utils 3.0.2 → 4.0.0-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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overtime-live-trading-utils",
3
- "version": "3.0.2",
3
+ "version": "4.0.0-rc.0",
4
4
  "description": "",
5
5
  "main": "main.js",
6
6
  "scripts": {
@@ -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 { getLastPolledDataForBookmakers, MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST } from '../utils/helper';
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- 5,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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 { getLastPolledDataForBookmakers, MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST } from '../utils/helper';
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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 { getLastPolledDataForBookmakers, MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST } from '../utils/helper';
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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 { getLastPolledDataForBookmakers, MAX_ALLOWED_PROVIDER_DATA_STALE_DELAY_TEST } from '../utils/helper';
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- MAX_IMPLIED_PERCENTAGE_DIFF,
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
- 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
- ];
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
+ };
@@ -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
- maxImpliedPercentageDifference: number,
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
- (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)
127
+ shouldBlockOdds(homeOdd, otherHomeOdd, anchors) ||
128
+ shouldBlockOdds(awayOdd, otherAwayOdd, anchors) ||
129
+ 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
- maxImpliedPercentageDifference: number
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
- 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);
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
+ };