@gotza02/sequential-thinking 10000.1.0 → 10000.1.2
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/dist/dashboard/server.d.ts +0 -34
- package/dist/dashboard/server.js +50 -269
- package/dist/tools/sports/core/alert-manager.d.ts +1 -50
- package/dist/tools/sports/core/alert-manager.js +62 -123
- package/dist/tools/sports/core/data-quality.d.ts +0 -44
- package/dist/tools/sports/core/data-quality.js +49 -266
- package/dist/tools/sports/core/historical-analyzer.d.ts +0 -54
- package/dist/tools/sports/core/historical-analyzer.js +56 -256
- package/dist/tools/sports/core/index.d.ts +1 -1
- package/dist/tools/sports/core/index.js +1 -1
- package/dist/tools/sports/core/ml-prediction.d.ts +7 -65
- package/dist/tools/sports/core/ml-prediction.js +43 -185
- package/dist/tools/sports/core/realtime-manager.d.ts +1 -52
- package/dist/tools/sports/core/realtime-manager.js +18 -127
- package/package.json +1 -1
|
@@ -1,53 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
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
3
|
*/
|
|
4
|
+
// Simple logger fallback
|
|
5
|
+
const logger = {
|
|
6
|
+
info: (...args) => console.error('[INFO]', ...args),
|
|
7
|
+
warn: (...args) => console.warn('[WARN]', ...args),
|
|
8
|
+
error: (...args) => console.error('[ERROR]', ...args),
|
|
9
|
+
debug: (...args) => { }
|
|
10
|
+
};
|
|
9
11
|
export class HistoricalAnalyzer {
|
|
10
12
|
matchHistory = [];
|
|
11
|
-
patterns = new Map();
|
|
12
|
-
constructor() {
|
|
13
|
-
this.loadHistoricalData();
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Analyze team performance over time
|
|
17
|
-
*/
|
|
18
13
|
analyzeTeamPerformance(teamId, query) {
|
|
19
14
|
const matches = this.getTeamMatches(teamId, query);
|
|
20
|
-
if (matches.length === 0)
|
|
15
|
+
if (matches.length === 0)
|
|
21
16
|
return this.createEmptyPerformance(teamId);
|
|
22
|
-
}
|
|
23
17
|
const wins = matches.filter(m => this.isWin(m, teamId)).length;
|
|
24
18
|
const draws = matches.filter(m => this.isDraw(m)).length;
|
|
25
19
|
const losses = matches.filter(m => this.isLoss(m, teamId)).length;
|
|
26
20
|
const totalGoalsFor = matches.reduce((sum, m) => sum + this.getGoalsFor(m, teamId), 0);
|
|
27
21
|
const totalGoalsAgainst = matches.reduce((sum, m) => sum + this.getGoalsAgainst(m, teamId), 0);
|
|
28
|
-
const patterns = this.findPatterns(teamId, matches);
|
|
29
22
|
return {
|
|
30
23
|
team: matches[0].homeTeam.id === teamId ? matches[0].homeTeam : matches[0].awayTeam,
|
|
31
24
|
totalMatches: matches.length,
|
|
32
|
-
wins,
|
|
33
|
-
draws,
|
|
34
|
-
losses,
|
|
25
|
+
wins, draws, losses,
|
|
35
26
|
avgGoalsFor: totalGoalsFor / matches.length,
|
|
36
27
|
avgGoalsAgainst: totalGoalsAgainst / matches.length,
|
|
37
28
|
homeAdvantage: this.calculateHomeAdvantage(teamId, matches),
|
|
38
|
-
patterns,
|
|
29
|
+
patterns: this.findPatterns(teamId, matches),
|
|
39
30
|
};
|
|
40
31
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Analyze head-to-head history
|
|
43
|
-
*/
|
|
44
32
|
analyzeHeadToHead(teamAId, teamBId, limit = 10) {
|
|
45
33
|
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;
|
|
34
|
+
let homeWins = 0, awayWins = 0, draws = 0, homeGoals = 0, awayGoals = 0;
|
|
51
35
|
matches.forEach(match => {
|
|
52
36
|
if (!match.score)
|
|
53
37
|
return;
|
|
@@ -74,71 +58,38 @@ export class HistoricalAnalyzer {
|
|
|
74
58
|
homeTeam: matches[0]?.homeTeam || { id: teamAId, name: '', country: '' },
|
|
75
59
|
awayTeam: matches[0]?.awayTeam || { id: teamBId, name: '', country: '' },
|
|
76
60
|
totalMatches: matches.length,
|
|
77
|
-
homeWins,
|
|
78
|
-
|
|
79
|
-
awayWins,
|
|
80
|
-
homeGoals,
|
|
81
|
-
awayGoals,
|
|
61
|
+
homeWins, draws, awayWins,
|
|
62
|
+
homeGoals, awayGoals,
|
|
82
63
|
recentMatches: matches.slice(0, 5),
|
|
83
64
|
};
|
|
84
65
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Analyze trends for a team
|
|
87
|
-
*/
|
|
88
66
|
analyzeTrends(teamId, windowSize = 5) {
|
|
89
67
|
const matches = this.getTeamMatches(teamId).slice(0, windowSize);
|
|
90
68
|
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
|
-
};
|
|
69
|
+
return { direction: 'stable', strength: 0, confidence: 0, dataPoints: matches.length, description: 'Insufficient data' };
|
|
98
70
|
}
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const olderAvg = olderPerformance.reduce((a, b) => a + b, 0) / olderPerformance.length;
|
|
71
|
+
const recent = matches.slice(0, Math.ceil(windowSize / 2)).map(m => this.getMatchPoints(m, teamId));
|
|
72
|
+
const older = matches.slice(Math.ceil(windowSize / 2)).map(m => this.getMatchPoints(m, teamId));
|
|
73
|
+
const recentAvg = recent.reduce((a, b) => a + b, 0) / recent.length;
|
|
74
|
+
const olderAvg = older.reduce((a, b) => a + b, 0) / older.length;
|
|
104
75
|
const difference = recentAvg - olderAvg;
|
|
105
76
|
const strength = Math.min(1, Math.abs(difference) / 2);
|
|
106
|
-
|
|
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`;
|
|
77
|
+
const direction = difference > 0.5 ? 'up' : difference < -0.5 ? 'down' : 'stable';
|
|
118
78
|
return {
|
|
119
|
-
direction,
|
|
120
|
-
strength,
|
|
79
|
+
direction, strength,
|
|
121
80
|
confidence: Math.min(100, matches.length * 10),
|
|
122
81
|
dataPoints: matches.length,
|
|
123
|
-
description
|
|
82
|
+
description: direction === 'up'
|
|
83
|
+
? `Improving: ${recentAvg.toFixed(2)} pts/game vs ${olderAvg.toFixed(2)} before`
|
|
84
|
+
: direction === 'down'
|
|
85
|
+
? `Declining: ${recentAvg.toFixed(2)} pts/game vs ${olderAvg.toFixed(2)} before`
|
|
86
|
+
: `Stable: ${recentAvg.toFixed(2)} pts/game`,
|
|
124
87
|
};
|
|
125
88
|
}
|
|
126
|
-
/**
|
|
127
|
-
* Calculate performance metrics
|
|
128
|
-
*/
|
|
129
89
|
calculateMetrics(teamId, query) {
|
|
130
90
|
const matches = this.getTeamMatches(teamId, query);
|
|
131
91
|
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
|
-
};
|
|
92
|
+
return { winRate: 0, drawRate: 0, lossRate: 0, avgGoalsFor: 0, avgGoalsAgainst: 0, cleanSheetRate: 0, bothTeamsScoreRate: 0, over25Rate: 0 };
|
|
142
93
|
}
|
|
143
94
|
const wins = matches.filter(m => this.isWin(m, teamId)).length;
|
|
144
95
|
const draws = matches.filter(m => this.isDraw(m)).length;
|
|
@@ -147,10 +98,7 @@ export class HistoricalAnalyzer {
|
|
|
147
98
|
const totalGoalsAgainst = matches.reduce((sum, m) => sum + this.getGoalsAgainst(m, teamId), 0);
|
|
148
99
|
const cleanSheets = matches.filter(m => this.getGoalsAgainst(m, teamId) === 0).length;
|
|
149
100
|
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;
|
|
101
|
+
const over25 = matches.filter(m => this.getGoalsFor(m, teamId) + this.getGoalsAgainst(m, teamId) > 2.5).length;
|
|
154
102
|
return {
|
|
155
103
|
winRate: wins / matches.length,
|
|
156
104
|
drawRate: draws / matches.length,
|
|
@@ -162,13 +110,9 @@ export class HistoricalAnalyzer {
|
|
|
162
110
|
over25Rate: over25 / matches.length,
|
|
163
111
|
};
|
|
164
112
|
}
|
|
165
|
-
/**
|
|
166
|
-
* Find betting patterns
|
|
167
|
-
*/
|
|
168
113
|
findBettingPatterns(query) {
|
|
169
114
|
const patterns = [];
|
|
170
115
|
const matches = this.queryMatches(query);
|
|
171
|
-
// Pattern: Home favorites
|
|
172
116
|
const homeFavorites = matches.filter(m => m.odds && m.odds.homeWin < 1.5 && m.score && m.score.home > m.score.away);
|
|
173
117
|
if (homeFavorites.length >= 5) {
|
|
174
118
|
patterns.push({
|
|
@@ -179,136 +123,42 @@ export class HistoricalAnalyzer {
|
|
|
179
123
|
profit: this.calculateProfit(homeFavorites, 'home'),
|
|
180
124
|
});
|
|
181
125
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const total = (m.score?.home || 0) + (m.score?.away || 0);
|
|
185
|
-
return total < 2.5;
|
|
186
|
-
});
|
|
187
|
-
if (lowScoringMatches.length >= 10) {
|
|
126
|
+
const lowScoring = matches.filter(m => (m.score?.home || 0) + (m.score?.away || 0) < 2.5);
|
|
127
|
+
if (lowScoring.length >= 10) {
|
|
188
128
|
patterns.push({
|
|
189
129
|
pattern: 'Under 2.5 goals',
|
|
190
|
-
occurrence:
|
|
191
|
-
successRate:
|
|
192
|
-
avgOdds: 1.8,
|
|
193
|
-
profit: (
|
|
130
|
+
occurrence: lowScoring.length,
|
|
131
|
+
successRate: lowScoring.length / matches.length,
|
|
132
|
+
avgOdds: 1.8,
|
|
133
|
+
profit: (lowScoring.length * 0.8 - (matches.length - lowScoring.length)) * 10,
|
|
194
134
|
});
|
|
195
135
|
}
|
|
196
|
-
// Pattern: BTTS
|
|
197
136
|
const bttsMatches = matches.filter(m => (m.score?.home || 0) > 0 && (m.score?.away || 0) > 0);
|
|
198
137
|
if (bttsMatches.length >= 10) {
|
|
199
138
|
patterns.push({
|
|
200
139
|
pattern: 'Both teams score',
|
|
201
140
|
occurrence: bttsMatches.length,
|
|
202
141
|
successRate: bttsMatches.length / matches.length,
|
|
203
|
-
avgOdds: 1.9,
|
|
142
|
+
avgOdds: 1.9,
|
|
204
143
|
profit: (bttsMatches.length * 0.9 - (matches.length - bttsMatches.length)) * 10,
|
|
205
144
|
});
|
|
206
145
|
}
|
|
207
146
|
return patterns.sort((a, b) => b.profit - a.profit);
|
|
208
147
|
}
|
|
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
148
|
getTeamMatches(teamId, query) {
|
|
292
|
-
let matches = this.matchHistory.filter(m => (m.homeTeam.id === teamId || m.awayTeam.id === teamId) &&
|
|
293
|
-
|
|
294
|
-
if (query?.dateFrom) {
|
|
149
|
+
let matches = this.matchHistory.filter(m => (m.homeTeam.id === teamId || m.awayTeam.id === teamId) && m.status === 'finished');
|
|
150
|
+
if (query?.dateFrom)
|
|
295
151
|
matches = matches.filter(m => m.date >= query.dateFrom);
|
|
296
|
-
|
|
297
|
-
if (query?.dateTo) {
|
|
152
|
+
if (query?.dateTo)
|
|
298
153
|
matches = matches.filter(m => m.date <= query.dateTo);
|
|
299
|
-
}
|
|
300
154
|
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);
|
|
155
|
+
matches = matches.filter(m => query.venue === 'home' ? m.homeTeam.id === teamId : query.venue === 'away' ? m.awayTeam.id === teamId : true);
|
|
304
156
|
}
|
|
305
157
|
return matches.sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
306
158
|
}
|
|
307
159
|
getH2HMatches(teamAId, teamBId, limit) {
|
|
308
160
|
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')
|
|
161
|
+
.filter(m => ((m.homeTeam.id === teamAId && m.awayTeam.id === teamBId) || (m.homeTeam.id === teamBId && m.awayTeam.id === teamAId)) && m.status === 'finished')
|
|
312
162
|
.sort((a, b) => b.date.getTime() - a.date.getTime())
|
|
313
163
|
.slice(0, limit);
|
|
314
164
|
}
|
|
@@ -316,36 +166,22 @@ export class HistoricalAnalyzer {
|
|
|
316
166
|
if (!match.score)
|
|
317
167
|
return false;
|
|
318
168
|
const isHome = match.homeTeam.id === teamId;
|
|
319
|
-
return isHome
|
|
320
|
-
? match.score.home > match.score.away
|
|
321
|
-
: match.score.away > match.score.home;
|
|
169
|
+
return isHome ? match.score.home > match.score.away : match.score.away > match.score.home;
|
|
322
170
|
}
|
|
323
171
|
isDraw(match) {
|
|
324
|
-
|
|
325
|
-
return false;
|
|
326
|
-
return match.score.home === match.score.away;
|
|
172
|
+
return match.score ? match.score.home === match.score.away : false;
|
|
327
173
|
}
|
|
328
174
|
isLoss(match, teamId) {
|
|
329
175
|
if (!match.score)
|
|
330
176
|
return false;
|
|
331
177
|
const isHome = match.homeTeam.id === teamId;
|
|
332
|
-
return isHome
|
|
333
|
-
? match.score.home < match.score.away
|
|
334
|
-
: match.score.away < match.score.home;
|
|
178
|
+
return isHome ? match.score.home < match.score.away : match.score.away < match.score.home;
|
|
335
179
|
}
|
|
336
180
|
getGoalsFor(match, teamId) {
|
|
337
|
-
|
|
338
|
-
return 0;
|
|
339
|
-
return match.homeTeam.id === teamId
|
|
340
|
-
? match.score.home
|
|
341
|
-
: match.score.away;
|
|
181
|
+
return match.score ? (match.homeTeam.id === teamId ? match.score.home : match.score.away) : 0;
|
|
342
182
|
}
|
|
343
183
|
getGoalsAgainst(match, teamId) {
|
|
344
|
-
|
|
345
|
-
return 0;
|
|
346
|
-
return match.homeTeam.id === teamId
|
|
347
|
-
? match.score.away
|
|
348
|
-
: match.score.home;
|
|
184
|
+
return match.score ? (match.homeTeam.id === teamId ? match.score.away : match.score.home) : 0;
|
|
349
185
|
}
|
|
350
186
|
getMatchPoints(match, teamId) {
|
|
351
187
|
if (this.isWin(match, teamId))
|
|
@@ -365,28 +201,14 @@ export class HistoricalAnalyzer {
|
|
|
365
201
|
}
|
|
366
202
|
findPatterns(teamId, matches) {
|
|
367
203
|
const patterns = [];
|
|
368
|
-
// Pattern: Strong at home
|
|
369
204
|
const homeMatches = matches.filter(m => m.homeTeam.id === teamId);
|
|
370
205
|
const homeWins = homeMatches.filter(m => this.isWin(m, teamId)).length;
|
|
371
206
|
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
|
-
});
|
|
207
|
+
patterns.push({ pattern: 'Strong home record', occurrence: homeWins, successRate: homeWins / homeMatches.length, avgOdds: 1.8, profit: 0 });
|
|
379
208
|
}
|
|
380
|
-
// Pattern: High scoring
|
|
381
209
|
const highScoring = matches.filter(m => this.getGoalsFor(m, teamId) + this.getGoalsAgainst(m, teamId) > 2.5);
|
|
382
210
|
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
|
-
});
|
|
211
|
+
patterns.push({ pattern: 'High scoring matches', occurrence: highScoring.length, successRate: highScoring.length / matches.length, avgOdds: 1.9, profit: 0 });
|
|
390
212
|
}
|
|
391
213
|
return patterns;
|
|
392
214
|
}
|
|
@@ -395,9 +217,7 @@ export class HistoricalAnalyzer {
|
|
|
395
217
|
matches.forEach(m => {
|
|
396
218
|
if (!m.odds || !m.score)
|
|
397
219
|
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;
|
|
220
|
+
const won = betOn === 'home' && m.score.home > m.score.away || betOn === 'away' && m.score.away > m.score.home || betOn === 'draw' && m.score.home === m.score.away;
|
|
401
221
|
if (won) {
|
|
402
222
|
profit += (m.odds[betOn === 'home' ? 'homeWin' : betOn === 'away' ? 'awayWin' : 'draw'] - 1) * 10;
|
|
403
223
|
}
|
|
@@ -409,53 +229,33 @@ export class HistoricalAnalyzer {
|
|
|
409
229
|
}
|
|
410
230
|
queryMatches(query) {
|
|
411
231
|
let matches = this.matchHistory.filter(m => m.status === 'finished');
|
|
412
|
-
if (query.team)
|
|
232
|
+
if (query.team)
|
|
413
233
|
matches = matches.filter(m => m.homeTeam.name === query.team || m.awayTeam.name === query.team);
|
|
414
|
-
|
|
415
|
-
if (query.league) {
|
|
234
|
+
if (query.league)
|
|
416
235
|
matches = matches.filter(m => m.league.name === query.league);
|
|
417
|
-
|
|
418
|
-
if (query.dateFrom) {
|
|
236
|
+
if (query.dateFrom)
|
|
419
237
|
matches = matches.filter(m => m.date >= query.dateFrom);
|
|
420
|
-
|
|
421
|
-
if (query.dateTo) {
|
|
238
|
+
if (query.dateTo)
|
|
422
239
|
matches = matches.filter(m => m.date <= query.dateTo);
|
|
423
|
-
}
|
|
424
240
|
return matches;
|
|
425
241
|
}
|
|
426
242
|
createEmptyPerformance(teamId) {
|
|
427
243
|
return {
|
|
428
244
|
team: { id: teamId, name: '', country: '' },
|
|
429
|
-
totalMatches: 0,
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
losses: 0,
|
|
433
|
-
avgGoalsFor: 0,
|
|
434
|
-
avgGoalsAgainst: 0,
|
|
435
|
-
homeAdvantage: 0,
|
|
436
|
-
patterns: [],
|
|
245
|
+
totalMatches: 0, wins: 0, draws: 0, losses: 0,
|
|
246
|
+
avgGoalsFor: 0, avgGoalsAgainst: 0,
|
|
247
|
+
homeAdvantage: 0, patterns: [],
|
|
437
248
|
};
|
|
438
249
|
}
|
|
439
|
-
loadHistoricalData() {
|
|
440
|
-
// Would load from database
|
|
441
|
-
// For now, start empty
|
|
442
|
-
}
|
|
443
|
-
/**
|
|
444
|
-
* Add match to history
|
|
445
|
-
*/
|
|
446
250
|
addMatch(match) {
|
|
447
251
|
this.matchHistory.push(match);
|
|
448
|
-
|
|
449
|
-
if (this.matchHistory.length > 1000) {
|
|
252
|
+
if (this.matchHistory.length > 1000)
|
|
450
253
|
this.matchHistory.shift();
|
|
451
|
-
}
|
|
452
254
|
}
|
|
453
255
|
}
|
|
454
|
-
// Singleton instance
|
|
455
256
|
let globalHistoricalAnalyzer = null;
|
|
456
257
|
export function getHistoricalAnalyzer() {
|
|
457
|
-
if (!globalHistoricalAnalyzer)
|
|
258
|
+
if (!globalHistoricalAnalyzer)
|
|
458
259
|
globalHistoricalAnalyzer = new HistoricalAnalyzer();
|
|
459
|
-
}
|
|
460
260
|
return globalHistoricalAnalyzer;
|
|
461
261
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ML PREDICTION ENGINE
|
|
3
|
-
* Machine learning based match outcome prediction
|
|
4
3
|
*/
|
|
5
|
-
import type { Match, TeamStats,
|
|
4
|
+
import type { Match, TeamStats, HeadToHead } from './types.js';
|
|
6
5
|
export interface PredictionInput {
|
|
7
6
|
match: Match;
|
|
8
7
|
homeForm: FormData;
|
|
@@ -36,6 +35,12 @@ export interface WeatherData {
|
|
|
36
35
|
windSpeed: number;
|
|
37
36
|
precipitation: boolean;
|
|
38
37
|
}
|
|
38
|
+
export interface PredictionFactor {
|
|
39
|
+
name: string;
|
|
40
|
+
weight: number;
|
|
41
|
+
impact: 'positive' | 'negative' | 'neutral';
|
|
42
|
+
description: string;
|
|
43
|
+
}
|
|
39
44
|
export interface PredictionResult {
|
|
40
45
|
homeWin: number;
|
|
41
46
|
draw: number;
|
|
@@ -45,90 +50,27 @@ export interface PredictionResult {
|
|
|
45
50
|
confidence: number;
|
|
46
51
|
factors: PredictionFactor[];
|
|
47
52
|
}
|
|
48
|
-
/**
|
|
49
|
-
* ML Prediction Engine
|
|
50
|
-
* Uses weighted factors and historical patterns for predictions
|
|
51
|
-
*/
|
|
52
53
|
export declare class MLPredictionEngine {
|
|
53
54
|
private weights;
|
|
54
55
|
private historicalData;
|
|
55
|
-
constructor();
|
|
56
|
-
/**
|
|
57
|
-
* Predict match outcome
|
|
58
|
-
*/
|
|
59
56
|
predict(input: PredictionInput): Promise<PredictionResult>;
|
|
60
|
-
/**
|
|
61
|
-
* Calculate form score (-1 to 1)
|
|
62
|
-
*/
|
|
63
57
|
private calculateFormScore;
|
|
64
58
|
private formToPoints;
|
|
65
|
-
/**
|
|
66
|
-
* Calculate H2H score (-1 to 1)
|
|
67
|
-
*/
|
|
68
59
|
private calculateH2HScore;
|
|
69
|
-
/**
|
|
70
|
-
* Calculate home advantage score (0 to 1)
|
|
71
|
-
*/
|
|
72
60
|
private calculateHomeAdvantage;
|
|
73
|
-
/**
|
|
74
|
-
* Calculate xG score (-1 to 1)
|
|
75
|
-
*/
|
|
76
61
|
private calculateXgScore;
|
|
77
|
-
/**
|
|
78
|
-
* Calculate injury impact (-1 to 1)
|
|
79
|
-
*/
|
|
80
62
|
private calculateInjuryImpact;
|
|
81
|
-
/**
|
|
82
|
-
* Calculate fatigue impact (-1 to 1)
|
|
83
|
-
*/
|
|
84
63
|
private calculateFatigueImpact;
|
|
85
|
-
/**
|
|
86
|
-
* Calculate weather impact (-0.5 to 0.5)
|
|
87
|
-
*/
|
|
88
64
|
private calculateWeatherImpact;
|
|
89
|
-
/**
|
|
90
|
-
* Combine weighted scores
|
|
91
|
-
*/
|
|
92
65
|
private combineScores;
|
|
93
|
-
/**
|
|
94
|
-
* Convert score to probabilities
|
|
95
|
-
*/
|
|
96
66
|
private scoreToProbabilities;
|
|
97
|
-
/**
|
|
98
|
-
* Calculate confidence level
|
|
99
|
-
*/
|
|
100
67
|
private calculateConfidence;
|
|
101
|
-
/**
|
|
102
|
-
* Predict Over 2.5 goals
|
|
103
|
-
*/
|
|
104
68
|
private predictOver25;
|
|
105
|
-
/**
|
|
106
|
-
* Predict Both Teams to Score
|
|
107
|
-
*/
|
|
108
69
|
private predictBTTS;
|
|
109
|
-
/**
|
|
110
|
-
* Describe form impact
|
|
111
|
-
*/
|
|
112
70
|
private describeFormImpact;
|
|
113
|
-
/**
|
|
114
|
-
* Describe H2H impact
|
|
115
|
-
*/
|
|
116
71
|
private describeH2HImpact;
|
|
117
|
-
/**
|
|
118
|
-
* Describe injury impact
|
|
119
|
-
*/
|
|
120
72
|
private describeInjuryImpact;
|
|
121
|
-
/**
|
|
122
|
-
* Describe weather impact
|
|
123
|
-
*/
|
|
124
73
|
private describeWeatherImpact;
|
|
125
|
-
/**
|
|
126
|
-
* Load historical data
|
|
127
|
-
*/
|
|
128
|
-
private loadHistoricalData;
|
|
129
|
-
/**
|
|
130
|
-
* Learn from past predictions
|
|
131
|
-
*/
|
|
132
74
|
learnFromResult(prediction: PredictionResult, actualResult: 'home' | 'draw' | 'away', odds: number): void;
|
|
133
75
|
}
|
|
134
76
|
export declare function getPredictionEngine(): MLPredictionEngine;
|