overtime-live-trading-utils 2.1.19 → 2.1.21

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