import { PrismaClient } from '@prisma/client'; import * as dotenv from 'dotenv'; dotenv.config(); (BigInt.prototype as any).toJSON = function () { return this.toString(); }; const prisma = new PrismaClient(); async function analyzeReversalMatches() { console.log('🔍 ANALYZING HT/FT REVERSAL MATCHES (1/2 & 2/1)'); console.log('='.repeat(80)); // Fetch all completed matches with HT and FT scores const matches = await prisma.match.findMany({ where: { status: 'FT', htScoreHome: { not: null }, htScoreAway: { not: null }, scoreHome: { not: null }, scoreAway: { not: null }, oddCategories: { some: {} } }, include: { homeTeam: true, awayTeam: true, league: true, oddCategories: { include: { selections: true } } }, orderBy: { mstUtc: 'desc' } }); console.log(`📊 Total completed matches with odds: ${matches.length}`); // Analyze HT/FT results const reversalMatches: any[] = []; let totalMatches = 0; let htftCounts: Record = { '1/1': 0, '1/X': 0, '1/2': 0, 'X/1': 0, 'X/X': 0, 'X/2': 0, '2/1': 0, '2/X': 0, '2/2': 0 }; for (const match of matches) { const htHome = match.htScoreHome!; const htAway = match.htScoreAway!; const ftHome = match.scoreHome!; const ftAway = match.scoreAway!; const htResult = htHome > htAway ? '1' : htHome === htAway ? 'X' : '2'; const ftResult = ftHome > ftAway ? '1' : ftHome === ftAway ? 'X' : '2'; const htft = `${htResult}/${ftResult}`; htftCounts[htft] = (htftCounts[htft] || 0) + 1; totalMatches++; if (htft === '1/2' || htft === '2/1') { // Extract odds let msHomeOdds: number | null = null; let msDrawOdds: number | null = null; let msAwayOdds: number | null = null; let htHomeOdds: number | null = null; let htDrawOdds: number | null = null; let htAwayOdds: number | null = null; for (const cat of match.oddCategories) { const catName = (cat.name || '').toLowerCase(); const isHT = catName.includes('1.yarı'); for (const sel of cat.selections) { const selName = (sel.name || '').toLowerCase(); if (!sel.oddValue) continue; const odd = parseFloat(sel.oddValue.toString()); if (catName.includes('maç sonucu') || catName.includes('1.yarı sonucu')) { if (selName === '1') { if (isHT) htHomeOdds = odd; else msHomeOdds = odd; } else if (selName === 'x' || selName === '0') { if (isHT) htDrawOdds = odd; else msDrawOdds = odd; } else if (selName === '2') { if (isHT) htAwayOdds = odd; else msAwayOdds = odd; } } } } if (!match.homeTeam || !match.awayTeam || !match.league) continue; reversalMatches.push({ id: match.id, homeTeam: match.homeTeam.name, awayTeam: match.awayTeam.name, league: match.league.name, htHome, htAway, ftHome, ftAway, htft, msHomeOdds, msDrawOdds, msAwayOdds, htHomeOdds, htDrawOdds, htAwayOdds, date: match.mstUtc, }); } } // Print HT/FT distribution console.log('\n📊 HT/FT DISTRIBUTION:'); for (const [key, count] of Object.entries(htftCounts)) { const pct = (count / totalMatches * 100).toFixed(2); const marker = (key === '1/2' || key === '2/1') ? ' ⚠️ REVERSAL' : ''; console.log(` ${key}: ${count} (${pct}%)${marker}`); } console.log(`\n⚠️ TOTAL REVERSAL MATCHES: ${reversalMatches.length} (${(reversalMatches.length / totalMatches * 100).toFixed(2)}%)`); // ANALYSIS 1: League distribution console.log('\n📈 ANALYSIS 1: LEAGUE DISTRIBUTION OF REVERSALS'); console.log('-'.repeat(80)); const leagueCounts: Record = {}; for (const match of matches) { if (!match.league) continue; const htHome = match.htScoreHome!; const htAway = match.htScoreAway!; const ftHome = match.scoreHome!; const ftAway = match.scoreAway!; const htResult = htHome > htAway ? '1' : htHome === htAway ? 'X' : '2'; const ftResult = ftHome > ftAway ? '1' : ftHome === ftAway ? 'X' : '2'; const htft = `${htResult}/${ftResult}`; const league = match.league.name; if (!leagueCounts[league]) leagueCounts[league] = { total: 0, reversal: 0 }; leagueCounts[league].total++; if (htft === '1/2' || htft === '2/1') leagueCounts[league].reversal++; } const leagueSorted = Object.entries(leagueCounts) .filter(([_, v]) => v.reversal > 0 && v.total >= 50) .sort((a, b) => (b[1].reversal / b[1].total) - (a[1].reversal / a[1].total)) .slice(0, 20); console.log('\nTop 20 leagues by reversal rate (min 50 matches):'); for (const [league, data] of leagueSorted) { const rate = (data.reversal / data.total * 100).toFixed(2); console.log(` ${league}: ${data.reversal}/${data.total} (${rate}%)`); } // ANALYSIS 2: Odds patterns console.log('\n📈 ANALYSIS 2: ODDS PATTERNS IN REVERSAL MATCHES'); console.log('-'.repeat(80)); const ms1_2 = reversalMatches.filter(m => m.htft === '1/2'); const ms2_1 = reversalMatches.filter(m => m.htft === '2/1'); console.log(`\n1/2 Reversals: ${ms1_2.length}`); console.log(`2/1 Reversals: ${ms2_1.length}`); // MS odds analysis for 1/2 const ms1_2_withOdds = ms1_2.filter(m => m.msHomeOdds && m.msAwayOdds); if (ms1_2_withOdds.length > 0) { const avgHomeOdd = ms1_2_withOdds.reduce((sum, m) => sum + m.msHomeOdds!, 0) / ms1_2_withOdds.length; const avgAwayOdd = ms1_2_withOdds.reduce((sum, m) => sum + m.msAwayOdds!, 0) / ms1_2_withOdds.length; const avgDrawOdd = ms1_2_withOdds.filter(m => m.msDrawOdds).reduce((sum, m) => sum + m.msDrawOdds!, 0) / ms1_2_withOdds.filter(m => m.msDrawOdds).length || 0; console.log(`\n 1/2 Matches - Average MS Odds:`); console.log(` Home Win: ${avgHomeOdd.toFixed(2)} (HT was WINNING!)`); console.log(` Draw: ${avgDrawOdd.toFixed(2)}`); console.log(` Away Win: ${avgAwayOdd.toFixed(2)} (but AWAY won FT!)`); // Favorite analysis let favoriteWon = 0; let underdogWon = 0; let noFavorite = 0; for (const m of ms1_2_withOdds) { if (m.msHomeOdds! < m.msAwayOdds!) { // Home was favorite, but away won = UNDERDOG underdogWon++; } else if (m.msAwayOdds! < m.msHomeOdds!) { // Away was favorite and won = FAVORITE favoriteWon++; } else { noFavorite++; } } console.log(`\n 1/2 - Who was favored vs who won:`); console.log(` Favorite won (Away was fav): ${favoriteWon} (${(favoriteWon / ms1_2_withOdds.length * 100).toFixed(1)}%)`); console.log(` Underdog won (Home was fav): ${underdogWon} (${(underdogWon / ms1_2_withOdds.length * 100).toFixed(1)}%) ⚠️`); } // MS odds analysis for 2/1 const ms2_1_withOdds = ms2_1.filter(m => m.msHomeOdds && m.msAwayOdds); if (ms2_1_withOdds.length > 0) { const avgHomeOdd = ms2_1_withOdds.reduce((sum, m) => sum + m.msHomeOdds!, 0) / ms2_1_withOdds.length; const avgAwayOdd = ms2_1_withOdds.reduce((sum, m) => sum + m.msAwayOdds!, 0) / ms2_1_withOdds.length; const avgDrawOdd = ms2_1_withOdds.filter(m => m.msDrawOdds).reduce((sum, m) => sum + m.msDrawOdds!, 0) / ms2_1_withOdds.filter(m => m.msDrawOdds).length || 0; console.log(`\n 2/1 Matches - Average MS Odds:`); console.log(` Home Win: ${avgHomeOdd.toFixed(2)} (HOME won FT!)`); console.log(` Draw: ${avgDrawOdd.toFixed(2)}`); console.log(` Away Win: ${avgAwayOdd.toFixed(2)} (Away was WINNING at HT!)`); let favoriteWon = 0; let underdogWon = 0; for (const m of ms2_1_withOdds) { if (m.msAwayOdds! < m.msHomeOdds!) { // Away was favorite at HT, but home won = UNDERDOG underdogWon++; } else if (m.msHomeOdds! < m.msAwayOdds!) { // Home was favorite and won = FAVORITE favoriteWon++; } } console.log(`\n 2/1 - Who was favored vs who won:`); console.log(` Favorite won (Home was fav): ${favoriteWon} (${(favoriteWon / ms2_1_withOdds.length * 100).toFixed(1)}%)`); console.log(` Underdog won (Away was fav): ${underdogWon} (${(underdogWon / ms2_1_withOdds.length * 100).toFixed(1)}%) ⚠️`); } // ANALYSIS 3: Suspicious patterns console.log('\n📈 ANALYSIS 3: SUSPICIOUS PATTERNS'); console.log('-'.repeat(80)); // Pattern 1: Heavy favorite loses after leading (1/2 with low home odds) const suspicious_1_2 = ms1_2_withOdds.filter(m => m.msHomeOdds! < 1.5); console.log(`\n⚠️ PATTERN 1: Heavy Home Favorite loses after HT lead (MS Home Odds < 1.5):`); console.log(` Count: ${suspicious_1_2.length}`); if (suspicious_1_2.length > 0) { const avgOdd = suspicious_1_2.reduce((sum, m) => sum + m.msHomeOdds!, 0) / suspicious_1_2.length; console.log(` Avg Home Odds: ${avgOdd.toFixed(2)}`); console.log(` Sample matches:`); suspicious_1_2.slice(0, 5).forEach(m => { console.log(` ${m.league}: ${m.homeTeam} (${m.msHomeOdds}) vs ${m.awayTeam} (${m.msAwayOdds}) => HT: ${m.htHome}-${m.htAway}, FT: ${m.ftHome}-${m.ftAway}`); }); } // Pattern 2: Heavy away favorite loses after leading (2/1 with low away odds) const suspicious_2_1 = ms2_1_withOdds.filter(m => m.msAwayOdds! < 1.5); console.log(`\n⚠️ PATTERN 2: Heavy Away Favorite loses after HT lead (MS Away Odds < 1.5):`); console.log(` Count: ${suspicious_2_1.length}`); if (suspicious_2_1.length > 0) { const avgOdd = suspicious_2_1.reduce((sum, m) => sum + m.msAwayOdds!, 0) / suspicious_2_1.length; console.log(` Avg Away Odds: ${avgOdd.toFixed(2)}`); console.log(` Sample matches:`); suspicious_2_1.slice(0, 5).forEach(m => { console.log(` ${m.league}: ${m.homeTeam} (${m.msHomeOdds}) vs ${m.awayTeam} (${m.msAwayOdds}) => HT: ${m.htHome}-${m.htAway}, FT: ${m.ftHome}-${m.ftAway}`); }); } // ANALYSIS 4: HT Odds vs MS Odds correlation console.log('\n📈 ANALYSIS 4: HT ODDS CORRELATION'); console.log('-'.repeat(80)); const withHTOdds = reversalMatches.filter(m => m.htHomeOdds && m.htAwayOdds); if (withHTOdds.length > 0) { console.log(`\n Matches with HT odds: ${withHTOdds.length}`); let htCorrectlyPredicted = 0; for (const m of withHTOdds) { const htFav = m.htHomeOdds! < m.htAwayOdds! ? '1' : m.htAwayOdds! < m.htHomeOdds! ? '2' : 'X'; const htActual = m.htHome > m.htAway ? '1' : m.htAway > m.htHome ? '2' : 'X'; if (htFav === htActual) htCorrectlyPredicted++; } console.log(` HT Favorite correctly led at HT: ${htCorrectlyPredicted}/${withHTOdds.length} (${(htCorrectlyPredicted / withHTOdds.length * 100).toFixed(1)}%)`); // How often did HT favorite lose FT? let htFavoriteLostFT = 0; for (const m of withHTOdds) { const htFav = m.htHomeOdds! < m.htAwayOdds! ? '1' : m.htAwayOdds! < m.htHomeOdds! ? '2' : 'X'; const ftActual = m.ftHome > m.ftAway ? '1' : m.ftAway > m.ftHome ? '2' : 'X'; if (htFav !== ftActual) htFavoriteLostFT++; } console.log(` HT Favorite lost FT: ${htFavoriteLostFT}/${withHTOdds.length} (${(htFavoriteLostFT / withHTOdds.length * 100).toFixed(1)}%) ⚠️`); } // ANALYSIS 5: Score patterns console.log('\n📈 ANALYSIS 5: SCORE PATTERNS IN REVERSALS'); console.log('-'.repeat(80)); // HT score distribution for reversals const htScores: Record = {}; for (const m of reversalMatches) { const key = `${m.htHome}-${m.htAway}`; htScores[key] = (htScores[key] || 0) + 1; } console.log('\nMost common HT scores in reversal matches:'); Object.entries(htScores) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .forEach(([score, count]) => { console.log(` HT ${score}: ${count} matches`); }); // FT score distribution const ftScores: Record = {}; for (const m of reversalMatches) { const key = `${m.ftHome}-${m.ftAway}`; ftScores[key] = (ftScores[key] || 0) + 1; } console.log('\nMost common FT scores in reversal matches:'); Object.entries(ftScores) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .forEach(([score, count]) => { console.log(` FT ${score}: ${count} matches`); }); // ANALYSIS 6: Goal difference patterns console.log('\n📈 ANALYSIS 6: COMEBACK MAGNITUDE'); console.log('-'.repeat(80)); let comebackBy1 = 0; let comebackBy2 = 0; let comebackBy3Plus = 0; for (const m of reversalMatches) { const htDiff = Math.abs(m.htHome - m.htAway); const ftDiff = Math.abs(m.ftHome - m.ftAway); if (m.htft === '1/2') { // Home was leading, away won const margin = (m.ftAway - m.ftHome); if (margin === 1) comebackBy1++; else if (margin === 2) comebackBy2++; else comebackBy3Plus++; } else { // Away was leading, home won const margin = (m.ftHome - m.ftAway); if (margin === 1) comebackBy1++; else if (margin === 2) comebackBy2++; else comebackBy3Plus++; } } console.log(`\n Comeback by 1 goal: ${comebackBy1} (${(comebackBy1 / reversalMatches.length * 100).toFixed(1)}%)`); console.log(` Comeback by 2 goals: ${comebackBy2} (${(comebackBy2 / reversalMatches.length * 100).toFixed(1)}%)`); console.log(` Comeback by 3+ goals: ${comebackBy3Plus} (${(comebackBy3Plus / reversalMatches.length * 100).toFixed(1)}%) ⚠️`); // Show extreme comebacks const extremeComebacks = reversalMatches .filter(m => { if (m.htft === '1/2') return (m.ftAway - m.ftHome) >= 2; return (m.ftHome - m.ftAway) >= 2; }) .sort((a, b) => { const diffA = a.htft === '1/2' ? (a.ftAway - a.ftHome) : (a.ftHome - a.ftAway); const diffB = b.htft === '1/2' ? (b.ftAway - b.ftHome) : (b.ftHome - b.ftAway); return diffB - diffA; }) .slice(0, 10); console.log('\nTop 10 most extreme comebacks:'); extremeComebacks.forEach(m => { const diff = m.htft === '1/2' ? (m.ftAway - m.ftHome) : (m.ftHome - m.ftAway); console.log(` ${m.league}: ${m.homeTeam} vs ${m.awayTeam} | HT: ${m.htHome}-${m.htAway} => FT: ${m.ftHome}-${m.ftAway} (Diff: ${diff})`); }); console.log('\n' + '='.repeat(80)); console.log('✅ ANALYSIS COMPLETE'); console.log('='.repeat(80)); await prisma.$disconnect(); } analyzeReversalMatches().catch(console.error);