@gotza02/sequential-thinking 10000.0.8 → 10000.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.
@@ -0,0 +1,402 @@
1
+ /**
2
+ * ML PREDICTION ENGINE
3
+ * Machine learning based match outcome prediction
4
+ */
5
+ import { logger } from '../../../utils.js';
6
+ import { ML_CONFIG } from './constants.js';
7
+ /**
8
+ * ML Prediction Engine
9
+ * Uses weighted factors and historical patterns for predictions
10
+ */
11
+ export class MLPredictionEngine {
12
+ weights = ML_CONFIG.FEATURE_WEIGHTS;
13
+ historicalData = new Map();
14
+ constructor() {
15
+ this.loadHistoricalData();
16
+ }
17
+ /**
18
+ * Predict match outcome
19
+ */
20
+ async predict(input) {
21
+ const factors = [];
22
+ // Calculate form score
23
+ const formScore = this.calculateFormScore(input);
24
+ factors.push({
25
+ name: 'Recent Form',
26
+ weight: this.weights.form,
27
+ impact: formScore > 0 ? 'positive' : formScore < 0 ? 'negative' : 'neutral',
28
+ description: this.describeFormImpact(input),
29
+ });
30
+ // Calculate H2H score
31
+ const h2hScore = this.calculateH2HScore(input);
32
+ factors.push({
33
+ name: 'Head-to-Head',
34
+ weight: this.weights.h2h,
35
+ impact: h2hScore > 0 ? 'positive' : h2hScore < 0 ? 'negative' : 'neutral',
36
+ description: this.describeH2HImpact(input),
37
+ });
38
+ // Calculate home advantage
39
+ const homeAdvantageScore = this.calculateHomeAdvantage(input);
40
+ factors.push({
41
+ name: 'Home Advantage',
42
+ weight: this.weights.home_advantage,
43
+ impact: homeAdvantageScore > 0 ? 'positive' : 'neutral',
44
+ description: `Home advantage factor: ${(homeAdvantageScore * 100).toFixed(1)}%`,
45
+ });
46
+ // Calculate xG score
47
+ const xgScore = this.calculateXgScore(input);
48
+ factors.push({
49
+ name: 'Expected Goals (xG)',
50
+ weight: this.weights.xg,
51
+ impact: xgScore > 0 ? 'positive' : xgScore < 0 ? 'negative' : 'neutral',
52
+ description: `xG differential: ${(xgScore * 100).toFixed(1)}%`,
53
+ });
54
+ // Calculate injury impact
55
+ const injuryScore = this.calculateInjuryImpact(input);
56
+ factors.push({
57
+ name: 'Injuries',
58
+ weight: this.weights.injuries,
59
+ impact: injuryScore > 0 ? 'positive' : injuryScore < 0 ? 'negative' : 'neutral',
60
+ description: this.describeInjuryImpact(input),
61
+ });
62
+ // Calculate fatigue impact
63
+ const fatigueScore = this.calculateFatigueImpact(input);
64
+ factors.push({
65
+ name: 'Fatigue',
66
+ weight: this.weights.fatigue,
67
+ impact: fatigueScore > 0 ? 'positive' : fatigueScore < 0 ? 'negative' : 'neutral',
68
+ description: `Fatigue differential: ${(fatigueScore * 100).toFixed(1)}%`,
69
+ });
70
+ // Weather impact
71
+ const weatherScore = this.calculateWeatherImpact(input);
72
+ factors.push({
73
+ name: 'Weather',
74
+ weight: this.weights.weather,
75
+ impact: weatherScore !== 0 ? (weatherScore > 0 ? 'positive' : 'negative') : 'neutral',
76
+ description: this.describeWeatherImpact(input),
77
+ });
78
+ // Combine all factors
79
+ const combinedScore = this.combineScores([
80
+ { score: formScore, weight: this.weights.form },
81
+ { score: h2hScore, weight: this.weights.h2h },
82
+ { score: homeAdvantageScore, weight: this.weights.home_advantage },
83
+ { score: xgScore, weight: this.weights.xg },
84
+ { score: injuryScore, weight: this.weights.injuries },
85
+ { score: fatigueScore, weight: this.weights.fatigue },
86
+ { score: weatherScore, weight: this.weights.weather },
87
+ ]);
88
+ // Calculate probabilities
89
+ const probabilities = this.scoreToProbabilities(combinedScore);
90
+ // Calculate confidence
91
+ const confidence = this.calculateConfidence(factors, input);
92
+ // Calculate additional markets
93
+ const over25Prob = this.predictOver25(input);
94
+ const bttsProb = this.predictBTTS(input);
95
+ logger.info(`[MLPrediction] ${input.match.homeTeam.name} vs ${input.match.awayTeam.name}: ` +
96
+ `H:${(probabilities.home * 100).toFixed(1)}% D:${(probabilities.draw * 100).toFixed(1)}% A:${(probabilities.away * 100).toFixed(1)}% ` +
97
+ `Confidence: ${confidence.toFixed(1)}%`);
98
+ return {
99
+ homeWin: probabilities.home,
100
+ draw: probabilities.draw,
101
+ awayWin: probabilities.away,
102
+ over25: over25Prob,
103
+ btts: bttsProb,
104
+ confidence,
105
+ factors,
106
+ };
107
+ }
108
+ /**
109
+ * Calculate form score (-1 to 1)
110
+ */
111
+ calculateFormScore(input) {
112
+ const homePoints = this.formToPoints(input.homeForm.last5);
113
+ const awayPoints = this.formToPoints(input.awayForm.last5);
114
+ // Normalize to -1 to 1
115
+ const maxPoints = 15; // 5 wins = 15 points
116
+ const homeNormalized = (homePoints / maxPoints) * 2 - 1;
117
+ const awayNormalized = (awayPoints / maxPoints) * 2 - 1;
118
+ return homeNormalized - awayNormalized;
119
+ }
120
+ formToPoints(form) {
121
+ return form.reduce((sum, result) => {
122
+ if (result === 'W')
123
+ return sum + 3;
124
+ if (result === 'D')
125
+ return sum + 1;
126
+ return sum;
127
+ }, 0);
128
+ }
129
+ /**
130
+ * Calculate H2H score (-1 to 1)
131
+ */
132
+ calculateH2HScore(input) {
133
+ const h2h = input.h2h;
134
+ const total = h2h.totalMatches;
135
+ if (total === 0)
136
+ return 0;
137
+ const homeWinRate = h2h.homeWins / total;
138
+ const awayWinRate = h2h.awayWins / total;
139
+ // Weight recent matches more heavily
140
+ const recentMatches = h2h.recentMatches.slice(0, 5);
141
+ let recentScore = 0;
142
+ recentMatches.forEach((match, index) => {
143
+ const weight = (5 - index) / 5; // More recent = higher weight
144
+ if (match.score) {
145
+ if (match.score.home > match.score.away)
146
+ recentScore += weight;
147
+ else if (match.score.home < match.score.away)
148
+ recentScore -= weight;
149
+ }
150
+ });
151
+ // Combine historical and recent
152
+ const historicalScore = homeWinRate - awayWinRate;
153
+ const combinedScore = (historicalScore * 0.4) + (recentScore / 5 * 0.6);
154
+ return Math.max(-1, Math.min(1, combinedScore));
155
+ }
156
+ /**
157
+ * Calculate home advantage score (0 to 1)
158
+ */
159
+ calculateHomeAdvantage(input) {
160
+ const homeStats = input.homeStats;
161
+ const awayStats = input.awayStats;
162
+ if (!homeStats?.homeStats || !awayStats?.awayStats) {
163
+ return 0.1; // Default home advantage
164
+ }
165
+ const homeWinRate = homeStats.homeStats.wins / (homeStats.homeStats.matchesPlayed || 1);
166
+ const awayWinRate = awayStats.awayStats.wins / (awayStats.awayStats.matchesPlayed || 1);
167
+ return Math.max(0, homeWinRate - awayWinRate);
168
+ }
169
+ /**
170
+ * Calculate xG score (-1 to 1)
171
+ */
172
+ calculateXgScore(input) {
173
+ const homeXg = input.homeForm.xG;
174
+ const homeXga = input.homeForm.xGA;
175
+ const awayXg = input.awayForm.xG;
176
+ const awayXga = input.awayForm.xGA;
177
+ // xG differential (attack - defense)
178
+ const homeXgDiff = homeXg - homeXga;
179
+ const awayXgDiff = awayXg - awayXga;
180
+ // Normalize (typical range is -1 to 1 per game)
181
+ const normalized = (homeXgDiff - awayXgDiff) / 2;
182
+ return Math.max(-1, Math.min(1, normalized));
183
+ }
184
+ /**
185
+ * Calculate injury impact (-1 to 1)
186
+ */
187
+ calculateInjuryImpact(input) {
188
+ const homeKeyInjuries = input.injuries.home.keyPlayers;
189
+ const awayKeyInjuries = input.injuries.away.keyPlayers;
190
+ // Each key player injury is worth ~0.1
191
+ const impact = (awayKeyInjuries - homeKeyInjuries) * 0.1;
192
+ return Math.max(-1, Math.min(1, impact));
193
+ }
194
+ /**
195
+ * Calculate fatigue impact (-1 to 1)
196
+ */
197
+ calculateFatigueImpact(input) {
198
+ // This would need actual fatigue data
199
+ // For now, use matches played as proxy
200
+ const homeMatches = input.homeStats.matchesPlayed;
201
+ const awayMatches = input.awayStats.matchesPlayed;
202
+ // More matches = more fatigue
203
+ const diff = awayMatches - homeMatches;
204
+ const impact = diff * 0.02; // Each match difference = 2%
205
+ return Math.max(-1, Math.min(1, impact));
206
+ }
207
+ /**
208
+ * Calculate weather impact (-0.5 to 0.5)
209
+ */
210
+ calculateWeatherImpact(input) {
211
+ if (!input.weather)
212
+ return 0;
213
+ const weather = input.weather;
214
+ let impact = 0;
215
+ // Rain favors defensive teams
216
+ if (weather.precipitation) {
217
+ impact -= 0.1;
218
+ }
219
+ // High wind affects passing teams
220
+ if (weather.windSpeed > 20) {
221
+ impact -= 0.1;
222
+ }
223
+ // Extreme temperatures
224
+ if (weather.temperature > 30 || weather.temperature < 0) {
225
+ impact -= 0.05;
226
+ }
227
+ return impact;
228
+ }
229
+ /**
230
+ * Combine weighted scores
231
+ */
232
+ combineScores(scores) {
233
+ const totalWeight = scores.reduce((sum, s) => sum + s.weight, 0);
234
+ const weightedSum = scores.reduce((sum, s) => sum + (s.score * s.weight), 0);
235
+ return weightedSum / totalWeight;
236
+ }
237
+ /**
238
+ * Convert score to probabilities
239
+ */
240
+ scoreToProbabilities(score) {
241
+ // Score is from -1 (away advantage) to 1 (home advantage)
242
+ // Convert to probabilities using softmax-like function
243
+ const homeExp = Math.exp(score * 2);
244
+ const drawExp = Math.exp(0); // Neutral
245
+ const awayExp = Math.exp(-score * 2);
246
+ const total = homeExp + drawExp + awayExp;
247
+ return {
248
+ home: homeExp / total,
249
+ draw: drawExp / total,
250
+ away: awayExp / total,
251
+ };
252
+ }
253
+ /**
254
+ * Calculate confidence level
255
+ */
256
+ calculateConfidence(factors, input) {
257
+ let confidence = 50; // Base confidence
258
+ // More data = higher confidence
259
+ if (input.homeForm.last5.length >= 5)
260
+ confidence += 10;
261
+ if (input.awayForm.last5.length >= 5)
262
+ confidence += 10;
263
+ if (input.h2h.totalMatches >= 3)
264
+ confidence += 10;
265
+ // High impact factors increase confidence
266
+ const highImpactFactors = factors.filter(f => Math.abs(f.weight) > 0.15 && f.impact !== 'neutral').length;
267
+ confidence += highImpactFactors * 5;
268
+ // Cap at 95%
269
+ return Math.min(95, confidence);
270
+ }
271
+ /**
272
+ * Predict Over 2.5 goals
273
+ */
274
+ predictOver25(input) {
275
+ const homeXg = input.homeForm.xG / 5; // per game
276
+ const awayXg = input.awayForm.xG / 5;
277
+ const homeXga = input.homeForm.xGA / 5;
278
+ const awayXga = input.awayForm.xGA / 5;
279
+ const expectedGoals = (homeXg + awayXga + awayXg + homeXga) / 2;
280
+ // Convert to probability
281
+ return Math.min(0.9, Math.max(0.1, expectedGoals / 3));
282
+ }
283
+ /**
284
+ * Predict Both Teams to Score
285
+ */
286
+ predictBTTS(input) {
287
+ const homeScoring = input.homeForm.goalsFor / 5;
288
+ const awayScoring = input.awayForm.goalsFor / 5;
289
+ const homeConceding = input.homeForm.goalsAgainst / 5;
290
+ const awayConceding = input.awayForm.goalsAgainst / 5;
291
+ const homeLikelyToScore = homeScoring > 1 ? 0.7 : homeScoring > 0.5 ? 0.5 : 0.3;
292
+ const awayLikelyToScore = awayScoring > 1 ? 0.7 : awayScoring > 0.5 ? 0.5 : 0.3;
293
+ return homeLikelyToScore * awayLikelyToScore;
294
+ }
295
+ /**
296
+ * Describe form impact
297
+ */
298
+ describeFormImpact(input) {
299
+ const homePoints = this.formToPoints(input.homeForm.last5);
300
+ const awayPoints = this.formToPoints(input.awayForm.last5);
301
+ if (homePoints > awayPoints + 4) {
302
+ return `${input.match.homeTeam.name} in significantly better form`;
303
+ }
304
+ else if (awayPoints > homePoints + 4) {
305
+ return `${input.match.awayTeam.name} in significantly better form`;
306
+ }
307
+ else if (Math.abs(homePoints - awayPoints) <= 2) {
308
+ return 'Teams in similar form';
309
+ }
310
+ return 'Form advantage detected';
311
+ }
312
+ /**
313
+ * Describe H2H impact
314
+ */
315
+ describeH2HImpact(input) {
316
+ const h2h = input.h2h;
317
+ if (h2h.totalMatches === 0) {
318
+ return 'No recent head-to-head data';
319
+ }
320
+ const homeWinRate = (h2h.homeWins / h2h.totalMatches * 100).toFixed(0);
321
+ return `${input.match.homeTeam.name} won ${homeWinRate}% of ${h2h.totalMatches} meetings`;
322
+ }
323
+ /**
324
+ * Describe injury impact
325
+ */
326
+ describeInjuryImpact(input) {
327
+ const homeKey = input.injuries.home.keyPlayers;
328
+ const awayKey = input.injuries.away.keyPlayers;
329
+ if (homeKey === 0 && awayKey === 0) {
330
+ return 'No key injuries for either team';
331
+ }
332
+ else if (homeKey > awayKey) {
333
+ return `${input.match.homeTeam.name} has ${homeKey} key player(s) injured`;
334
+ }
335
+ else if (awayKey > homeKey) {
336
+ return `${input.match.awayTeam.name} has ${awayKey} key player(s) injured`;
337
+ }
338
+ return 'Both teams have similar injury concerns';
339
+ }
340
+ /**
341
+ * Describe weather impact
342
+ */
343
+ describeWeatherImpact(input) {
344
+ if (!input.weather)
345
+ return 'Weather data not available';
346
+ const conditions = [];
347
+ if (input.weather.precipitation)
348
+ conditions.push('rain');
349
+ if (input.weather.windSpeed > 20)
350
+ conditions.push('high wind');
351
+ if (input.weather.temperature > 30)
352
+ conditions.push('hot');
353
+ if (input.weather.temperature < 5)
354
+ conditions.push('cold');
355
+ if (conditions.length === 0) {
356
+ return 'Good weather conditions';
357
+ }
358
+ return `Adverse conditions: ${conditions.join(', ')}`;
359
+ }
360
+ /**
361
+ * Load historical data
362
+ */
363
+ loadHistoricalData() {
364
+ // Would load from database or file
365
+ // For now, start empty
366
+ }
367
+ /**
368
+ * Learn from past predictions
369
+ */
370
+ learnFromResult(prediction, actualResult, odds) {
371
+ // Update weights based on accuracy
372
+ // This is a simplified version - real ML would use gradient descent
373
+ const predictedOutcome = prediction.homeWin > prediction.draw && prediction.homeWin > prediction.awayWin
374
+ ? 'home'
375
+ : prediction.awayWin > prediction.draw
376
+ ? 'away'
377
+ : 'draw';
378
+ const wasCorrect = predictedOutcome === actualResult;
379
+ if (wasCorrect) {
380
+ logger.info(`[MLPrediction] Correct prediction at ${odds}x`);
381
+ }
382
+ else {
383
+ logger.info(`[MLPrediction] Incorrect prediction. Predicted: ${predictedOutcome}, Actual: ${actualResult}`);
384
+ }
385
+ // Store for analysis
386
+ this.historicalData.set(`result-${Date.now()}`, {
387
+ prediction,
388
+ actualResult,
389
+ odds,
390
+ wasCorrect,
391
+ timestamp: Date.now(),
392
+ });
393
+ }
394
+ }
395
+ // Singleton instance
396
+ let globalPredictionEngine = null;
397
+ export function getPredictionEngine() {
398
+ if (!globalPredictionEngine) {
399
+ globalPredictionEngine = new MLPredictionEngine();
400
+ }
401
+ return globalPredictionEngine;
402
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * REALTIME DATA MANAGER
3
+ * WebSocket/SSE based real-time data streaming with fallback polling
4
+ */
5
+ import { EventEmitter } from 'events';
6
+ import type { Match } from './types.js';
7
+ export interface LiveEvent {
8
+ type: 'goal' | 'card' | 'substitution' | 'var' | 'injury' | 'whistle' | 'odds_change' | 'status_change';
9
+ matchId: string;
10
+ timestamp: number;
11
+ minute?: number;
12
+ data: any;
13
+ }
14
+ export interface OddsChangeEvent extends LiveEvent {
15
+ type: 'odds_change';
16
+ data: {
17
+ bookmaker: string;
18
+ market: string;
19
+ selection: string;
20
+ oldOdds: number;
21
+ newOdds: number;
22
+ change: number;
23
+ timestamp: number;
24
+ };
25
+ }
26
+ export declare class RealtimeDataManager extends EventEmitter {
27
+ private cache;
28
+ private wsConnections;
29
+ private pollingIntervals;
30
+ private reconnectAttempts;
31
+ private isRunning;
32
+ constructor();
33
+ /**
34
+ * Start the realtime manager
35
+ */
36
+ start(): Promise<void>;
37
+ /**
38
+ * Stop the realtime manager
39
+ */
40
+ stop(): void;
41
+ /**
42
+ * Subscribe to a specific match's events
43
+ */
44
+ subscribeToMatch(matchId: string, callback: (event: LiveEvent) => void): () => void;
45
+ /**
46
+ * Subscribe to odds changes for a match
47
+ */
48
+ subscribeToOdds(matchId: string, callback: (event: OddsChangeEvent) => void): () => void;
49
+ /**
50
+ * Subscribe to all live events
51
+ */
52
+ subscribeToAllEvents(callback: (event: LiveEvent) => void): () => void;
53
+ /**
54
+ * Process and broadcast an event
55
+ */
56
+ processEvent(event: LiveEvent): void;
57
+ /**
58
+ * Get current live matches
59
+ */
60
+ getLiveMatches(): Match[];
61
+ /**
62
+ * Get match events history
63
+ */
64
+ getMatchEvents(matchId: string): LiveEvent[];
65
+ /**
66
+ * Connect to match stream (WebSocket or polling)
67
+ */
68
+ private connectToMatchStream;
69
+ /**
70
+ * Unsubscribe from a match
71
+ */
72
+ private unsubscribeFromMatch;
73
+ /**
74
+ * Start polling for live scores
75
+ */
76
+ private startLiveScoresPolling;
77
+ /**
78
+ * Start polling for odds
79
+ */
80
+ private startOddsPolling;
81
+ /**
82
+ * Start polling for specific match events
83
+ */
84
+ private startMatchEventsPolling;
85
+ /**
86
+ * Detect score changes and emit goal events
87
+ */
88
+ private detectScoreChanges;
89
+ /**
90
+ * Detect odds changes and emit events
91
+ */
92
+ private detectOddsChanges;
93
+ /**
94
+ * Update cache with event
95
+ */
96
+ private updateCache;
97
+ private fetchLiveScores;
98
+ private fetchOdds;
99
+ private fetchMatchEvents;
100
+ }
101
+ export declare function getRealtimeManager(): RealtimeDataManager;
102
+ export declare function resetRealtimeManager(): void;