overtime-live-trading-utils 2.1.33 → 2.1.35

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.
@@ -36,8 +36,8 @@ export enum SportPeriodType {
36
36
  * Period 2 = 2nd half
37
37
  */
38
38
  export const HALVES_PERIOD_TYPE_ID_MAPPING: { [period: number]: number[] } = {
39
- 1: [10021, 10031, 10041, 10051, 10061, 10071, 10081, 10111, 10112, 10121, 10163, 10200], // 1st half
40
- 2: [10022, 10032, 10042, 10052, 10062, 10072, 10082, 10211, 10212, 10122, 10164, 10201], // 2nd half
39
+ 1: [10021, 10031, 10041, 10051, 10061, 10071, 10081, 10111, 10112, 10121, 10163, 10200, 10101], // 1st half
40
+ 2: [10022, 10032, 10042, 10052, 10062, 10072, 10082, 10211, 10212, 10122, 10164, 10201, 10102], // 2nd half
41
41
  };
42
42
 
43
43
  /**
@@ -49,11 +49,11 @@ export const HALVES_PERIOD_TYPE_ID_MAPPING: { [period: number]: number[] } = {
49
49
  * Period 5+ = Overtime
50
50
  */
51
51
  export const QUARTERS_PERIOD_TYPE_ID_MAPPING: { [period: number]: number[] } = {
52
- 1: [10021, 10031, 10041, 10061, 10071, 10081, 10111, 10112, 10121, 10163], // 1st quarter
53
- 2: [10022, 10032, 10042, 10051, 10062, 10072, 10082, 10211, 10212, 10122, 10164], // 2nd quarter + 1st half
54
- 3: [10023, 10033, 10043, 10063, 10073, 10083, 10311, 10312, 10123, 10165], // 3rd quarter
55
- 4: [10024, 10034, 10044, 10052, 10064, 10074, 10084, 10411, 10412, 10124, 10166], // 4th quarter + 2nd half
56
- 5: [10025, 10035, 10045, 10055, 10065, 10075, 10085, 10511, 10512, 10167], // Overtime/5th period
52
+ 1: [10021, 10031, 10041, 10061, 10071, 10081, 10111, 10112, 10121, 10163, 10101], // 1st quarter
53
+ 2: [10022, 10032, 10042, 10051, 10062, 10072, 10082, 10211, 10212, 10122, 10164, 10102], // 2nd quarter + 1st half
54
+ 3: [10023, 10033, 10043, 10063, 10073, 10083, 10311, 10312, 10123, 10165, 10103], // 3rd quarter
55
+ 4: [10024, 10034, 10044, 10052, 10064, 10074, 10084, 10411, 10412, 10124, 10166, 10104], // 4th quarter + 2nd half
56
+ 5: [10025, 10035, 10045, 10055, 10065, 10075, 10085, 10511, 10512, 10167, 10105], // Overtime/5th period
57
57
  };
58
58
 
59
59
  /**
@@ -62,15 +62,15 @@ export const QUARTERS_PERIOD_TYPE_ID_MAPPING: { [period: number]: number[] } = {
62
62
  * Period 6-9 = Innings 6-9 (period 9 completes 2nd half - typeId 10052)
63
63
  */
64
64
  export const INNINGS_PERIOD_TYPE_ID_MAPPING: { [period: number]: number[] } = {
65
- 1: [10021, 10031, 10041, 10061, 10071, 10081, 10111, 10112, 10121, 10163], // 1st inning
66
- 2: [10022, 10032, 10042, 10062, 10072, 10082, 10211, 10212, 10122, 10164], // 2nd inning
67
- 3: [10023, 10033, 10043, 10063, 10073, 10083, 10311, 10312, 10123, 10165, 10170, 10171, 10172, 10173, 10174, 10201], // 3rd inning + first 3 innings
68
- 4: [10024, 10034, 10044, 10064, 10074, 10084, 10411, 10412, 10124, 10166], // 4th inning
69
- 5: [10025, 10035, 10045, 10051, 10065, 10075, 10085, 10511, 10512, 10167, 10200], // 5th inning + 1st half (first 5 innings)
70
- 6: [10026, 10036, 10046, 10056, 10066, 10076, 10086, 10611, 10612, 10168], // 6th inning
71
- 7: [10027, 10037, 10047, 10057, 10067, 10077, 10087, 10711, 10712, 10169, 10175, 10176, 10177, 10178, 10179, 10202], // 7th inning + first 7 innings
72
- 8: [10028, 10038, 10048, 10058, 10068, 10078, 10088, 10811, 10812, 10197], // 8th inning
73
- 9: [10029, 10039, 10049, 10052, 10069, 10079, 10089, 10198], // 9th inning
65
+ 1: [10021, 10031, 10041, 10061, 10071, 10081, 10111, 10112, 10121, 10163, 10101], // 1st inning
66
+ 2: [10022, 10032, 10042, 10062, 10072, 10082, 10211, 10212, 10122, 10164, 10102], // 2nd inning
67
+ 3: [10023, 10033, 10043, 10063, 10073, 10083, 10311, 10312, 10123, 10165, 10170, 10171, 10172, 10173, 10174, 10201, 10103], // 3rd inning + first 3 innings
68
+ 4: [10024, 10034, 10044, 10064, 10074, 10084, 10411, 10412, 10124, 10166, 10104], // 4th inning
69
+ 5: [10025, 10035, 10045, 10051, 10065, 10075, 10085, 10511, 10512, 10167, 10200, 10105], // 5th inning + 1st half (first 5 innings)
70
+ 6: [10026, 10036, 10046, 10056, 10066, 10076, 10086, 10611, 10612, 10168, 10106], // 6th inning
71
+ 7: [10027, 10037, 10047, 10057, 10067, 10077, 10087, 10711, 10712, 10169, 10175, 10176, 10177, 10178, 10179, 10202, 10107], // 7th inning + first 7 innings
72
+ 8: [10028, 10038, 10048, 10058, 10068, 10078, 10088, 10811, 10812, 10197, 10108], // 8th inning
73
+ 9: [10029, 10039, 10049, 10052, 10069, 10079, 10089, 10198, 10109], // 9th inning
74
74
  };
75
75
 
76
76
  /**
@@ -78,15 +78,15 @@ export const INNINGS_PERIOD_TYPE_ID_MAPPING: { [period: number]: number[] } = {
78
78
  * Excludes halves types (10051, 10052) and secondary moneyline types (ending in 11/12)
79
79
  */
80
80
  export const PERIOD_BASED_TYPE_ID_MAPPING: { [period: number]: number[] } = {
81
- 1: [10021, 10031, 10041, 10061, 10071, 10081, 10121, 10163], // 1st period
82
- 2: [10022, 10032, 10042, 10062, 10072, 10082, 10122, 10164], // 2nd period
83
- 3: [10023, 10033, 10043, 10063, 10073, 10083, 10123, 10165], // 3rd period
84
- 4: [10024, 10034, 10044, 10064, 10074, 10084, 10124, 10166], // 4th period
85
- 5: [10025, 10035, 10045, 10065, 10075, 10085, 10167], // 5th period
86
- 6: [10026, 10036, 10046, 10056, 10066, 10076, 10086, 10168], // 6th period
87
- 7: [10027, 10037, 10047, 10057, 10067, 10077, 10087, 10169], // 7th period
88
- 8: [10028, 10038, 10048, 10058, 10068, 10078, 10088, 10197], // 8th period
89
- 9: [10029, 10039, 10049, 10069, 10079, 10089, 10198], // 9th period
81
+ 1: [10021, 10031, 10041, 10061, 10071, 10081, 10121, 10163, 10101], // 1st period
82
+ 2: [10022, 10032, 10042, 10062, 10072, 10082, 10122, 10164, 10102], // 2nd period
83
+ 3: [10023, 10033, 10043, 10063, 10073, 10083, 10123, 10165, 10103], // 3rd period
84
+ 4: [10024, 10034, 10044, 10064, 10074, 10084, 10124, 10166, 10104], // 4th period
85
+ 5: [10025, 10035, 10045, 10065, 10075, 10085, 10167, 10105], // 5th period
86
+ 6: [10026, 10036, 10046, 10056, 10066, 10076, 10086, 10168, 10106], // 6th period
87
+ 7: [10027, 10037, 10047, 10057, 10067, 10077, 10087, 10169, 10107], // 7th period
88
+ 8: [10028, 10038, 10048, 10058, 10068, 10078, 10088, 10197, 10108], // 8th period
89
+ 9: [10029, 10039, 10049, 10069, 10079, 10089, 10198, 10109], // 9th period
90
90
  };
91
91
 
92
92
  /**
@@ -9,6 +9,7 @@ import {
9
9
  PERIOD_BASED_TYPE_ID_MAPPING,
10
10
  FULL_GAME_TYPE_IDS,
11
11
  } from '../types/resolution';
12
+ import { getSportPeriodTypeFromEvent } from './sportPeriodMapping';
12
13
 
13
14
  /**
14
15
  * Detects completed periods for a game based on OpticOdds API event data
@@ -16,12 +17,14 @@ import {
16
17
  * @param event - Event object from OpticOdds API
17
18
  * @returns PeriodResolutionData with completed periods, readiness status, and period scores
18
19
  */
19
- export const detectCompletedPeriods = (
20
- event: OpticOddsEvent
21
- ): PeriodResolutionData | null => {
20
+ export const detectCompletedPeriods = (event: OpticOddsEvent): PeriodResolutionData | null => {
22
21
  const status = (event.fixture?.status || event.status || '').toLowerCase();
23
22
  const isLive = event.fixture?.is_live ?? event.is_live ?? false;
24
23
 
24
+ // Determine sport period type for sport-aware logic
25
+ // Defaults to PERIOD_BASED (no halftime processing) for unknown sports
26
+ const sportType = getSportPeriodTypeFromEvent(event);
27
+
25
28
  // Extract period scores from the event
26
29
  const homePeriods = event.scores?.home?.periods || {};
27
30
  const awayPeriods = event.scores?.away?.periods || {};
@@ -44,14 +47,15 @@ export const detectCompletedPeriods = (
44
47
  const currentLivePeriod = inPlayPeriod && !isNaN(parseInt(inPlayPeriod)) ? parseInt(inPlayPeriod) : null;
45
48
 
46
49
  // Check if game is in overtime/extra time (non-numeric period indicator)
47
- const isInOvertime = inPlayPeriod &&
50
+ const isInOvertime =
51
+ inPlayPeriod &&
48
52
  (inPlayPeriod.toLowerCase().includes('overtime') ||
49
- inPlayPeriod.toLowerCase().includes('ot') ||
50
- inPlayPeriod.toLowerCase().includes('extra'));
53
+ inPlayPeriod.toLowerCase().includes('ot') ||
54
+ inPlayPeriod.toLowerCase().includes('extra'));
51
55
 
52
- // Check if game is at halftime (period 1 is complete)
53
- const isAtHalftime = (status === 'half' || status === 'halftime') ||
54
- (inPlayPeriod && inPlayPeriod.toLowerCase() === 'half');
56
+ // Check if game is at halftime
57
+ const isAtHalftime =
58
+ status === 'half' || status === 'halftime' || (inPlayPeriod && inPlayPeriod.toLowerCase() === 'half');
55
59
 
56
60
  // For each period, check if it's complete
57
61
  for (const periodNum of periodKeys) {
@@ -67,16 +71,38 @@ export const detectCompletedPeriods = (
67
71
  // 1. Game is completed (status = completed/finished), OR
68
72
  // 2. Game is live AND in_play.period is GREATER than this period, OR
69
73
  // 3. Game is in overtime (all regulation periods are complete), OR
70
- // 4. Game is at halftime AND this is period 1
74
+ // 4. Game is at halftime AND this period is complete based on sport type
71
75
  //
72
76
  // Note: We do NOT check if next period exists in data, as OpticOdds may include
73
77
  // future periods with scores (including zeros). Only in_play.period is
74
78
  // the source of truth for live games.
75
79
  const isCompleted = status === 'completed' || status === 'complete' || status === 'finished';
76
80
  const isCompletedInLiveGame = isLive && currentLivePeriod !== null && currentLivePeriod > periodNum;
77
- const isFirstPeriodAtHalftime = isAtHalftime && periodNum === 1;
78
81
 
79
- if (isCompleted || isCompletedInLiveGame || isInOvertime || isFirstPeriodAtHalftime) {
82
+ // Sport-aware halftime logic
83
+ let isCompletedAtHalftime = false;
84
+ if (isAtHalftime) {
85
+ switch (sportType) {
86
+ case SportPeriodType.HALVES_BASED:
87
+ // Soccer, etc: halftime = end of period 1 (first half complete)
88
+ isCompletedAtHalftime = periodNum === 1;
89
+ break;
90
+ case SportPeriodType.QUARTERS_BASED:
91
+ // Basketball, Football: halftime = end of period 2 (first two quarters complete)
92
+ isCompletedAtHalftime = periodNum === 1 || periodNum === 2;
93
+ break;
94
+ case SportPeriodType.INNINGS_BASED:
95
+ // Baseball: halftime = middle of game (first 5 innings complete)
96
+ isCompletedAtHalftime = periodNum >= 1 && periodNum <= 5;
97
+ break;
98
+ case SportPeriodType.PERIOD_BASED:
99
+ // Hockey: no traditional halftime concept
100
+ isCompletedAtHalftime = false;
101
+ break;
102
+ }
103
+ }
104
+
105
+ if (isCompleted || isCompletedInLiveGame || isInOvertime || isCompletedAtHalftime) {
80
106
  completedPeriods.push(periodNum);
81
107
  }
82
108
  }
@@ -86,9 +112,7 @@ export const detectCompletedPeriods = (
86
112
  // For live games with numeric in_play period, use that as the authoritative current period
87
113
  // Otherwise use highest period number from the data
88
114
  const highestPeriodInData = periodKeys.length > 0 ? Math.max(...periodKeys) : undefined;
89
- const currentPeriod = isLive && currentLivePeriod !== null
90
- ? currentLivePeriod
91
- : highestPeriodInData;
115
+ const currentPeriod = isLive && currentLivePeriod !== null ? currentLivePeriod : highestPeriodInData;
92
116
 
93
117
  return completedPeriods.length > 0
94
118
  ? {
@@ -116,7 +140,9 @@ export function mapNumberToSportPeriodType(sportTypeNum: number): SportPeriodTyp
116
140
  case 3:
117
141
  return SportPeriodType.PERIOD_BASED;
118
142
  default:
119
- throw new Error(`Invalid sport type number: ${sportTypeNum}. Must be 0 (halves), 1 (quarters), 2 (innings), or 3 (period).`);
143
+ throw new Error(
144
+ `Invalid sport type number: ${sportTypeNum}. Must be 0 (halves), 1 (quarters), 2 (innings), or 3 (period).`
145
+ );
120
146
  }
121
147
  }
122
148
 
@@ -153,11 +179,7 @@ function selectMappingForSportType(sportType: SportPeriodType): { [period: numbe
153
179
  * @param sportType - The sport period type enum
154
180
  * @returns true if the market can be resolved based on completed periods
155
181
  */
156
- export function canResolveMarketsForEvent(
157
- event: OpticOddsEvent,
158
- typeId: number,
159
- sportType: SportPeriodType
160
- ): boolean {
182
+ export function canResolveMarketsForEvent(event: OpticOddsEvent, typeId: number, sportType: SportPeriodType): boolean {
161
183
  const periodData = detectCompletedPeriods(event);
162
184
  if (!periodData) return false;
163
185
 
@@ -202,7 +224,6 @@ export function canResolveMarketsForEventNumber(
202
224
  return canResolveMarketsForEvent(event, typeId, sportTypeEnum);
203
225
  }
204
226
 
205
-
206
227
  /**
207
228
  * Checks if multiple market types can be resolved, returning a boolean for each
208
229
  *
@@ -0,0 +1,36 @@
1
+ import { OpticOddsEvent, SportPeriodType } from '../types/resolution';
2
+
3
+ /**
4
+ * Determines the period structure type for a sport based on OpticOdds event data
5
+ * @param event - OpticOdds event object containing sport information
6
+ * @returns SportPeriodType enum value indicating the period structure
7
+ */
8
+ export function getSportPeriodTypeFromEvent(event: OpticOddsEvent): SportPeriodType {
9
+ const sportId = event.sport?.id?.toLowerCase();
10
+
11
+ if (!sportId) {
12
+ // Default to PERIOD_BASED (no halftime processing) for unknown sports
13
+ return SportPeriodType.PERIOD_BASED;
14
+ }
15
+
16
+ // Map OpticOdds sport string ID to SportPeriodType
17
+ switch (sportId) {
18
+ case 'soccer':
19
+ return SportPeriodType.HALVES_BASED;
20
+
21
+ case 'football':
22
+ case 'basketball':
23
+ return SportPeriodType.QUARTERS_BASED;
24
+
25
+ case 'baseball':
26
+ return SportPeriodType.INNINGS_BASED;
27
+
28
+ case 'hockey':
29
+ case 'ice_hockey':
30
+ return SportPeriodType.PERIOD_BASED;
31
+
32
+ default:
33
+ // Default to PERIOD_BASED (no halftime processing) for unknown sports
34
+ return SportPeriodType.PERIOD_BASED;
35
+ }
36
+ }
package/tsconfig.json CHANGED
@@ -3,15 +3,11 @@
3
3
  "module": "esnext",
4
4
  "esModuleInterop": true,
5
5
  "target": "es5",
6
- "lib": [
7
- "dom",
8
- "dom.iterable",
9
- "esnext"
10
- ],
6
+ "lib": ["dom", "dom.iterable", "esnext"],
11
7
  "moduleResolution": "node",
12
8
  "sourceMap": true,
13
9
  "outDir": "build",
14
10
  "declaration": true,
15
- "strict": true,
11
+ "strict": true
16
12
  }
17
- }
13
+ }