overtime-live-trading-utils 1.0.8 → 1.1.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/.circleci/config.yml +30 -0
- package/codecov.yml +20 -0
- package/jest.config.ts +16 -0
- package/main.js +1 -1
- package/package.json +7 -2
- package/src/constants/errors.ts +5 -0
- package/src/constants/sports.ts +64 -4
- package/src/enums/sports.ts +6 -0
- package/src/tests/mock/MockLeagueMap.ts +75 -0
- package/src/tests/mock/MockOpticSoccer.ts +9267 -0
- package/src/tests/mock/MockSoccerRedis.ts +2309 -0
- package/src/tests/unit/bookmakers.test.ts +77 -0
- package/src/tests/unit/markets.test.ts +133 -0
- package/src/tests/unit/odds.test.ts +92 -0
- package/src/tests/unit/sports.test.ts +47 -0
- package/src/tests/unit/spread.test.ts +31 -0
- package/src/utils/bookmakers.ts +2 -5
- package/src/utils/constraints.ts +1 -1
- package/src/utils/markets.ts +2 -2
- package/src/utils/odds.ts +75 -23
- package/src/utils/sports.ts +14 -11
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { DIFF_BETWEEN_BOOKMAKERS_MESSAGE, ZERO_ODDS_MESSAGE } from '../../constants/errors';
|
|
2
|
+
import { processMarket } from '../../utils/markets';
|
|
3
|
+
import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
|
|
4
|
+
import { LeagueMocks } from '../mock/MockLeagueMap';
|
|
5
|
+
import { MockOnlyMoneyline, MockOnlyMoneylineWithDifferentSportsbook } from '../mock/MockOpticSoccer';
|
|
6
|
+
import { mockSoccer } from '../mock/MockSoccerRedis';
|
|
7
|
+
|
|
8
|
+
describe('Bookmakers', () => {
|
|
9
|
+
it('Should return zero odds for moneyline when one of the bookmakers has no odds', () => {
|
|
10
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
11
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOnlyMoneyline));
|
|
12
|
+
const market = processMarket(
|
|
13
|
+
freshMockSoccer,
|
|
14
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
15
|
+
['draftkings', 'bovada'],
|
|
16
|
+
[],
|
|
17
|
+
true,
|
|
18
|
+
undefined,
|
|
19
|
+
undefined,
|
|
20
|
+
LeagueMocks.leagueInfoEnabledSpeadAndTotals
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const hasOdds = market.odds.some(
|
|
24
|
+
(odd) => odd.american !== 0 || odd.decimal !== 0 || odd.normalizedImplied !== 0
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(hasOdds).toBe(false);
|
|
28
|
+
expect(market).toHaveProperty('errorMessage');
|
|
29
|
+
expect(market.errorMessage).toBe(ZERO_ODDS_MESSAGE);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('Should return zero odds for moneyline when there is quote diff between bookmakers', () => {
|
|
33
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
34
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOnlyMoneylineWithDifferentSportsbook));
|
|
35
|
+
const market = processMarket(
|
|
36
|
+
freshMockSoccer,
|
|
37
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
38
|
+
['draftkings', 'bovada'],
|
|
39
|
+
[],
|
|
40
|
+
true,
|
|
41
|
+
undefined,
|
|
42
|
+
5,
|
|
43
|
+
LeagueMocks.leagueInfoOnlyParent
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const hasOdds = market.odds.some(
|
|
47
|
+
(odd) => odd.american !== 0 || odd.decimal !== 0 || odd.normalizedImplied !== 0
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(hasOdds).toBe(false);
|
|
51
|
+
expect(market).toHaveProperty('errorMessage');
|
|
52
|
+
expect(market.errorMessage).toBe(DIFF_BETWEEN_BOOKMAKERS_MESSAGE);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('Should return zero odds for moneyline as no matching bookmaker was provided', () => {
|
|
56
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
57
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOnlyMoneyline));
|
|
58
|
+
const market = processMarket(
|
|
59
|
+
freshMockSoccer,
|
|
60
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
61
|
+
['bovada', 'draftkings'],
|
|
62
|
+
[],
|
|
63
|
+
true,
|
|
64
|
+
undefined,
|
|
65
|
+
undefined,
|
|
66
|
+
LeagueMocks.leagueInfoEnabledSpeadAndTotals
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const hasOdds = market.odds.some(
|
|
70
|
+
(odd) => odd.american !== 0 || odd.decimal !== 0 || odd.normalizedImplied !== 0
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(hasOdds).toBe(false);
|
|
74
|
+
expect(market).toHaveProperty('errorMessage');
|
|
75
|
+
expect(market.errorMessage).toBe(ZERO_ODDS_MESSAGE); // should be no matching bookmakers mesage
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { processMarket } from '../../utils/markets';
|
|
2
|
+
import { mockSoccer } from '../mock/MockSoccerRedis';
|
|
3
|
+
import { MockOnlyMoneyline, MockOpticSoccer } from '../mock/MockOpticSoccer';
|
|
4
|
+
import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
|
|
5
|
+
import { LeagueMocks } from '../mock/MockLeagueMap';
|
|
6
|
+
import { NO_MARKETS_FOR_LEAGUE_ID } from '../../constants/errors';
|
|
7
|
+
|
|
8
|
+
describe('Markets', () => {
|
|
9
|
+
describe('LeagueMap configuration', () => {
|
|
10
|
+
it('Should return an empty array for child markets when they are not added to list', () => {
|
|
11
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
12
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOpticSoccer));
|
|
13
|
+
|
|
14
|
+
const market = processMarket(
|
|
15
|
+
freshMockSoccer,
|
|
16
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
17
|
+
['draftkings'],
|
|
18
|
+
[],
|
|
19
|
+
true,
|
|
20
|
+
undefined,
|
|
21
|
+
undefined,
|
|
22
|
+
LeagueMocks.leagueInfoOnlyParent
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(market.childMarkets).toHaveLength(0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('Should return an empty array for child markets when they are disabled', () => {
|
|
29
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
30
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOpticSoccer));
|
|
31
|
+
const market = processMarket(
|
|
32
|
+
freshMockSoccer,
|
|
33
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
34
|
+
['draftkings'],
|
|
35
|
+
[],
|
|
36
|
+
true,
|
|
37
|
+
undefined,
|
|
38
|
+
undefined,
|
|
39
|
+
LeagueMocks.leagueInfoMockDisabledChilds
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(market.childMarkets).toHaveLength(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('Should return only spread child markets without total child markets', () => {
|
|
46
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
47
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOpticSoccer));
|
|
48
|
+
const market = processMarket(
|
|
49
|
+
freshMockSoccer,
|
|
50
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
51
|
+
['draftkings'],
|
|
52
|
+
[],
|
|
53
|
+
true,
|
|
54
|
+
undefined,
|
|
55
|
+
undefined,
|
|
56
|
+
LeagueMocks.leagueInfoEnabledSpreadDisabledTotals
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const containsSpread = market.childMarkets.some((child) => child.type === 'spread');
|
|
60
|
+
const containsTotal = market.childMarkets.some((child) => child.type === 'total');
|
|
61
|
+
|
|
62
|
+
expect(containsSpread).toBe(true);
|
|
63
|
+
expect(containsTotal).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('Should return both totals and spread child markets', () => {
|
|
67
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
68
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOpticSoccer));
|
|
69
|
+
const market = processMarket(
|
|
70
|
+
freshMockSoccer,
|
|
71
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
72
|
+
['draftkings'],
|
|
73
|
+
[],
|
|
74
|
+
true,
|
|
75
|
+
undefined,
|
|
76
|
+
undefined,
|
|
77
|
+
LeagueMocks.leagueInfoEnabledSpeadAndTotals
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const containsSpread = market.childMarkets.some((child) => child.type === 'spread');
|
|
81
|
+
const containsTotal = market.childMarkets.some((child) => child.type === 'total');
|
|
82
|
+
|
|
83
|
+
expect(containsSpread).toBe(true);
|
|
84
|
+
expect(containsTotal).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('Should return all child markets', () => {
|
|
88
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
89
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOpticSoccer));
|
|
90
|
+
const market = processMarket(
|
|
91
|
+
freshMockSoccer,
|
|
92
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
93
|
+
['draftkings'],
|
|
94
|
+
[],
|
|
95
|
+
true,
|
|
96
|
+
undefined,
|
|
97
|
+
undefined,
|
|
98
|
+
LeagueMocks.leagueInfoEnabledAll
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const containsSpread = market.childMarkets.some((child) => child.type === 'spread');
|
|
102
|
+
const containsTotal = market.childMarkets.some((child) => child.type === 'total');
|
|
103
|
+
const containsChildMoneyline = market.childMarkets.some((child) => child.type === 'moneyline');
|
|
104
|
+
|
|
105
|
+
expect(containsSpread).toBe(true);
|
|
106
|
+
expect(containsTotal).toBe(true);
|
|
107
|
+
expect(containsChildMoneyline).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('Should return warning message that there are is no configuration available in league map csv', () => {
|
|
111
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
112
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOnlyMoneyline));
|
|
113
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
114
|
+
|
|
115
|
+
processMarket(
|
|
116
|
+
freshMockSoccer,
|
|
117
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
118
|
+
['draftkings'],
|
|
119
|
+
[],
|
|
120
|
+
true,
|
|
121
|
+
undefined,
|
|
122
|
+
undefined,
|
|
123
|
+
LeagueMocks.leagueInfoOnlyParentDiffSportId
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
127
|
+
expect(warnSpy).toHaveBeenCalledWith(NO_MARKETS_FOR_LEAGUE_ID);
|
|
128
|
+
|
|
129
|
+
// Restore the original implementation
|
|
130
|
+
warnSpy.mockRestore();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ZERO_ODDS_MESSAGE } from '../../constants/errors';
|
|
2
|
+
import { processMarket } from '../../utils/markets';
|
|
3
|
+
import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
|
|
4
|
+
import { LeagueMocks } from '../mock/MockLeagueMap';
|
|
5
|
+
import {
|
|
6
|
+
MockOnlyMoneyline,
|
|
7
|
+
MockZeroOdds,
|
|
8
|
+
MockOddsChildMarketsGoodOdds,
|
|
9
|
+
MockOddsChildMarketsOddsCut,
|
|
10
|
+
} from '../mock/MockOpticSoccer';
|
|
11
|
+
import { mockSoccer } from '../mock/MockSoccerRedis';
|
|
12
|
+
|
|
13
|
+
describe('Odds', () => {
|
|
14
|
+
it('Should return odds for moneyline', () => {
|
|
15
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
16
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOnlyMoneyline));
|
|
17
|
+
const market = processMarket(
|
|
18
|
+
freshMockSoccer,
|
|
19
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
20
|
+
['draftkings'],
|
|
21
|
+
[],
|
|
22
|
+
true,
|
|
23
|
+
undefined,
|
|
24
|
+
undefined,
|
|
25
|
+
LeagueMocks.leagueInfoOnlyParent
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const hasOdds = market.odds.some(
|
|
29
|
+
(odd) => odd.american !== 0 || odd.decimal !== 0 || odd.normalizedImplied !== 0
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
expect(hasOdds).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('Should return zero odds for moneyline', () => {
|
|
36
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
37
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockZeroOdds));
|
|
38
|
+
const market = processMarket(
|
|
39
|
+
freshMockSoccer,
|
|
40
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
41
|
+
['draftkings'],
|
|
42
|
+
[],
|
|
43
|
+
true,
|
|
44
|
+
undefined,
|
|
45
|
+
undefined,
|
|
46
|
+
LeagueMocks.leagueInfoEnabledSpeadAndTotals
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const hasOdds = market.odds.some(
|
|
50
|
+
(odd) => odd.american !== 0 || odd.decimal !== 0 || odd.normalizedImplied !== 0
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(hasOdds).toBe(false);
|
|
54
|
+
expect(market).toHaveProperty('errorMessage');
|
|
55
|
+
expect(market.errorMessage).toBe(ZERO_ODDS_MESSAGE);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('Should contain child markets for good odds', () => {
|
|
59
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
60
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOddsChildMarketsGoodOdds));
|
|
61
|
+
const market = processMarket(
|
|
62
|
+
freshMockSoccer,
|
|
63
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
64
|
+
['draftkings'],
|
|
65
|
+
[],
|
|
66
|
+
true,
|
|
67
|
+
undefined,
|
|
68
|
+
undefined,
|
|
69
|
+
LeagueMocks.leagueInfoEnabledSpeadAndTotals
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const hasChildMarkets = market.childMarkets.length > 0;
|
|
73
|
+
expect(hasChildMarkets).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('Should return empty array for child child markets after odds cut', () => {
|
|
77
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
78
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockOddsChildMarketsOddsCut));
|
|
79
|
+
const market = processMarket(
|
|
80
|
+
freshMockSoccer,
|
|
81
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
82
|
+
['draftkings'],
|
|
83
|
+
[],
|
|
84
|
+
true,
|
|
85
|
+
undefined,
|
|
86
|
+
undefined,
|
|
87
|
+
LeagueMocks.leagueInfoEnabledSpeadAndTotals
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(market.childMarkets).toHaveLength(0);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getBetTypesForLeague,
|
|
3
|
+
getLeagueSpreadTypes,
|
|
4
|
+
getLeagueTotalTypes,
|
|
5
|
+
getLiveSupportedLeagues,
|
|
6
|
+
} from '../../utils/sports';
|
|
7
|
+
import { LeagueMocks } from '../mock/MockLeagueMap';
|
|
8
|
+
|
|
9
|
+
describe('Sports', () => {
|
|
10
|
+
it('Should return all enabled leagues for LIVE', () => {
|
|
11
|
+
const supportedLeageus = getLiveSupportedLeagues(LeagueMocks.leagueInfoEnabledSpeadAndTotals);
|
|
12
|
+
|
|
13
|
+
expect(supportedLeageus).toContain(9806);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('Should return all enabled bet types for league', () => {
|
|
17
|
+
const betTypes = getBetTypesForLeague(9806, LeagueMocks.leagueInfoEnabledSpeadAndTotals);
|
|
18
|
+
|
|
19
|
+
expect(betTypes).toContain('Moneyline');
|
|
20
|
+
expect(betTypes).toContain('Goal Spread');
|
|
21
|
+
expect(betTypes).toContain('Total Goals');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('Should return all enabled bet types for league, and not contain disabled ones', () => {
|
|
25
|
+
const betTypes = getBetTypesForLeague(9806, LeagueMocks.leagueInfoEnabledSpreadDisabledTotals);
|
|
26
|
+
|
|
27
|
+
expect(betTypes).toContain('Moneyline');
|
|
28
|
+
expect(betTypes).toContain('Goal Spread');
|
|
29
|
+
expect(betTypes).not.toContain('Total Goals');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('Should return all enabled spread bet types for league', () => {
|
|
33
|
+
const betTypes = getLeagueSpreadTypes(9806, LeagueMocks.leagueInfoEnabledSpeadAndTotals);
|
|
34
|
+
|
|
35
|
+
expect(betTypes).not.toContain('moneyline');
|
|
36
|
+
expect(betTypes).toContain('goal spread');
|
|
37
|
+
expect(betTypes).not.toContain('total goals');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('Should return all enabled total bet types for league', () => {
|
|
41
|
+
const betTypes = getLeagueTotalTypes(9806, LeagueMocks.leagueInfoEnabledSpeadAndTotals);
|
|
42
|
+
|
|
43
|
+
expect(betTypes).not.toContain('moneyline');
|
|
44
|
+
expect(betTypes).not.toContain('goal spread');
|
|
45
|
+
expect(betTypes).toContain('total goals');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ZERO_ODDS_AFTER_SPREAD_ADJUSTMENT } from '../../constants/errors';
|
|
2
|
+
import { processMarket } from '../../utils/markets';
|
|
3
|
+
import { mapOpticOddsApiFixtureOdds } from '../../utils/opticOdds';
|
|
4
|
+
import { LeagueMocks } from '../mock/MockLeagueMap';
|
|
5
|
+
import { MockAfterSpreadZeroOdds1 } from '../mock/MockOpticSoccer';
|
|
6
|
+
import { mockSoccer } from '../mock/MockSoccerRedis';
|
|
7
|
+
|
|
8
|
+
describe('Spread configuration', () => {
|
|
9
|
+
it('Should return zero odds for quotes that sum up total probability above 1', () => {
|
|
10
|
+
const freshMockSoccer = JSON.parse(JSON.stringify(mockSoccer));
|
|
11
|
+
const freshMockOpticSoccer = JSON.parse(JSON.stringify(MockAfterSpreadZeroOdds1));
|
|
12
|
+
const market = processMarket(
|
|
13
|
+
freshMockSoccer,
|
|
14
|
+
mapOpticOddsApiFixtureOdds([freshMockOpticSoccer])[0],
|
|
15
|
+
['draftkings'],
|
|
16
|
+
[],
|
|
17
|
+
false,
|
|
18
|
+
undefined,
|
|
19
|
+
undefined,
|
|
20
|
+
LeagueMocks.leagueInfoEnabledSpeadAndTotals
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const hasOdds = market.odds.some(
|
|
24
|
+
(odd) => odd.american !== 0 || odd.decimal !== 0 || odd.normalizedImplied !== 0
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(hasOdds).toBe(false);
|
|
28
|
+
expect(market).toHaveProperty('errorMessage');
|
|
29
|
+
expect(market.errorMessage).toBe(ZERO_ODDS_AFTER_SPREAD_ADJUSTMENT); // should be no matching bookmakers mesage
|
|
30
|
+
});
|
|
31
|
+
});
|
package/src/utils/bookmakers.ts
CHANGED
|
@@ -54,7 +54,7 @@ export const checkOddsFromBookmakers = (
|
|
|
54
54
|
(!isTwoPositionalSport && line.drawOdds === 1)
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
|
-
return false;
|
|
57
|
+
return false; // fix for es-lint
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
if (hasZeroOrOne) {
|
|
@@ -128,7 +128,7 @@ export const checkOddsFromBookmakers = (
|
|
|
128
128
|
return true;
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
-
return false;
|
|
131
|
+
return false; // fix for es-lint
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
if (hasLargeImpliedPercentageDifference) {
|
|
@@ -140,9 +140,6 @@ export const checkOddsFromBookmakers = (
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
let lines: any[] = [];
|
|
144
|
-
arrayOfBookmakers.forEach((bookmaker) => lines.push(oddsMap.get(bookmaker)));
|
|
145
|
-
|
|
146
143
|
return {
|
|
147
144
|
homeOdds: homeOdd,
|
|
148
145
|
awayOdds: awayOdd,
|
package/src/utils/constraints.ts
CHANGED
|
@@ -98,7 +98,7 @@ export const allowSoccerGame = (homeTeam, awayTeam, currentClock, currentPeriod,
|
|
|
98
98
|
const currentClockNumber = Number(currentClock);
|
|
99
99
|
if (
|
|
100
100
|
(!Number.isNaN(currentClockNumber) && currentClockNumber >= soccerMinuteLimitForLiveTrading) ||
|
|
101
|
-
(Number.isNaN(currentClockNumber) && currentPeriod != '
|
|
101
|
+
(Number.isNaN(currentClockNumber) && currentPeriod.toLowerCase() != 'half')
|
|
102
102
|
) {
|
|
103
103
|
return { allow: false, message: `Blocking game ${homeTeam} - ${awayTeam} due to clock: ${currentClock}min` };
|
|
104
104
|
}
|
package/src/utils/markets.ts
CHANGED
|
@@ -54,7 +54,7 @@ export const processMarket = (
|
|
|
54
54
|
if (_odd != 0) {
|
|
55
55
|
return {
|
|
56
56
|
american: oddslib.from('impliedProbability', _odd).to('moneyline'),
|
|
57
|
-
decimal: oddslib.from('impliedProbability', _odd).to('decimal'),
|
|
57
|
+
decimal: Number(oddslib.from('impliedProbability', _odd).to('decimal').toFixed(10)),
|
|
58
58
|
normalizedImplied: _odd,
|
|
59
59
|
};
|
|
60
60
|
} else {
|
|
@@ -91,7 +91,7 @@ export const processMarket = (
|
|
|
91
91
|
|
|
92
92
|
return {
|
|
93
93
|
american: oddslib.from('impliedProbability', _odd).to('moneyline'),
|
|
94
|
-
decimal: oddslib.from('impliedProbability', _odd).to('decimal'),
|
|
94
|
+
decimal: Number(oddslib.from('impliedProbability', _odd).to('decimal').toFixed(10)),
|
|
95
95
|
normalizedImplied: _odd,
|
|
96
96
|
};
|
|
97
97
|
});
|
package/src/utils/odds.ts
CHANGED
|
@@ -191,7 +191,7 @@ export const createChildMarkets: (
|
|
|
191
191
|
defaultSpreadForLiveMarkets,
|
|
192
192
|
leagueMap
|
|
193
193
|
) => {
|
|
194
|
-
const [spreadOdds, totalOdds, childMarkets]: any[] = [[], [], []];
|
|
194
|
+
const [spreadOdds, totalOdds, moneylineOdds, childMarkets]: any[] = [[], [], [], []];
|
|
195
195
|
const leagueInfo = getLeagueInfo(leagueId, leagueMap);
|
|
196
196
|
const commonData = {
|
|
197
197
|
homeTeam: apiResponseWithOdds.homeTeam,
|
|
@@ -205,19 +205,20 @@ export const createChildMarkets: (
|
|
|
205
205
|
liveOddsProviders[0]
|
|
206
206
|
);
|
|
207
207
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
allValidOdds.forEach((odd) => {
|
|
208
|
+
allChildOdds.forEach((odd) => {
|
|
211
209
|
if (odd.type === 'Total') {
|
|
212
|
-
totalOdds.push(odd);
|
|
210
|
+
if (Math.abs(Number(odd.points) % 1) === 0.5) totalOdds.push(odd);
|
|
213
211
|
} else if (odd.type === 'Spread') {
|
|
214
|
-
spreadOdds.push(odd);
|
|
212
|
+
if (Math.abs(Number(odd.points) % 1) === 0.5) spreadOdds.push(odd);
|
|
213
|
+
} else if (odd.type === 'Moneyline') {
|
|
214
|
+
moneylineOdds.push(odd);
|
|
215
215
|
}
|
|
216
216
|
});
|
|
217
217
|
|
|
218
218
|
const formattedOdds = [
|
|
219
219
|
...groupAndFormatSpreadOdds(spreadOdds, commonData),
|
|
220
220
|
...groupAndFormatTotalOdds(totalOdds, commonData),
|
|
221
|
+
...groupAndFormatMoneylineOdds(moneylineOdds, commonData),
|
|
221
222
|
];
|
|
222
223
|
|
|
223
224
|
const oddsWithSpreadAdjusted = adjustSpreadOnChildOdds(
|
|
@@ -231,7 +232,7 @@ export const createChildMarkets: (
|
|
|
231
232
|
leagueId: Number(data.sportId),
|
|
232
233
|
typeId: Number(data.typeId),
|
|
233
234
|
type: data.type.toLowerCase(),
|
|
234
|
-
line: Number(data.line),
|
|
235
|
+
line: Number(data.line || 0),
|
|
235
236
|
odds: data.odds,
|
|
236
237
|
};
|
|
237
238
|
const leagueInfoByTypeId = leagueInfo.find((league) => Number(league.typeId) === Number(data.typeId));
|
|
@@ -266,7 +267,11 @@ export const createChildMarkets: (
|
|
|
266
267
|
*/
|
|
267
268
|
export const filterOddsByMarketNameBookmaker = (oddsArray, leagueInfos: LeagueInfo[], oddsProvider) => {
|
|
268
269
|
const allChildMarketsTypes = leagueInfos
|
|
269
|
-
.filter(
|
|
270
|
+
.filter(
|
|
271
|
+
(leagueInfo) =>
|
|
272
|
+
leagueInfo.marketName.toLowerCase() !== MoneylineTypes.MONEYLINE.toLowerCase() &&
|
|
273
|
+
leagueInfo.enabled === 'true'
|
|
274
|
+
)
|
|
270
275
|
.map((leagueInfo) => leagueInfo.marketName.toLowerCase());
|
|
271
276
|
return oddsArray
|
|
272
277
|
.filter(
|
|
@@ -294,10 +299,10 @@ export const filterOddsByMarketNameBookmaker = (oddsArray, leagueInfos: LeagueIn
|
|
|
294
299
|
export const groupAndFormatSpreadOdds = (oddsArray, commonData) => {
|
|
295
300
|
// Group odds by their selection points and selection
|
|
296
301
|
const groupedOdds = oddsArray.reduce((acc: any, odd: any) => {
|
|
297
|
-
const { points, price, selection, typeId, sportId, type } = odd;
|
|
302
|
+
const { points, marketName, price, selection, typeId, sportId, type } = odd;
|
|
298
303
|
const isHomeTeam = selection === commonData.homeTeam;
|
|
299
304
|
|
|
300
|
-
const key = isHomeTeam ? points : -points
|
|
305
|
+
const key = `${marketName}_${isHomeTeam ? points : -points}`;
|
|
301
306
|
|
|
302
307
|
if (!acc[key]) {
|
|
303
308
|
acc[key] = { home: null, away: null, typeId: null, sportId: null };
|
|
@@ -317,7 +322,8 @@ export const groupAndFormatSpreadOdds = (oddsArray, commonData) => {
|
|
|
317
322
|
}, {}) as any;
|
|
318
323
|
// Format the grouped odds into the desired output
|
|
319
324
|
const formattedOdds = (Object.entries(groupedOdds as any) as any).reduce((acc, [key, value]) => {
|
|
320
|
-
const
|
|
325
|
+
const [_marketName, lineFloat] = key.split('_');
|
|
326
|
+
const line = parseFloat(lineFloat);
|
|
321
327
|
if ((value as any).home !== null && (value as any).away !== null) {
|
|
322
328
|
acc.push({
|
|
323
329
|
line: line as any,
|
|
@@ -343,7 +349,7 @@ export const groupAndFormatTotalOdds = (oddsArray, commonData) => {
|
|
|
343
349
|
// Group odds by their selection points and selection
|
|
344
350
|
const groupedOdds = oddsArray.reduce((acc, odd) => {
|
|
345
351
|
if (odd) {
|
|
346
|
-
const key = `${odd.selection}_${odd.points}`;
|
|
352
|
+
const key = `${odd.marketName}_${odd.selection}_${odd.points}`;
|
|
347
353
|
if (!acc[key]) {
|
|
348
354
|
acc[key] = { over: null, under: null };
|
|
349
355
|
}
|
|
@@ -363,7 +369,7 @@ export const groupAndFormatTotalOdds = (oddsArray, commonData) => {
|
|
|
363
369
|
|
|
364
370
|
// Format the grouped odds into the desired output
|
|
365
371
|
const formattedOdds = (Object.entries(groupedOdds as any) as any).reduce((acc, [key, value]) => {
|
|
366
|
-
const [selection, selectionLine] = key.split('_');
|
|
372
|
+
const [_marketName, selection, selectionLine] = key.split('_');
|
|
367
373
|
const line = parseFloat(selectionLine);
|
|
368
374
|
|
|
369
375
|
// if we have away team in total odds we know the market is team total and we need to increase typeId by one.
|
|
@@ -384,12 +390,61 @@ export const groupAndFormatTotalOdds = (oddsArray, commonData) => {
|
|
|
384
390
|
return formattedOdds;
|
|
385
391
|
};
|
|
386
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Groups spread odds by their lines and formats the result.
|
|
395
|
+
*
|
|
396
|
+
* @param {Array} oddsArray - The input array of odds objects.
|
|
397
|
+
* @param {Object} commonData - The common data object containing homeTeam information.
|
|
398
|
+
* @returns {Array} The grouped and formatted spread odds.
|
|
399
|
+
*/
|
|
400
|
+
export const groupAndFormatMoneylineOdds = (oddsArray, commonData) => {
|
|
401
|
+
// Group odds by their selection points and selection
|
|
402
|
+
const groupedOdds = oddsArray.reduce((acc: any, odd: any) => {
|
|
403
|
+
const { price, selection, typeId, sportId, type } = odd;
|
|
404
|
+
const key = typeId;
|
|
405
|
+
|
|
406
|
+
if (!acc[key]) {
|
|
407
|
+
acc[key] = { home: null, away: null, draw: null, typeId: null, sportId: null };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (selection.toLowerCase() === commonData.homeTeam.toLowerCase()) acc[key].home = price;
|
|
411
|
+
else if (selection.toLowerCase() === commonData.awayTeam.toLowerCase()) acc[key].away = price;
|
|
412
|
+
else if (selection.toLowerCase() === DRAW.toLowerCase()) acc[key].draw = price;
|
|
413
|
+
|
|
414
|
+
acc[key].typeId = typeId;
|
|
415
|
+
acc[key].type = type;
|
|
416
|
+
acc[key].sportId = sportId;
|
|
417
|
+
|
|
418
|
+
return acc;
|
|
419
|
+
}, {}) as any;
|
|
420
|
+
// Format the grouped odds into the desired output
|
|
421
|
+
const formattedOdds = (Object.entries(groupedOdds as any) as any).reduce((acc, [_key, value]) => {
|
|
422
|
+
if ((value as any).home !== null && (value as any).away !== null) {
|
|
423
|
+
acc.push({
|
|
424
|
+
odds: (value as any).draw
|
|
425
|
+
? [(value as any).home, (value as any).away, (value as any).draw]
|
|
426
|
+
: [(value as any).home, (value as any).away],
|
|
427
|
+
typeId: value.typeId,
|
|
428
|
+
sportId: value.sportId,
|
|
429
|
+
type: value.type,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return acc;
|
|
433
|
+
}, []);
|
|
434
|
+
|
|
435
|
+
return formattedOdds;
|
|
436
|
+
};
|
|
437
|
+
|
|
387
438
|
export const adjustSpreadOnChildOdds = (iterableGroupedOdds, spreadDataForSport, defaultSpreadForLiveMarkets) => {
|
|
388
439
|
const result: any[] = [];
|
|
389
440
|
iterableGroupedOdds.forEach((data) => {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
441
|
+
const hasDrawOdds = data.odds.length === 3;
|
|
442
|
+
const homeTeamOdds = convertOddsToImpl(data.odds[0]) || ZERO;
|
|
443
|
+
const awayTeamOdds = convertOddsToImpl(data.odds[1]) || ZERO;
|
|
444
|
+
const drawOdds = convertOddsToImpl(data.odds[2]) || ZERO;
|
|
445
|
+
const odds = hasDrawOdds ? [homeTeamOdds, awayTeamOdds, drawOdds] : [homeTeamOdds, awayTeamOdds];
|
|
446
|
+
|
|
447
|
+
const isZeroOddsChild = homeTeamOdds === ZERO || awayTeamOdds === ZERO || (hasDrawOdds && drawOdds === ZERO);
|
|
393
448
|
if (!isZeroOddsChild) {
|
|
394
449
|
const spreadData = getSpreadData(
|
|
395
450
|
spreadDataForSport,
|
|
@@ -397,17 +452,14 @@ export const adjustSpreadOnChildOdds = (iterableGroupedOdds, spreadDataForSport,
|
|
|
397
452
|
data.typeId,
|
|
398
453
|
defaultSpreadForLiveMarkets
|
|
399
454
|
);
|
|
455
|
+
|
|
400
456
|
let adjustedOdds;
|
|
401
457
|
if (spreadData !== null) {
|
|
402
|
-
adjustedOdds = adjustSpreadOnOdds(
|
|
403
|
-
[homeTeamOdds, awayTeamOdds],
|
|
404
|
-
spreadData.minSpread,
|
|
405
|
-
spreadData.targetSpread
|
|
406
|
-
);
|
|
458
|
+
adjustedOdds = adjustSpreadOnOdds(odds, spreadData.minSpread, spreadData.targetSpread);
|
|
407
459
|
} else {
|
|
408
|
-
adjustedOdds = adjustSpreadOnOdds(
|
|
460
|
+
adjustedOdds = adjustSpreadOnOdds(odds, defaultSpreadForLiveMarkets, 0);
|
|
409
461
|
}
|
|
410
|
-
|
|
462
|
+
|
|
411
463
|
result.push({
|
|
412
464
|
...data,
|
|
413
465
|
odds: adjustedOdds,
|