@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.
- package/README.md +249 -39
- package/dist/dashboard/server.d.ts +79 -2
- package/dist/dashboard/server.js +466 -61
- package/dist/index.js +1 -4
- package/dist/tools/sports/core/alert-manager.d.ts +145 -0
- package/dist/tools/sports/core/alert-manager.js +380 -0
- package/dist/tools/sports/core/cache.d.ts +19 -8
- package/dist/tools/sports/core/cache.js +95 -38
- package/dist/tools/sports/core/circuit-breaker.d.ts +40 -0
- package/dist/tools/sports/core/circuit-breaker.js +99 -0
- package/dist/tools/sports/core/constants.d.ts +63 -4
- package/dist/tools/sports/core/constants.js +86 -11
- package/dist/tools/sports/core/data-quality.d.ts +80 -0
- package/dist/tools/sports/core/data-quality.js +460 -0
- package/dist/tools/sports/core/historical-analyzer.d.ts +108 -0
- package/dist/tools/sports/core/historical-analyzer.js +461 -0
- package/dist/tools/sports/core/index.d.ts +13 -0
- package/dist/tools/sports/core/index.js +16 -0
- package/dist/tools/sports/core/ml-prediction.d.ts +134 -0
- package/dist/tools/sports/core/ml-prediction.js +402 -0
- package/dist/tools/sports/core/realtime-manager.d.ts +102 -0
- package/dist/tools/sports/core/realtime-manager.js +331 -0
- package/dist/tools/sports/core/retry.d.ts +29 -0
- package/dist/tools/sports/core/retry.js +77 -0
- package/dist/tools/sports/core/types.d.ts +40 -1
- package/package.json +1 -1
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HISTORICAL ANALYZER
|
|
3
|
+
* Analyzes historical data for patterns and trends
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Historical Analyzer
|
|
7
|
+
* Analyzes past matches for patterns and predictive insights
|
|
8
|
+
*/
|
|
9
|
+
export class HistoricalAnalyzer {
|
|
10
|
+
matchHistory = [];
|
|
11
|
+
patterns = new Map();
|
|
12
|
+
constructor() {
|
|
13
|
+
this.loadHistoricalData();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Analyze team performance over time
|
|
17
|
+
*/
|
|
18
|
+
analyzeTeamPerformance(teamId, query) {
|
|
19
|
+
const matches = this.getTeamMatches(teamId, query);
|
|
20
|
+
if (matches.length === 0) {
|
|
21
|
+
return this.createEmptyPerformance(teamId);
|
|
22
|
+
}
|
|
23
|
+
const wins = matches.filter(m => this.isWin(m, teamId)).length;
|
|
24
|
+
const draws = matches.filter(m => this.isDraw(m)).length;
|
|
25
|
+
const losses = matches.filter(m => this.isLoss(m, teamId)).length;
|
|
26
|
+
const totalGoalsFor = matches.reduce((sum, m) => sum + this.getGoalsFor(m, teamId), 0);
|
|
27
|
+
const totalGoalsAgainst = matches.reduce((sum, m) => sum + this.getGoalsAgainst(m, teamId), 0);
|
|
28
|
+
const patterns = this.findPatterns(teamId, matches);
|
|
29
|
+
return {
|
|
30
|
+
team: matches[0].homeTeam.id === teamId ? matches[0].homeTeam : matches[0].awayTeam,
|
|
31
|
+
totalMatches: matches.length,
|
|
32
|
+
wins,
|
|
33
|
+
draws,
|
|
34
|
+
losses,
|
|
35
|
+
avgGoalsFor: totalGoalsFor / matches.length,
|
|
36
|
+
avgGoalsAgainst: totalGoalsAgainst / matches.length,
|
|
37
|
+
homeAdvantage: this.calculateHomeAdvantage(teamId, matches),
|
|
38
|
+
patterns,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Analyze head-to-head history
|
|
43
|
+
*/
|
|
44
|
+
analyzeHeadToHead(teamAId, teamBId, limit = 10) {
|
|
45
|
+
const matches = this.getH2HMatches(teamAId, teamBId, limit);
|
|
46
|
+
let homeWins = 0;
|
|
47
|
+
let awayWins = 0;
|
|
48
|
+
let draws = 0;
|
|
49
|
+
let homeGoals = 0;
|
|
50
|
+
let awayGoals = 0;
|
|
51
|
+
matches.forEach(match => {
|
|
52
|
+
if (!match.score)
|
|
53
|
+
return;
|
|
54
|
+
const isTeamAHome = match.homeTeam.id === teamAId;
|
|
55
|
+
if (match.score.home > match.score.away) {
|
|
56
|
+
if (isTeamAHome)
|
|
57
|
+
homeWins++;
|
|
58
|
+
else
|
|
59
|
+
awayWins++;
|
|
60
|
+
}
|
|
61
|
+
else if (match.score.home < match.score.away) {
|
|
62
|
+
if (isTeamAHome)
|
|
63
|
+
awayWins++;
|
|
64
|
+
else
|
|
65
|
+
homeWins++;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
draws++;
|
|
69
|
+
}
|
|
70
|
+
homeGoals += isTeamAHome ? match.score.home : match.score.away;
|
|
71
|
+
awayGoals += isTeamAHome ? match.score.away : match.score.home;
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
homeTeam: matches[0]?.homeTeam || { id: teamAId, name: '', country: '' },
|
|
75
|
+
awayTeam: matches[0]?.awayTeam || { id: teamBId, name: '', country: '' },
|
|
76
|
+
totalMatches: matches.length,
|
|
77
|
+
homeWins,
|
|
78
|
+
draws,
|
|
79
|
+
awayWins,
|
|
80
|
+
homeGoals,
|
|
81
|
+
awayGoals,
|
|
82
|
+
recentMatches: matches.slice(0, 5),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Analyze trends for a team
|
|
87
|
+
*/
|
|
88
|
+
analyzeTrends(teamId, windowSize = 5) {
|
|
89
|
+
const matches = this.getTeamMatches(teamId).slice(0, windowSize);
|
|
90
|
+
if (matches.length < 3) {
|
|
91
|
+
return {
|
|
92
|
+
direction: 'stable',
|
|
93
|
+
strength: 0,
|
|
94
|
+
confidence: 0,
|
|
95
|
+
dataPoints: matches.length,
|
|
96
|
+
description: 'Insufficient data for trend analysis',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Calculate performance in recent matches
|
|
100
|
+
const recentPerformance = matches.slice(0, Math.ceil(windowSize / 2)).map(m => this.getMatchPoints(m, teamId));
|
|
101
|
+
const olderPerformance = matches.slice(Math.ceil(windowSize / 2)).map(m => this.getMatchPoints(m, teamId));
|
|
102
|
+
const recentAvg = recentPerformance.reduce((a, b) => a + b, 0) / recentPerformance.length;
|
|
103
|
+
const olderAvg = olderPerformance.reduce((a, b) => a + b, 0) / olderPerformance.length;
|
|
104
|
+
const difference = recentAvg - olderAvg;
|
|
105
|
+
const strength = Math.min(1, Math.abs(difference) / 2);
|
|
106
|
+
let direction;
|
|
107
|
+
if (difference > 0.5)
|
|
108
|
+
direction = 'up';
|
|
109
|
+
else if (difference < -0.5)
|
|
110
|
+
direction = 'down';
|
|
111
|
+
else
|
|
112
|
+
direction = 'stable';
|
|
113
|
+
const description = direction === 'up'
|
|
114
|
+
? `Team improving: ${recentAvg.toFixed(2)} pts/game recently vs ${olderAvg.toFixed(2)} before`
|
|
115
|
+
: direction === 'down'
|
|
116
|
+
? `Team declining: ${recentAvg.toFixed(2)} pts/game recently vs ${olderAvg.toFixed(2)} before`
|
|
117
|
+
: `Team stable: ${recentAvg.toFixed(2)} pts/game`;
|
|
118
|
+
return {
|
|
119
|
+
direction,
|
|
120
|
+
strength,
|
|
121
|
+
confidence: Math.min(100, matches.length * 10),
|
|
122
|
+
dataPoints: matches.length,
|
|
123
|
+
description,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Calculate performance metrics
|
|
128
|
+
*/
|
|
129
|
+
calculateMetrics(teamId, query) {
|
|
130
|
+
const matches = this.getTeamMatches(teamId, query);
|
|
131
|
+
if (matches.length === 0) {
|
|
132
|
+
return {
|
|
133
|
+
winRate: 0,
|
|
134
|
+
drawRate: 0,
|
|
135
|
+
lossRate: 0,
|
|
136
|
+
avgGoalsFor: 0,
|
|
137
|
+
avgGoalsAgainst: 0,
|
|
138
|
+
cleanSheetRate: 0,
|
|
139
|
+
bothTeamsScoreRate: 0,
|
|
140
|
+
over25Rate: 0,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const wins = matches.filter(m => this.isWin(m, teamId)).length;
|
|
144
|
+
const draws = matches.filter(m => this.isDraw(m)).length;
|
|
145
|
+
const losses = matches.filter(m => this.isLoss(m, teamId)).length;
|
|
146
|
+
const totalGoalsFor = matches.reduce((sum, m) => sum + this.getGoalsFor(m, teamId), 0);
|
|
147
|
+
const totalGoalsAgainst = matches.reduce((sum, m) => sum + this.getGoalsAgainst(m, teamId), 0);
|
|
148
|
+
const cleanSheets = matches.filter(m => this.getGoalsAgainst(m, teamId) === 0).length;
|
|
149
|
+
const btts = matches.filter(m => this.getGoalsFor(m, teamId) > 0 && this.getGoalsAgainst(m, teamId) > 0).length;
|
|
150
|
+
const over25 = matches.filter(m => {
|
|
151
|
+
const total = this.getGoalsFor(m, teamId) + this.getGoalsAgainst(m, teamId);
|
|
152
|
+
return total > 2.5;
|
|
153
|
+
}).length;
|
|
154
|
+
return {
|
|
155
|
+
winRate: wins / matches.length,
|
|
156
|
+
drawRate: draws / matches.length,
|
|
157
|
+
lossRate: losses / matches.length,
|
|
158
|
+
avgGoalsFor: totalGoalsFor / matches.length,
|
|
159
|
+
avgGoalsAgainst: totalGoalsAgainst / matches.length,
|
|
160
|
+
cleanSheetRate: cleanSheets / matches.length,
|
|
161
|
+
bothTeamsScoreRate: btts / matches.length,
|
|
162
|
+
over25Rate: over25 / matches.length,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Find betting patterns
|
|
167
|
+
*/
|
|
168
|
+
findBettingPatterns(query) {
|
|
169
|
+
const patterns = [];
|
|
170
|
+
const matches = this.queryMatches(query);
|
|
171
|
+
// Pattern: Home favorites
|
|
172
|
+
const homeFavorites = matches.filter(m => m.odds && m.odds.homeWin < 1.5 && m.score && m.score.home > m.score.away);
|
|
173
|
+
if (homeFavorites.length >= 5) {
|
|
174
|
+
patterns.push({
|
|
175
|
+
pattern: 'Home favorites win',
|
|
176
|
+
occurrence: homeFavorites.length,
|
|
177
|
+
successRate: homeFavorites.length / matches.filter(m => m.odds?.homeWin < 1.5).length,
|
|
178
|
+
avgOdds: homeFavorites.reduce((sum, m) => sum + (m.odds?.homeWin || 0), 0) / homeFavorites.length,
|
|
179
|
+
profit: this.calculateProfit(homeFavorites, 'home'),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
// Pattern: Under 2.5 in low-scoring leagues
|
|
183
|
+
const lowScoringMatches = matches.filter(m => {
|
|
184
|
+
const total = (m.score?.home || 0) + (m.score?.away || 0);
|
|
185
|
+
return total < 2.5;
|
|
186
|
+
});
|
|
187
|
+
if (lowScoringMatches.length >= 10) {
|
|
188
|
+
patterns.push({
|
|
189
|
+
pattern: 'Under 2.5 goals',
|
|
190
|
+
occurrence: lowScoringMatches.length,
|
|
191
|
+
successRate: lowScoringMatches.length / matches.length,
|
|
192
|
+
avgOdds: 1.8, // Typical under 2.5 odds
|
|
193
|
+
profit: (lowScoringMatches.length * 0.8 - (matches.length - lowScoringMatches.length)) * 10,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
// Pattern: BTTS
|
|
197
|
+
const bttsMatches = matches.filter(m => (m.score?.home || 0) > 0 && (m.score?.away || 0) > 0);
|
|
198
|
+
if (bttsMatches.length >= 10) {
|
|
199
|
+
patterns.push({
|
|
200
|
+
pattern: 'Both teams score',
|
|
201
|
+
occurrence: bttsMatches.length,
|
|
202
|
+
successRate: bttsMatches.length / matches.length,
|
|
203
|
+
avgOdds: 1.9, // Typical BTTS odds
|
|
204
|
+
profit: (bttsMatches.length * 0.9 - (matches.length - bttsMatches.length)) * 10,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return patterns.sort((a, b) => b.profit - a.profit);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Analyze referee bias
|
|
211
|
+
*/
|
|
212
|
+
analyzeRefereeBias(refereeName, teamId) {
|
|
213
|
+
const matches = this.matchHistory.filter(m => m.referee?.name === refereeName && m.status === 'finished');
|
|
214
|
+
const homeWins = matches.filter(m => m.score && m.score.home > m.score.away).length;
|
|
215
|
+
// Calculate team bias if teamId provided
|
|
216
|
+
let teamBias;
|
|
217
|
+
if (teamId) {
|
|
218
|
+
const teamMatches = matches.filter(m => m.homeTeam.id === teamId || m.awayTeam.id === teamId);
|
|
219
|
+
const teamWins = teamMatches.filter(m => this.isWin(m, teamId)).length;
|
|
220
|
+
teamBias = teamWins / (teamMatches.length || 1);
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
referee: refereeName,
|
|
224
|
+
matches: matches.length,
|
|
225
|
+
avgCards: 3.5, // Placeholder
|
|
226
|
+
homeWinRate: homeWins / (matches.length || 1),
|
|
227
|
+
teamBias,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Analyze weather impact
|
|
232
|
+
*/
|
|
233
|
+
analyzeWeatherImpact(condition, teamId) {
|
|
234
|
+
const matches = this.matchHistory.filter(m => m.weather?.condition === condition && m.status === 'finished');
|
|
235
|
+
const totalGoals = matches.reduce((sum, m) => sum + (m.score?.home || 0) + (m.score?.away || 0), 0);
|
|
236
|
+
const homeWins = matches.filter(m => m.score && m.score.home > m.score.away).length;
|
|
237
|
+
let teamPerformance;
|
|
238
|
+
if (teamId) {
|
|
239
|
+
const teamMatches = matches.filter(m => m.homeTeam.id === teamId || m.awayTeam.id === teamId);
|
|
240
|
+
const points = teamMatches.reduce((sum, m) => sum + this.getMatchPoints(m, teamId), 0);
|
|
241
|
+
teamPerformance = points / (teamMatches.length || 1);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
condition,
|
|
245
|
+
matches: matches.length,
|
|
246
|
+
avgGoals: totalGoals / (matches.length || 1),
|
|
247
|
+
homeWinRate: homeWins / (matches.length || 1),
|
|
248
|
+
teamPerformance,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Get similar matches
|
|
253
|
+
*/
|
|
254
|
+
findSimilarMatches(match, limit = 5) {
|
|
255
|
+
return this.matchHistory
|
|
256
|
+
.filter(m => m.id !== match.id &&
|
|
257
|
+
m.status === 'finished' &&
|
|
258
|
+
m.league.id === match.league.id)
|
|
259
|
+
.map(m => ({
|
|
260
|
+
match: m,
|
|
261
|
+
similarity: this.calculateSimilarity(match, m),
|
|
262
|
+
}))
|
|
263
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
264
|
+
.slice(0, limit)
|
|
265
|
+
.map(r => r.match);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Calculate similarity between two matches
|
|
269
|
+
*/
|
|
270
|
+
calculateSimilarity(matchA, matchB) {
|
|
271
|
+
let score = 0;
|
|
272
|
+
// Same league
|
|
273
|
+
if (matchA.league.id === matchB.league.id)
|
|
274
|
+
score += 0.3;
|
|
275
|
+
// Similar odds
|
|
276
|
+
if (matchA.odds && matchB.odds) {
|
|
277
|
+
const oddsDiff = Math.abs(matchA.odds.homeWin - matchB.odds.homeWin);
|
|
278
|
+
score += Math.max(0, 0.3 - oddsDiff);
|
|
279
|
+
}
|
|
280
|
+
// Similar teams (by position)
|
|
281
|
+
if (matchA.homeTeam.stats?.position && matchB.homeTeam.stats?.position) {
|
|
282
|
+
const posDiff = Math.abs(matchA.homeTeam.stats.position - matchB.homeTeam.stats.position);
|
|
283
|
+
score += Math.max(0, 0.2 - (posDiff / 20));
|
|
284
|
+
}
|
|
285
|
+
// Same venue type
|
|
286
|
+
if (matchA.venue && matchB.venue)
|
|
287
|
+
score += 0.2;
|
|
288
|
+
return score;
|
|
289
|
+
}
|
|
290
|
+
// Helper methods
|
|
291
|
+
getTeamMatches(teamId, query) {
|
|
292
|
+
let matches = this.matchHistory.filter(m => (m.homeTeam.id === teamId || m.awayTeam.id === teamId) &&
|
|
293
|
+
m.status === 'finished');
|
|
294
|
+
if (query?.dateFrom) {
|
|
295
|
+
matches = matches.filter(m => m.date >= query.dateFrom);
|
|
296
|
+
}
|
|
297
|
+
if (query?.dateTo) {
|
|
298
|
+
matches = matches.filter(m => m.date <= query.dateTo);
|
|
299
|
+
}
|
|
300
|
+
if (query?.venue) {
|
|
301
|
+
matches = matches.filter(m => query.venue === 'home' ? m.homeTeam.id === teamId :
|
|
302
|
+
query.venue === 'away' ? m.awayTeam.id === teamId :
|
|
303
|
+
true);
|
|
304
|
+
}
|
|
305
|
+
return matches.sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
306
|
+
}
|
|
307
|
+
getH2HMatches(teamAId, teamBId, limit) {
|
|
308
|
+
return this.matchHistory
|
|
309
|
+
.filter(m => ((m.homeTeam.id === teamAId && m.awayTeam.id === teamBId) ||
|
|
310
|
+
(m.homeTeam.id === teamBId && m.awayTeam.id === teamAId)) &&
|
|
311
|
+
m.status === 'finished')
|
|
312
|
+
.sort((a, b) => b.date.getTime() - a.date.getTime())
|
|
313
|
+
.slice(0, limit);
|
|
314
|
+
}
|
|
315
|
+
isWin(match, teamId) {
|
|
316
|
+
if (!match.score)
|
|
317
|
+
return false;
|
|
318
|
+
const isHome = match.homeTeam.id === teamId;
|
|
319
|
+
return isHome
|
|
320
|
+
? match.score.home > match.score.away
|
|
321
|
+
: match.score.away > match.score.home;
|
|
322
|
+
}
|
|
323
|
+
isDraw(match) {
|
|
324
|
+
if (!match.score)
|
|
325
|
+
return false;
|
|
326
|
+
return match.score.home === match.score.away;
|
|
327
|
+
}
|
|
328
|
+
isLoss(match, teamId) {
|
|
329
|
+
if (!match.score)
|
|
330
|
+
return false;
|
|
331
|
+
const isHome = match.homeTeam.id === teamId;
|
|
332
|
+
return isHome
|
|
333
|
+
? match.score.home < match.score.away
|
|
334
|
+
: match.score.away < match.score.home;
|
|
335
|
+
}
|
|
336
|
+
getGoalsFor(match, teamId) {
|
|
337
|
+
if (!match.score)
|
|
338
|
+
return 0;
|
|
339
|
+
return match.homeTeam.id === teamId
|
|
340
|
+
? match.score.home
|
|
341
|
+
: match.score.away;
|
|
342
|
+
}
|
|
343
|
+
getGoalsAgainst(match, teamId) {
|
|
344
|
+
if (!match.score)
|
|
345
|
+
return 0;
|
|
346
|
+
return match.homeTeam.id === teamId
|
|
347
|
+
? match.score.away
|
|
348
|
+
: match.score.home;
|
|
349
|
+
}
|
|
350
|
+
getMatchPoints(match, teamId) {
|
|
351
|
+
if (this.isWin(match, teamId))
|
|
352
|
+
return 3;
|
|
353
|
+
if (this.isDraw(match))
|
|
354
|
+
return 1;
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
357
|
+
calculateHomeAdvantage(teamId, matches) {
|
|
358
|
+
const homeMatches = matches.filter(m => m.homeTeam.id === teamId);
|
|
359
|
+
const awayMatches = matches.filter(m => m.awayTeam.id === teamId);
|
|
360
|
+
if (homeMatches.length === 0 || awayMatches.length === 0)
|
|
361
|
+
return 0;
|
|
362
|
+
const homeWinRate = homeMatches.filter(m => this.isWin(m, teamId)).length / homeMatches.length;
|
|
363
|
+
const awayWinRate = awayMatches.filter(m => this.isWin(m, teamId)).length / awayMatches.length;
|
|
364
|
+
return homeWinRate - awayWinRate;
|
|
365
|
+
}
|
|
366
|
+
findPatterns(teamId, matches) {
|
|
367
|
+
const patterns = [];
|
|
368
|
+
// Pattern: Strong at home
|
|
369
|
+
const homeMatches = matches.filter(m => m.homeTeam.id === teamId);
|
|
370
|
+
const homeWins = homeMatches.filter(m => this.isWin(m, teamId)).length;
|
|
371
|
+
if (homeMatches.length >= 5 && homeWins / homeMatches.length > 0.6) {
|
|
372
|
+
patterns.push({
|
|
373
|
+
pattern: 'Strong home record',
|
|
374
|
+
occurrence: homeWins,
|
|
375
|
+
successRate: homeWins / homeMatches.length,
|
|
376
|
+
avgOdds: 1.8,
|
|
377
|
+
profit: 0,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
// Pattern: High scoring
|
|
381
|
+
const highScoring = matches.filter(m => this.getGoalsFor(m, teamId) + this.getGoalsAgainst(m, teamId) > 2.5);
|
|
382
|
+
if (highScoring.length / matches.length > 0.6) {
|
|
383
|
+
patterns.push({
|
|
384
|
+
pattern: 'High scoring matches',
|
|
385
|
+
occurrence: highScoring.length,
|
|
386
|
+
successRate: highScoring.length / matches.length,
|
|
387
|
+
avgOdds: 1.9,
|
|
388
|
+
profit: 0,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return patterns;
|
|
392
|
+
}
|
|
393
|
+
calculateProfit(matches, betOn) {
|
|
394
|
+
let profit = 0;
|
|
395
|
+
matches.forEach(m => {
|
|
396
|
+
if (!m.odds || !m.score)
|
|
397
|
+
return;
|
|
398
|
+
const won = betOn === 'home' && m.score.home > m.score.away ||
|
|
399
|
+
betOn === 'away' && m.score.away > m.score.home ||
|
|
400
|
+
betOn === 'draw' && m.score.home === m.score.away;
|
|
401
|
+
if (won) {
|
|
402
|
+
profit += (m.odds[betOn === 'home' ? 'homeWin' : betOn === 'away' ? 'awayWin' : 'draw'] - 1) * 10;
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
profit -= 10;
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
return profit;
|
|
409
|
+
}
|
|
410
|
+
queryMatches(query) {
|
|
411
|
+
let matches = this.matchHistory.filter(m => m.status === 'finished');
|
|
412
|
+
if (query.team) {
|
|
413
|
+
matches = matches.filter(m => m.homeTeam.name === query.team || m.awayTeam.name === query.team);
|
|
414
|
+
}
|
|
415
|
+
if (query.league) {
|
|
416
|
+
matches = matches.filter(m => m.league.name === query.league);
|
|
417
|
+
}
|
|
418
|
+
if (query.dateFrom) {
|
|
419
|
+
matches = matches.filter(m => m.date >= query.dateFrom);
|
|
420
|
+
}
|
|
421
|
+
if (query.dateTo) {
|
|
422
|
+
matches = matches.filter(m => m.date <= query.dateTo);
|
|
423
|
+
}
|
|
424
|
+
return matches;
|
|
425
|
+
}
|
|
426
|
+
createEmptyPerformance(teamId) {
|
|
427
|
+
return {
|
|
428
|
+
team: { id: teamId, name: '', country: '' },
|
|
429
|
+
totalMatches: 0,
|
|
430
|
+
wins: 0,
|
|
431
|
+
draws: 0,
|
|
432
|
+
losses: 0,
|
|
433
|
+
avgGoalsFor: 0,
|
|
434
|
+
avgGoalsAgainst: 0,
|
|
435
|
+
homeAdvantage: 0,
|
|
436
|
+
patterns: [],
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
loadHistoricalData() {
|
|
440
|
+
// Would load from database
|
|
441
|
+
// For now, start empty
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Add match to history
|
|
445
|
+
*/
|
|
446
|
+
addMatch(match) {
|
|
447
|
+
this.matchHistory.push(match);
|
|
448
|
+
// Keep only last 1000 matches
|
|
449
|
+
if (this.matchHistory.length > 1000) {
|
|
450
|
+
this.matchHistory.shift();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// Singleton instance
|
|
455
|
+
let globalHistoricalAnalyzer = null;
|
|
456
|
+
export function getHistoricalAnalyzer() {
|
|
457
|
+
if (!globalHistoricalAnalyzer) {
|
|
458
|
+
globalHistoricalAnalyzer = new HistoricalAnalyzer();
|
|
459
|
+
}
|
|
460
|
+
return globalHistoricalAnalyzer;
|
|
461
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE CORE - Exports
|
|
3
|
+
*/
|
|
4
|
+
export * from './types.js';
|
|
5
|
+
export { CacheService, getGlobalCache, resetGlobalCache } from './cache.js';
|
|
6
|
+
export { CircuitBreaker, CircuitBreakerOpenError, getCircuitBreaker, getAllCircuitBreakerStatus } from './circuit-breaker.js';
|
|
7
|
+
export { withRetry, withRetryAndCircuitBreaker, createRetryable, DEFAULT_RETRY_CONFIG, AGGRESSIVE_RETRY_CONFIG, LIVE_DATA_RETRY_CONFIG } from './retry.js';
|
|
8
|
+
export { RealtimeDataManager, getRealtimeManager, resetRealtimeManager } from './realtime-manager.js';
|
|
9
|
+
export { AlertManager, getAlertManager, resetAlertManager } from './alert-manager.js';
|
|
10
|
+
export { DataQualityValidator, getDataQualityValidator } from './data-quality.js';
|
|
11
|
+
export { MLPredictionEngine, getPredictionEngine } from './ml-prediction.js';
|
|
12
|
+
export { HistoricalAnalyzer, getHistoricalAnalyzer } from './historical-analyzer.js';
|
|
13
|
+
export { API_CONFIG, CACHE_CONFIG, SCRAPER_CONFIG, LEAGUES, QUERY_TYPES, BETTING, POSITIONS, MATCH_STATUS, ERRORS, ALERT_CONFIG, REALTIME_CONFIG, ML_CONFIG, MODULE_INFO } from './constants.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE CORE - Exports
|
|
3
|
+
*/
|
|
4
|
+
// Types
|
|
5
|
+
export * from './types.js';
|
|
6
|
+
// Core classes
|
|
7
|
+
export { CacheService, getGlobalCache, resetGlobalCache } from './cache.js';
|
|
8
|
+
export { CircuitBreaker, CircuitBreakerOpenError, getCircuitBreaker, getAllCircuitBreakerStatus } from './circuit-breaker.js';
|
|
9
|
+
export { withRetry, withRetryAndCircuitBreaker, createRetryable, DEFAULT_RETRY_CONFIG, AGGRESSIVE_RETRY_CONFIG, LIVE_DATA_RETRY_CONFIG } from './retry.js';
|
|
10
|
+
export { RealtimeDataManager, getRealtimeManager, resetRealtimeManager } from './realtime-manager.js';
|
|
11
|
+
export { AlertManager, getAlertManager, resetAlertManager } from './alert-manager.js';
|
|
12
|
+
export { DataQualityValidator, getDataQualityValidator } from './data-quality.js';
|
|
13
|
+
export { MLPredictionEngine, getPredictionEngine } from './ml-prediction.js';
|
|
14
|
+
export { HistoricalAnalyzer, getHistoricalAnalyzer } from './historical-analyzer.js';
|
|
15
|
+
// Constants
|
|
16
|
+
export { API_CONFIG, CACHE_CONFIG, SCRAPER_CONFIG, LEAGUES, QUERY_TYPES, BETTING, POSITIONS, MATCH_STATUS, ERRORS, ALERT_CONFIG, REALTIME_CONFIG, ML_CONFIG, MODULE_INFO } from './constants.js';
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ML PREDICTION ENGINE
|
|
3
|
+
* Machine learning based match outcome prediction
|
|
4
|
+
*/
|
|
5
|
+
import type { Match, TeamStats, PredictionFactor, HeadToHead } from './types.js';
|
|
6
|
+
export interface PredictionInput {
|
|
7
|
+
match: Match;
|
|
8
|
+
homeForm: FormData;
|
|
9
|
+
awayForm: FormData;
|
|
10
|
+
h2h: HeadToHead;
|
|
11
|
+
homeStats: TeamStats;
|
|
12
|
+
awayStats: TeamStats;
|
|
13
|
+
injuries: InjuryData;
|
|
14
|
+
weather?: WeatherData;
|
|
15
|
+
}
|
|
16
|
+
export interface FormData {
|
|
17
|
+
last5: ('W' | 'D' | 'L')[];
|
|
18
|
+
goalsFor: number;
|
|
19
|
+
goalsAgainst: number;
|
|
20
|
+
xG: number;
|
|
21
|
+
xGA: number;
|
|
22
|
+
}
|
|
23
|
+
export interface InjuryData {
|
|
24
|
+
home: {
|
|
25
|
+
keyPlayers: number;
|
|
26
|
+
total: number;
|
|
27
|
+
};
|
|
28
|
+
away: {
|
|
29
|
+
keyPlayers: number;
|
|
30
|
+
total: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface WeatherData {
|
|
34
|
+
temperature: number;
|
|
35
|
+
condition: string;
|
|
36
|
+
windSpeed: number;
|
|
37
|
+
precipitation: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface PredictionResult {
|
|
40
|
+
homeWin: number;
|
|
41
|
+
draw: number;
|
|
42
|
+
awayWin: number;
|
|
43
|
+
over25: number;
|
|
44
|
+
btts: number;
|
|
45
|
+
confidence: number;
|
|
46
|
+
factors: PredictionFactor[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* ML Prediction Engine
|
|
50
|
+
* Uses weighted factors and historical patterns for predictions
|
|
51
|
+
*/
|
|
52
|
+
export declare class MLPredictionEngine {
|
|
53
|
+
private weights;
|
|
54
|
+
private historicalData;
|
|
55
|
+
constructor();
|
|
56
|
+
/**
|
|
57
|
+
* Predict match outcome
|
|
58
|
+
*/
|
|
59
|
+
predict(input: PredictionInput): Promise<PredictionResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Calculate form score (-1 to 1)
|
|
62
|
+
*/
|
|
63
|
+
private calculateFormScore;
|
|
64
|
+
private formToPoints;
|
|
65
|
+
/**
|
|
66
|
+
* Calculate H2H score (-1 to 1)
|
|
67
|
+
*/
|
|
68
|
+
private calculateH2HScore;
|
|
69
|
+
/**
|
|
70
|
+
* Calculate home advantage score (0 to 1)
|
|
71
|
+
*/
|
|
72
|
+
private calculateHomeAdvantage;
|
|
73
|
+
/**
|
|
74
|
+
* Calculate xG score (-1 to 1)
|
|
75
|
+
*/
|
|
76
|
+
private calculateXgScore;
|
|
77
|
+
/**
|
|
78
|
+
* Calculate injury impact (-1 to 1)
|
|
79
|
+
*/
|
|
80
|
+
private calculateInjuryImpact;
|
|
81
|
+
/**
|
|
82
|
+
* Calculate fatigue impact (-1 to 1)
|
|
83
|
+
*/
|
|
84
|
+
private calculateFatigueImpact;
|
|
85
|
+
/**
|
|
86
|
+
* Calculate weather impact (-0.5 to 0.5)
|
|
87
|
+
*/
|
|
88
|
+
private calculateWeatherImpact;
|
|
89
|
+
/**
|
|
90
|
+
* Combine weighted scores
|
|
91
|
+
*/
|
|
92
|
+
private combineScores;
|
|
93
|
+
/**
|
|
94
|
+
* Convert score to probabilities
|
|
95
|
+
*/
|
|
96
|
+
private scoreToProbabilities;
|
|
97
|
+
/**
|
|
98
|
+
* Calculate confidence level
|
|
99
|
+
*/
|
|
100
|
+
private calculateConfidence;
|
|
101
|
+
/**
|
|
102
|
+
* Predict Over 2.5 goals
|
|
103
|
+
*/
|
|
104
|
+
private predictOver25;
|
|
105
|
+
/**
|
|
106
|
+
* Predict Both Teams to Score
|
|
107
|
+
*/
|
|
108
|
+
private predictBTTS;
|
|
109
|
+
/**
|
|
110
|
+
* Describe form impact
|
|
111
|
+
*/
|
|
112
|
+
private describeFormImpact;
|
|
113
|
+
/**
|
|
114
|
+
* Describe H2H impact
|
|
115
|
+
*/
|
|
116
|
+
private describeH2HImpact;
|
|
117
|
+
/**
|
|
118
|
+
* Describe injury impact
|
|
119
|
+
*/
|
|
120
|
+
private describeInjuryImpact;
|
|
121
|
+
/**
|
|
122
|
+
* Describe weather impact
|
|
123
|
+
*/
|
|
124
|
+
private describeWeatherImpact;
|
|
125
|
+
/**
|
|
126
|
+
* Load historical data
|
|
127
|
+
*/
|
|
128
|
+
private loadHistoricalData;
|
|
129
|
+
/**
|
|
130
|
+
* Learn from past predictions
|
|
131
|
+
*/
|
|
132
|
+
learnFromResult(prediction: PredictionResult, actualResult: 'home' | 'draw' | 'away', odds: number): void;
|
|
133
|
+
}
|
|
134
|
+
export declare function getPredictionEngine(): MLPredictionEngine;
|