overtime-live-trading-utils 2.1.42 → 2.1.44

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.
Files changed (39) hide show
  1. package/.circleci/config.yml +32 -32
  2. package/.prettierrc +9 -9
  3. package/codecov.yml +20 -20
  4. package/index.ts +26 -26
  5. package/jest.config.ts +16 -16
  6. package/main.js +1 -1
  7. package/package.json +30 -30
  8. package/src/constants/common.ts +7 -7
  9. package/src/constants/errors.ts +6 -6
  10. package/src/constants/sports.ts +78 -78
  11. package/src/enums/sports.ts +109 -109
  12. package/src/tests/mock/MockLeagueMap.ts +170 -170
  13. package/src/tests/mock/MockOpticOddsEvents.ts +662 -662
  14. package/src/tests/mock/MockOpticSoccer.ts +9378 -9378
  15. package/src/tests/mock/MockSoccerRedis.ts +2308 -2308
  16. package/src/tests/unit/bookmakers.test.ts +79 -79
  17. package/src/tests/unit/markets.test.ts +156 -156
  18. package/src/tests/unit/odds.test.ts +92 -92
  19. package/src/tests/unit/resolution.test.ts +1489 -1489
  20. package/src/tests/unit/sports.test.ts +58 -58
  21. package/src/tests/unit/spread.test.ts +131 -131
  22. package/src/types/missing-types.d.ts +2 -2
  23. package/src/types/odds.ts +61 -61
  24. package/src/types/resolution.ts +656 -656
  25. package/src/types/sports.ts +19 -19
  26. package/src/utils/bookmakers.ts +159 -159
  27. package/src/utils/constraints.ts +210 -210
  28. package/src/utils/gameMatching.ts +81 -81
  29. package/src/utils/markets.ts +119 -119
  30. package/src/utils/odds.ts +918 -918
  31. package/src/utils/opticOdds.ts +71 -71
  32. package/src/utils/resolution.ts +319 -320
  33. package/src/utils/sportPeriodMapping.ts +36 -36
  34. package/src/utils/sports.ts +51 -51
  35. package/src/utils/spread.ts +97 -97
  36. package/tsconfig.json +16 -16
  37. package/webpack.config.js +24 -24
  38. package/CLAUDE.md +0 -84
  39. package/resolution_live_markets.md +0 -356
@@ -1,356 +0,0 @@
1
- # Live Market Resolution - TypeId-Based Implementation Plan
2
-
3
- ## Overview
4
-
5
- Enhance the resolution utilities to support checking if specific market types (identified by `typeId`) can be resolved based on which periods are complete in a live game.
6
-
7
- ## Problem Statement
8
-
9
- The current `canResolveMarketForGameIdAndSport` function only checks if periods are complete, but doesn't answer the key question: **"Can I resolve market type 10021 (e.g., 1st Quarter Winner) right now?"**
10
-
11
- For live trading, we need to know which specific markets can be resolved based on:
12
-
13
- 1. Which periods have been completed
14
- 2. The market type (typeId)
15
- 3. Whether the game is still live or fully completed
16
-
17
- ## Understanding TypeIds
18
-
19
- TypeIds identify specific market types:
20
-
21
- - `10021` - 1st period/quarter/half markets (various bet types)
22
- - `10022` - 2nd period markets
23
- - `10031` - 1st period totals
24
- - `0`, `10001`, `10002` - Full game markets (should NOT resolve during live games)
25
-
26
- Example from codebase:
27
-
28
- ```typescript
29
- {
30
- sportId: 9806,
31
- typeId: 10022,
32
- marketName: '1st Half Moneyline',
33
- type: 'Moneyline',
34
- }
35
- ```
36
-
37
- ## Solution Design
38
-
39
- ### 1. Sport-Specific Period-to-TypeId Mappings
40
-
41
- Created three separate mappings for different sport period structures:
42
-
43
- ```typescript
44
- // Sport period structure types
45
- export enum SportPeriodType {
46
- HALVES_BASED = 'halves_based', // Soccer, NCAAB
47
- QUARTERS_BASED = 'quarters_based', // NFL, NBA
48
- INNINGS_BASED = 'innings_based', // MLB, NPB, KBO, College Baseball
49
- }
50
-
51
- // HALVES-BASED (Soccer, NCAAB): 2 periods
52
- export const HALVES_PERIOD_TYPE_ID_MAPPING = {
53
- 1: [10021, 10031, 10041, 10051, ...], // Period 1 = 1st half
54
- 2: [10022, 10032, 10042, 10052, ...], // Period 2 = 2nd half
55
- };
56
-
57
- // QUARTERS-BASED (NFL, NBA): 4 quarters + overtime
58
- export const QUARTERS_PERIOD_TYPE_ID_MAPPING = {
59
- 1: [10021, 10031, 10041, ...], // 1st quarter
60
- 2: [10022, 10032, 10042, 10051, ...], // 2nd quarter + 1st half (10051)
61
- 3: [10023, 10033, 10043, ...], // 3rd quarter
62
- 4: [10024, 10034, 10044, 10052, ...], // 4th quarter + 2nd half (10052)
63
- 5: [10025, 10035, 10045, ...], // Overtime
64
- };
65
-
66
- // INNINGS-BASED (MLB, NPB, KBO): 9+ innings
67
- export const INNINGS_PERIOD_TYPE_ID_MAPPING = {
68
- 1-4: [...], // Innings 1-4
69
- 5: [10025, 10035, 10045, 10051, ...], // 5th inning + 1st half (10051)
70
- 6-8: [...], // Innings 6-8
71
- 9: [10029, 10039, 10049, 10052, ...], // 9th inning + 2nd half (10052)
72
- };
73
-
74
- // Full game type IDs that should be skipped for live period resolution
75
- const FULL_GAME_TYPE_IDS = [0, 10001, 10002, 10003, 10004, 10010, 10011, 10012];
76
- ```
77
-
78
- **Key Insight**: TypeId 10051 (1st half) resolves at different periods depending on sport type:
79
-
80
- - Soccer (HALVES): Period 1
81
- - NFL (QUARTERS): Period 2 (after both quarters 1 & 2)
82
- - MLB (INNINGS): Period 5 (after first 5 innings)
83
-
84
- ### 2. Updated Function Signature
85
-
86
- **Before:**
87
-
88
- ```typescript
89
- canResolveMarketForGameIdAndSport(
90
- gameId: string,
91
- event: OpticOddsEvent,
92
- sportName?: string
93
- ): boolean
94
- ```
95
-
96
- **After:**
97
-
98
- ```typescript
99
- // Single typeId overload
100
- canResolveMarketsForEvent(
101
- event: OpticOddsEvent,
102
- typeId: number,
103
- sportType: SportPeriodType, // REQUIRED
104
- sportName?: string
105
- ): boolean
106
-
107
- // Batch typeIds overload
108
- canResolveMarketsForEvent(
109
- event: OpticOddsEvent,
110
- typeIds: number[],
111
- sportType: SportPeriodType, // REQUIRED
112
- sportName?: string
113
- ): number[]
114
- ```
115
-
116
- ### 3. Implementation Logic
117
-
118
- ```typescript
119
- // Helper function to select appropriate mapping based on sport type
120
- function selectMappingForSportType(sportType: SportPeriodType) {
121
- switch (sportType) {
122
- case SportPeriodType.HALVES_BASED:
123
- return HALVES_PERIOD_TYPE_ID_MAPPING;
124
- case SportPeriodType.QUARTERS_BASED:
125
- return QUARTERS_PERIOD_TYPE_ID_MAPPING;
126
- case SportPeriodType.INNINGS_BASED:
127
- return INNINGS_PERIOD_TYPE_ID_MAPPING;
128
- }
129
- }
130
-
131
- export function canResolveMarketsForEvent(
132
- event: OpticOddsEvent,
133
- typeIdOrTypeIds: number | number[],
134
- sportType: SportPeriodType, // REQUIRED - must specify sport type
135
- sportName?: string
136
- ): boolean | number[] {
137
- // Get completed periods
138
- const periodData = detectCompletedPeriods(event, sportName);
139
- if (!periodData) {
140
- return Array.isArray(typeIdOrTypeIds) ? [] : false;
141
- }
142
-
143
- // Check if game is fully completed
144
- const status = (event.fixture?.status || event.status || '').toLowerCase();
145
- const isCompleted = status === 'completed' || status === 'complete' || status === 'finished';
146
-
147
- // Select appropriate mapping based on sport type
148
- const mapping = selectMappingForSportType(sportType);
149
-
150
- // Collect all resolvable typeIds based on completed periods
151
- const resolvableTypeIds = new Set<number>();
152
-
153
- for (const period of periodData.completedPeriods) {
154
- const typeIdsForPeriod = mapping[period] || [];
155
- typeIdsForPeriod.forEach((id) => resolvableTypeIds.add(id));
156
- }
157
-
158
- // Full game typeIds can only be resolved when game is completed
159
- if (isCompleted) {
160
- // Could add full game typeIds to resolvable set here if needed
161
- // For now, they're explicitly excluded during live games
162
- }
163
-
164
- // Single typeId check
165
- if (typeId !== undefined) {
166
- // Full game typeIds cannot be resolved during live games
167
- if (!isCompleted && FULL_GAME_TYPE_IDS.includes(typeId)) {
168
- return false;
169
- }
170
- return resolvableTypeIds.has(typeId);
171
- }
172
-
173
- // Batch typeIds check
174
- if (typeIds !== undefined) {
175
- return typeIds.filter((id) => {
176
- // Exclude full game typeIds during live games
177
- if (!isCompleted && FULL_GAME_TYPE_IDS.includes(id)) {
178
- return false;
179
- }
180
- return resolvableTypeIds.has(id);
181
- });
182
- }
183
-
184
- return false;
185
- }
186
- ```
187
-
188
- ### 4. Why Sport Parameter is Needed
189
-
190
- Different sports have different period structures:
191
-
192
- - **Soccer**: 2 halves (periods 1-2)
193
- - **NFL/NBA**: 4 quarters (periods 1-4)
194
- - **MLB**: 9+ innings (periods 1-9)
195
- - **NHL**: 3 periods (periods 1-3)
196
- - **Tennis**: Sets (periods 1-5)
197
-
198
- The sport parameter allows future customization where period-to-typeId mappings could be sport-specific. For example:
199
-
200
- - Soccer might map period 1 → "1st Half" markets
201
- - Basketball might map period 1 → "1st Quarter" markets
202
- - Both use period 1, but represent different market types
203
-
204
- ## Implementation Steps
205
-
206
- ### Phase 1: Add Sport-Specific Mappings ✅ COMPLETED
207
-
208
- - [x] Add `SportPeriodType` enum to `src/types/resolution.ts`
209
- - [x] Add `HALVES_PERIOD_TYPE_ID_MAPPING` to `src/types/resolution.ts`
210
- - [x] Add `QUARTERS_PERIOD_TYPE_ID_MAPPING` to `src/types/resolution.ts`
211
- - [x] Add `INNINGS_PERIOD_TYPE_ID_MAPPING` to `src/types/resolution.ts`
212
- - [x] Add `FULL_GAME_TYPE_IDS` to `src/types/resolution.ts`
213
- - [x] Export all constants and enum
214
-
215
- ### Phase 2: Update Resolution Function ✅ COMPLETED
216
-
217
- - [x] Update `canResolveMarketsForEvent` function signature to accept `sportType` parameter
218
- - [x] Implement sport-type-based mapping selection logic
219
- - [x] Update function overloads for clean API (no undefined placeholders)
220
- - [x] Handle full game typeIds exclusion during live games
221
- - [x] Keep backward compatibility with existing functions
222
-
223
- ### Phase 3: Add Tests ✅ COMPLETED
224
-
225
- - [x] Test single typeId resolution
226
- - [x] Test batch typeIds resolution
227
- - [x] Test that full game typeIds are NOT resolved during live games
228
- - [x] Test with real event data (Soccer, NFL, MLB)
229
- - [x] Test edge cases (no completed periods, game in overtime, etc.)
230
- - [x] Add sport-specific tests for typeId 10051 (1st half) with HALVES, QUARTERS, INNINGS
231
- - [x] Add sport-specific tests for typeId 10052 (2nd half)
232
- - [x] Test default behavior (QUARTERS_BASED when no sportType provided)
233
-
234
- ### Phase 4: Update Exports ✅ COMPLETED
235
-
236
- - [x] Export `SportPeriodType` enum from index.ts
237
- - [x] Export `HALVES_PERIOD_TYPE_ID_MAPPING` from index.ts
238
- - [x] Export `QUARTERS_PERIOD_TYPE_ID_MAPPING` from index.ts
239
- - [x] Export `INNINGS_PERIOD_TYPE_ID_MAPPING` from index.ts
240
- - [x] Export `FULL_GAME_TYPE_IDS` from index.ts
241
- - [x] Keep existing exports for backward compatibility
242
-
243
- ### Phase 5: Testing & Validation ✅ COMPLETED
244
-
245
- - [x] Run full test suite
246
- - [x] Verify all 85 tests pass
247
- - [x] Test with real OpticOdds API responses (Soccer, NFL, MLB)
248
- - [x] Verify TypeScript compilation succeeds
249
-
250
- ## Example Usage
251
-
252
- ### Single TypeId Check
253
-
254
- ```typescript
255
- import { canResolveMarketsForEvent, SportPeriodType } from 'overtime-live-trading-utils';
256
-
257
- // Check NFL (quarters-based) - Can we resolve "1st Quarter Winner" market (typeId 10021)?
258
- const canResolve = canResolveMarketsForEvent(nflEvent, 10021, SportPeriodType.QUARTERS_BASED);
259
-
260
- if (canResolve) {
261
- // Resolve the 1st quarter market
262
- resolveMarket(10021);
263
- }
264
- ```
265
-
266
- ### Sport-Specific TypeId 10051 (1st Half) Resolution
267
-
268
- ```typescript
269
- // Soccer (halves-based): Resolves after period 1
270
- const soccerCanResolve = canResolveMarketsForEvent(soccerEvent, 10051, SportPeriodType.HALVES_BASED); // true if period 1 complete
271
-
272
- // NFL (quarters-based): Resolves after period 2
273
- const nflCanResolve = canResolveMarketsForEvent(nflEvent, 10051, SportPeriodType.QUARTERS_BASED); // true if period 2 complete
274
-
275
- // MLB (innings-based): Resolves after period 5
276
- const mlbCanResolve = canResolveMarketsForEvent(mlbEvent, 10051, SportPeriodType.INNINGS_BASED); // true if period 5 complete
277
- ```
278
-
279
- ### Batch TypeIds Check
280
-
281
- ```typescript
282
- // Which of these markets can we resolve right now for an NFL game?
283
- const marketTypeIds = [10021, 10022, 10031, 10001];
284
- const resolvableMarkets = canResolveMarketsForEvent(nflEvent, marketTypeIds, SportPeriodType.QUARTERS_BASED);
285
-
286
- // Returns: [10021, 10031] if only period 1 is complete
287
- // Full game typeId 10001 is excluded during live games
288
- resolvableMarkets.forEach((typeId) => resolveMarket(typeId));
289
- ```
290
-
291
- ### Function Overloads (TypeScript)
292
-
293
- The function uses TypeScript overloads for clean API:
294
-
295
- ```typescript
296
- // Single typeId → returns boolean
297
- function canResolveMarketsForEvent(
298
- event: OpticOddsEvent,
299
- typeId: number,
300
- sportType: SportPeriodType, // REQUIRED
301
- sportName?: string
302
- ): boolean;
303
-
304
- // Batch typeIds → returns number[]
305
- function canResolveMarketsForEvent(
306
- event: OpticOddsEvent,
307
- typeIds: number[],
308
- sportType: SportPeriodType, // REQUIRED
309
- sportName?: string
310
- ): number[];
311
- ```
312
-
313
- ## Testing Strategy
314
-
315
- ### Test Cases
316
-
317
- 1. **Period 1 Complete (Live Soccer 2nd Half)**
318
-
319
- - Input: Soccer event in 2nd half, period 1 complete
320
- - TypeIds: [10021, 10022, 10001]
321
- - Expected: [10021] (only 1st half markets resolvable)
322
-
323
- 2. **Periods 1-4 Complete (NFL Overtime)**
324
-
325
- - Input: NFL event in overtime, all 4 quarters complete
326
- - TypeIds: [10021, 10022, 10023, 10024, 10001]
327
- - Expected: [10021, 10022, 10023, 10024] (all quarter markets, but not full game)
328
-
329
- 3. **Game Completed**
330
-
331
- - Input: Completed game with all periods
332
- - TypeIds: [10021, 10001]
333
- - Expected: [10021, 10001] (all markets including full game)
334
-
335
- 4. **No Periods Complete**
336
- - Input: Live game in 1st period
337
- - TypeIds: [10021, 10022]
338
- - Expected: [] (no markets resolvable yet)
339
-
340
- ## Benefits
341
-
342
- 1. **Centralized Logic**: Single source of truth for market resolution rules
343
- 2. **Sport-Agnostic**: Works across all sports (Soccer, NFL, NBA, MLB, NHL, Tennis, etc.)
344
- 3. **Flexible**: Supports both single and batch typeId checks
345
- 4. **Safe**: Prevents resolving full game markets during live games
346
- 5. **Efficient**: O(1) lookup for period→typeId mapping
347
- 6. **Maintainable**: Easy to add new typeIds or modify mappings
348
- 7. **Testable**: Clear input/output for comprehensive testing
349
-
350
- ## Future Enhancements
351
-
352
- 1. **Sport-Specific Mappings**: Different PERIOD_TYPE_ID_MAPPING per sport if needed
353
- 2. **Dynamic Mapping**: Load period→typeId mappings from database/config
354
- 3. **Market Type Validation**: Verify typeIds exist before checking resolution
355
- 4. **Resolution Metadata**: Return additional info (which period completed, when, scores)
356
- 5. **Caching**: Cache resolvable typeIds per event to avoid recalculation