import gradio as gr import pandas as pd import requests import numpy as np from datetime import datetime import json from collections import Counter import os from io import StringIO # Configuración de OpenAI OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY', '') # Mapeo de códigos de liga LEAGUE_CODES = { 'E0': 'Premier League', 'E1': 'Championship', 'E2': 'League One', 'E3': 'League Two', 'SP1': 'La Liga', 'SP2': 'La Liga 2', 'I1': 'Serie A', 'I2': 'Serie B', 'D1': 'Bundesliga', 'D2': 'Bundesliga 2', 'F1': 'Ligue 1', 'F2': 'Ligue 2', 'N1': 'Eredivisie', 'B1': 'Jupiler League', 'P1': 'Liga Portugal', 'T1': 'Super Lig', 'G1': 'Super League' } class FootballAnalyzer: def __init__(self): self.fixtures_df = None self.league_data = None self.selected_match = None self.analysis_data = None def download_fixtures(self): """Descarga el archivo fixtures.csv""" try: url = 'https://www.football-data.co.uk/fixtures.csv' response = requests.get(url, timeout=10) response.raise_for_status() with open('fixtures.csv', 'wb') as f: f.write(response.content) self.fixtures_df = pd.read_csv('fixtures.csv') matches = [] for idx, row in self.fixtures_df.iterrows(): league_name = LEAGUE_CODES.get(row['Div'], row['Div']) match_str = f"{league_name} | {row['Date']} {row['Time']} | {row['HomeTeam']} vs {row['AwayTeam']}" matches.append(match_str) return gr.update(choices=matches, value=None), "✅ Fixtures descargados exitosamente" except Exception as e: return gr.update(choices=[]), f"❌ Error: {str(e)}" def show_bet365_odds(self, selected_match_str): """Muestra las cuotas de Bet365""" if not selected_match_str or self.fixtures_df is None: return "Selecciona un partido primero" try: parts = selected_match_str.split('|') teams = parts[2].strip().split(' vs ') home_team = teams[0].strip() away_team = teams[1].strip() match_row = self.fixtures_df[ (self.fixtures_df['HomeTeam'] == home_team) & (self.fixtures_df['AwayTeam'] == away_team) ].iloc[0] self.selected_match = match_row odds_info = f""" ### 📊 Cuotas Bet365 **{home_team} vs {away_team}** - 🏠 **Local ({home_team}):** {match_row.get('B365H', 'N/A')} - 🤝 **Empate:** {match_row.get('B365D', 'N/A')} - ✈️ **Visitante ({away_team}):** {match_row.get('B365A', 'N/A')} **Liga:** {LEAGUE_CODES.get(match_row['Div'], match_row['Div'])} **Fecha:** {match_row['Date']} {match_row['Time']} """ return odds_info except Exception as e: return f"❌ Error: {str(e)}" def download_league_data(self, selected_match_str): """Descarga datos históricos de la liga""" if not selected_match_str or self.selected_match is None: return "Primero selecciona un partido y visualiza las cuotas", None, None try: league_code = self.selected_match['Div'] url = f'https://www.football-data.co.uk/mmz4281/2526/{league_code}.csv' response = requests.get(url, timeout=10) response.raise_for_status() filename = f'league_{league_code}.csv' with open(filename, 'wb') as f: f.write(response.content) self.league_data = pd.read_csv(filename) message = f"✅ Datos descargados: {len(self.league_data)} partidos" # Generar análisis analysis_md, tables_html = self.analyze_teams() return message, analysis_md, tables_html except Exception as e: return f"❌ Error: {str(e)}", None, None def calculate_statistics(self, values): """Calcula todas las estadísticas relevantes""" if len(values) == 0: return { 'media': 0, 'mediana': 0, 'moda': 0, 'volatilidad': 0, 'desv_std': 0, 'min': 0, 'max': 0, 'total': 0, 'p25': 0, 'p75': 0, 'coef_var': 0 } values_clean = [v for v in values if pd.notna(v) and str(v).strip() != ''] if len(values_clean) == 0: return { 'media': 0, 'mediana': 0, 'moda': 0, 'volatilidad': 0, 'desv_std': 0, 'min': 0, 'max': 0, 'total': 0, 'p25': 0, 'p75': 0, 'coef_var': 0 } values_clean = [float(v) for v in values_clean] media = np.mean(values_clean) mediana = np.median(values_clean) counter = Counter(values_clean) moda = counter.most_common(1)[0][0] if counter else 0 desv_std = np.std(values_clean) volatilidad = (desv_std / media * 100) if media != 0 else 0 p25 = np.percentile(values_clean, 25) p75 = np.percentile(values_clean, 75) return { 'media': round(media, 2), 'mediana': round(mediana, 2), 'moda': round(moda, 2), 'volatilidad': round(volatilidad, 2), 'desv_std': round(desv_std, 2), 'min': round(min(values_clean), 2), 'max': round(max(values_clean), 2), 'total': round(sum(values_clean), 2), 'p25': round(p25, 2), 'p75': round(p75, 2), 'coef_var': round(volatilidad, 2) } def analyze_team_comprehensive(self, team_data, team_name, is_home): """Análisis comprehensivo de un equipo con TODAS las categorías""" if len(team_data) == 0: return None # Filtrar partidos como local o visitante if is_home: team_matches = team_data[team_data['HomeTeam'] == team_name].copy() else: team_matches = team_data[team_data['AwayTeam'] == team_name].copy() if len(team_matches) == 0: return None # Obtener últimos 5 partidos last_5 = team_matches.tail(5).copy() # Definir todas las categorías a analizar categories = {} # GOLES if is_home: categories['Goles Favor'] = { 'season': team_matches['FTHG'].astype(float), 'last5': last_5['FTHG'].astype(float) } categories['Goles Contra'] = { 'season': team_matches['FTAG'].astype(float), 'last5': last_5['FTAG'].astype(float) } categories['Goles Medio Tiempo Favor'] = { 'season': team_matches['HTHG'].astype(float) if 'HTHG' in team_matches.columns else pd.Series([0]), 'last5': last_5['HTHG'].astype(float) if 'HTHG' in last_5.columns else pd.Series([0]) } categories['Goles Medio Tiempo Contra'] = { 'season': team_matches['HTAG'].astype(float) if 'HTAG' in team_matches.columns else pd.Series([0]), 'last5': last_5['HTAG'].astype(float) if 'HTAG' in last_5.columns else pd.Series([0]) } else: categories['Goles Favor'] = { 'season': team_matches['FTAG'].astype(float), 'last5': last_5['FTAG'].astype(float) } categories['Goles Contra'] = { 'season': team_matches['FTHG'].astype(float), 'last5': last_5['FTHG'].astype(float) } categories['Goles Medio Tiempo Favor'] = { 'season': team_matches['HTAG'].astype(float) if 'HTAG' in team_matches.columns else pd.Series([0]), 'last5': last_5['HTAG'].astype(float) if 'HTAG' in last_5.columns else pd.Series([0]) } categories['Goles Medio Tiempo Contra'] = { 'season': team_matches['HTHG'].astype(float) if 'HTHG' in team_matches.columns else pd.Series([0]), 'last5': last_5['HTHG'].astype(float) if 'HTHG' in last_5.columns else pd.Series([0]) } # TIROS if is_home: categories['Tiros Totales'] = { 'season': team_matches['HS'].astype(float) if 'HS' in team_matches.columns else pd.Series([0]), 'last5': last_5['HS'].astype(float) if 'HS' in last_5.columns else pd.Series([0]) } categories['Tiros al Arco'] = { 'season': team_matches['HST'].astype(float) if 'HST' in team_matches.columns else pd.Series([0]), 'last5': last_5['HST'].astype(float) if 'HST' in last_5.columns else pd.Series([0]) } categories['Tiros Contra'] = { 'season': team_matches['AS'].astype(float) if 'AS' in team_matches.columns else pd.Series([0]), 'last5': last_5['AS'].astype(float) if 'AS' in last_5.columns else pd.Series([0]) } categories['Tiros al Arco Contra'] = { 'season': team_matches['AST'].astype(float) if 'AST' in team_matches.columns else pd.Series([0]), 'last5': last_5['AST'].astype(float) if 'AST' in last_5.columns else pd.Series([0]) } else: categories['Tiros Totales'] = { 'season': team_matches['AS'].astype(float) if 'AS' in team_matches.columns else pd.Series([0]), 'last5': last_5['AS'].astype(float) if 'AS' in last_5.columns else pd.Series([0]) } categories['Tiros al Arco'] = { 'season': team_matches['AST'].astype(float) if 'AST' in team_matches.columns else pd.Series([0]), 'last5': last_5['AST'].astype(float) if 'AST' in last_5.columns else pd.Series([0]) } categories['Tiros Contra'] = { 'season': team_matches['HS'].astype(float) if 'HS' in team_matches.columns else pd.Series([0]), 'last5': last_5['HS'].astype(float) if 'HS' in last_5.columns else pd.Series([0]) } categories['Tiros al Arco Contra'] = { 'season': team_matches['HST'].astype(float) if 'HST' in team_matches.columns else pd.Series([0]), 'last5': last_5['HST'].astype(float) if 'HST' in last_5.columns else pd.Series([0]) } # CORNERS if is_home: categories['Corners Favor'] = { 'season': team_matches['HC'].astype(float) if 'HC' in team_matches.columns else pd.Series([0]), 'last5': last_5['HC'].astype(float) if 'HC' in last_5.columns else pd.Series([0]) } categories['Corners Contra'] = { 'season': team_matches['AC'].astype(float) if 'AC' in team_matches.columns else pd.Series([0]), 'last5': last_5['AC'].astype(float) if 'AC' in last_5.columns else pd.Series([0]) } else: categories['Corners Favor'] = { 'season': team_matches['AC'].astype(float) if 'AC' in team_matches.columns else pd.Series([0]), 'last5': last_5['AC'].astype(float) if 'AC' in last_5.columns else pd.Series([0]) } categories['Corners Contra'] = { 'season': team_matches['HC'].astype(float) if 'HC' in team_matches.columns else pd.Series([0]), 'last5': last_5['HC'].astype(float) if 'HC' in last_5.columns else pd.Series([0]) } # FALTAS if is_home: categories['Faltas Cometidas'] = { 'season': team_matches['HF'].astype(float) if 'HF' in team_matches.columns else pd.Series([0]), 'last5': last_5['HF'].astype(float) if 'HF' in last_5.columns else pd.Series([0]) } categories['Faltas Recibidas'] = { 'season': team_matches['AF'].astype(float) if 'AF' in team_matches.columns else pd.Series([0]), 'last5': last_5['AF'].astype(float) if 'AF' in last_5.columns else pd.Series([0]) } else: categories['Faltas Cometidas'] = { 'season': team_matches['AF'].astype(float) if 'AF' in team_matches.columns else pd.Series([0]), 'last5': last_5['AF'].astype(float) if 'AF' in last_5.columns else pd.Series([0]) } categories['Faltas Recibidas'] = { 'season': team_matches['HF'].astype(float) if 'HF' in team_matches.columns else pd.Series([0]), 'last5': last_5['HF'].astype(float) if 'HF' in last_5.columns else pd.Series([0]) } # TARJETAS if is_home: categories['Tarjetas Amarillas'] = { 'season': team_matches['HY'].astype(float) if 'HY' in team_matches.columns else pd.Series([0]), 'last5': last_5['HY'].astype(float) if 'HY' in last_5.columns else pd.Series([0]) } categories['Tarjetas Rojas'] = { 'season': team_matches['HR'].astype(float) if 'HR' in team_matches.columns else pd.Series([0]), 'last5': last_5['HR'].astype(float) if 'HR' in last_5.columns else pd.Series([0]) } categories['Tarjetas Amarillas Contra'] = { 'season': team_matches['AY'].astype(float) if 'AY' in team_matches.columns else pd.Series([0]), 'last5': last_5['AY'].astype(float) if 'AY' in last_5.columns else pd.Series([0]) } categories['Tarjetas Rojas Contra'] = { 'season': team_matches['AR'].astype(float) if 'AR' in team_matches.columns else pd.Series([0]), 'last5': last_5['AR'].astype(float) if 'AR' in last_5.columns else pd.Series([0]) } else: categories['Tarjetas Amarillas'] = { 'season': team_matches['AY'].astype(float) if 'AY' in team_matches.columns else pd.Series([0]), 'last5': last_5['AY'].astype(float) if 'AY' in last_5.columns else pd.Series([0]) } categories['Tarjetas Rojas'] = { 'season': team_matches['AR'].astype(float) if 'AR' in team_matches.columns else pd.Series([0]), 'last5': last_5['AR'].astype(float) if 'AR' in last_5.columns else pd.Series([0]) } categories['Tarjetas Amarillas Contra'] = { 'season': team_matches['HY'].astype(float) if 'HY' in team_matches.columns else pd.Series([0]), 'last5': last_5['HY'].astype(float) if 'HY' in last_5.columns else pd.Series([0]) } categories['Tarjetas Rojas Contra'] = { 'season': team_matches['HR'].astype(float) if 'HR' in team_matches.columns else pd.Series([0]), 'last5': last_5['HR'].astype(float) if 'HR' in last_5.columns else pd.Series([0]) } # Calcular estadísticas para cada categoría stats_dict = {} for cat_name, cat_data in categories.items(): stats_dict[cat_name] = { 'temporada': self.calculate_statistics(cat_data['season'].tolist()), 'ultimos_5': self.calculate_statistics(cat_data['last5'].tolist()) } # Calcular resultados (W/D/L) results = [] for _, match in team_matches.iterrows(): if is_home: if match['FTHG'] > match['FTAG']: results.append('W') elif match['FTHG'] < match['FTAG']: results.append('L') else: results.append('D') else: if match['FTAG'] > match['FTHG']: results.append('W') elif match['FTAG'] < match['FTHG']: results.append('L') else: results.append('D') last_5_results = results[-5:] if len(results) >= 5 else results return { 'partidos_totales': len(team_matches), 'victorias': results.count('W'), 'empates': results.count('D'), 'derrotas': results.count('L'), 'forma_reciente': ''.join(last_5_results), 'estadisticas': stats_dict, 'partidos_last5': len(last_5) } def create_stats_tables(self, home_analysis, away_analysis, home_team, away_team): """Crea tablas HTML con todas las estadísticas""" html = f""" """ def format_volatility(vol): if vol < 30: return f'{vol}%' elif vol < 60: return f'{vol}%' else: return f'{vol}%' # Tabla para cada equipo for team_name, analysis, is_home in [(home_team, home_analysis, True), (away_team, away_analysis, False)]: position = "Local" if is_home else "Visitante" html += f'
🏟️ {team_name} ({position})
' html += f'

Récord: {analysis["victorias"]}V - {analysis["empates"]}E - {analysis["derrotas"]}D | ' html += f'Forma Reciente: {analysis["forma_reciente"]}

' # Tabla de todas las categorías html += '' html += ''' ''' for cat_name, cat_stats in analysis['estadisticas'].items(): # Fila temporada completa s = cat_stats['temporada'] html += f''' ''' # Fila últimos 5 l5 = cat_stats['ultimos_5'] html += f''' ''' html += '
Categoría Período Media Mediana Moda Min-Max Volatilidad Desv.Std Total
{cat_name} Temporada ({analysis["partidos_totales"]} PJ) {s["media"]} {s["mediana"]} {s["moda"]} {s["min"]} - {s["max"]} {format_volatility(s["volatilidad"])} {s["desv_std"]} {s["total"]}
Últimos 5 {l5["media"]} {l5["mediana"]} {l5["moda"]} {l5["min"]} - {l5["max"]} {format_volatility(l5["volatilidad"])} {l5["desv_std"]} {l5["total"]}
' return html def analyze_teams(self): """Realiza análisis completo y genera reporte + tablas""" if self.league_data is None or self.selected_match is None: return "No hay datos para analizar", "" home_team = self.selected_match['HomeTeam'] away_team = self.selected_match['AwayTeam'] # Filtrar datos home_data = self.league_data[ (self.league_data['HomeTeam'] == home_team) | (self.league_data['AwayTeam'] == home_team) ] away_data = self.league_data[ (self.league_data['HomeTeam'] == away_team) | (self.league_data['AwayTeam'] == away_team) ] # Análisis completo home_analysis = self.analyze_team_comprehensive(home_data, home_team, True) away_analysis = self.analyze_team_comprehensive(away_data, away_team, False) if home_analysis is None or away_analysis is None: return "No hay suficientes datos históricos", "" # Guardar para OpenAI self.analysis_data = { 'home_team': home_team, 'away_team': away_team, 'home_analysis': home_analysis, 'away_analysis': away_analysis, 'bet365_odds': { 'home': self.selected_match.get('B365H'), 'draw': self.selected_match.get('B365D'), 'away': self.selected_match.get('B365A') } } # Generar reporte markdown report = f""" # 📊 ANÁLISIS ESTADÍSTICO COMPLETO ## ⚽ {home_team} (Local) vs {away_team} (Visitante) ### Resumen General **{home_team} (Local)** - Partidos: {home_analysis['partidos_totales']} - Récord: {home_analysis['victorias']}V - {home_analysis['empates']}E - {home_analysis['derrotas']}D - Forma reciente: {home_analysis['forma_reciente']} **{away_team} (Visitante)** - Partidos: {away_analysis['partidos_totales']} - Récord: {away_analysis['victorias']}V - {away_analysis['empates']}E - {away_analysis['derrotas']}D - Forma reciente: {away_analysis['forma_reciente']} --- ### 📈 Interpretación de Volatilidad - **< 30%**: Muy consistente (predecible) - **30-60%**: Moderadamente consistente - **> 60%**: Muy inconsistente (impredecible) Las tablas detalladas se muestran a continuación con todas las categorías analizadas. """ # Generar tablas HTML tables_html = self.create_stats_tables(home_analysis, away_analysis, home_team, away_team) return report, tables_html def get_ai_prediction(self, api_key): """Obtiene predicción profesional usando el prompt de apostador cuantitativo""" if not hasattr(self, 'analysis_data') or self.analysis_data is None: return "Primero debes descargar y analizar los datos del partido" if not api_key: return "⚠️ Por favor ingresa tu API Key de OpenAI" try: # Preparar datos estructurados para el prompt home = self.analysis_data['home_analysis'] away = self.analysis_data['away_analysis'] # Construir el prompt del apostador profesional prompt = f"""Actúa como un apostador profesional de fútbol y analista cuantitativo senior, especializado en valor esperado (EV), modelos probabilísticos (Poisson), simulación Monte Carlo, criterio de Kelly completo y dinámica de mercados de apuestas. Tu objetivo es maximizar el crecimiento logarítmico del capital, identificando ineficiencias reales del mercado, validándolas con movimiento de líneas en Internet, y descartando apuestas con riesgo de ruina inaceptable, incluso si tienen EV positivo. ## PARTIDO A ANALIZAR **{self.analysis_data['home_team']} (Local) vs {self.analysis_data['away_team']} (Visitante)** ### CUOTAS BET365 (Línea actual) - Local (1): {self.analysis_data['bet365_odds']['home']} - Empate (X): {self.analysis_data['bet365_odds']['draw']} - Visitante (2): {self.analysis_data['bet365_odds']['away']} ## DATOS ESTADÍSTICOS COMPLETOS ### {self.analysis_data['home_team']} (LOCAL) **Rendimiento General:** - Partidos como local: {home['partidos_totales']} - Récord: {home['victorias']}V-{home['empates']}E-{home['derrotas']}D - Forma reciente (últimos 5): {home['forma_reciente']} **Goles:** - Media temporada: {home['estadisticas']['Goles Favor']['temporada']['media']} goles/partido - Media últimos 5: {home['estadisticas']['Goles Favor']['ultimos_5']['media']} goles/partido - Mediana temporada: {home['estadisticas']['Goles Favor']['temporada']['mediana']} - Moda temporada: {home['estadisticas']['Goles Favor']['temporada']['moda']} - Volatilidad temporada: {home['estadisticas']['Goles Favor']['temporada']['volatilidad']}% - Volatilidad últimos 5: {home['estadisticas']['Goles Favor']['ultimos_5']['volatilidad']}% **Goles en Contra:** - Media temporada: {home['estadisticas']['Goles Contra']['temporada']['media']} goles/partido - Media últimos 5: {home['estadisticas']['Goles Contra']['ultimos_5']['media']} goles/partido - Volatilidad temporada: {home['estadisticas']['Goles Contra']['temporada']['volatilidad']}% **Tiros al Arco:** - Media temporada: {home['estadisticas']['Tiros al Arco']['temporada']['media']} - Media últimos 5: {home['estadisticas']['Tiros al Arco']['ultimos_5']['media']} - Volatilidad: {home['estadisticas']['Tiros al Arco']['temporada']['volatilidad']}% **Corners:** - Media temporada: {home['estadisticas']['Corners Favor']['temporada']['media']} - Media últimos 5: {home['estadisticas']['Corners Favor']['ultimos_5']['media']} - Volatilidad: {home['estadisticas']['Corners Favor']['temporada']['volatilidad']}% **Disciplina:** - Tarjetas amarillas (media): {home['estadisticas']['Tarjetas Amarillas']['temporada']['media']} - Faltas (media): {home['estadisticas']['Faltas Cometidas']['temporada']['media']} ### {self.analysis_data['away_team']} (VISITANTE) **Rendimiento General:** - Partidos como visitante: {away['partidos_totales']} - Récord: {away['victorias']}V-{away['empates']}E-{away['derrotas']}D - Forma reciente (últimos 5): {away['forma_reciente']} **Goles:** - Media temporada: {away['estadisticas']['Goles Favor']['temporada']['media']} goles/partido - Media últimos 5: {away['estadisticas']['Goles Favor']['ultimos_5']['media']} goles/partido - Mediana temporada: {away['estadisticas']['Goles Favor']['temporada']['mediana']} - Moda temporada: {away['estadisticas']['Goles Favor']['temporada']['moda']} - Volatilidad temporada: {away['estadisticas']['Goles Favor']['temporada']['volatilidad']}% - Volatilidad últimos 5: {away['estadisticas']['Goles Favor']['ultimos_5']['volatilidad']}% **Goles en Contra:** - Media temporada: {away['estadisticas']['Goles Contra']['temporada']['media']} goles/partido - Media últimos 5: {away['estadisticas']['Goles Contra']['ultimos_5']['media']} goles/partido - Volatilidad temporada: {away['estadisticas']['Goles Contra']['temporada']['volatilidad']}% **Tiros al Arco:** - Media temporada: {away['estadisticas']['Tiros al Arco']['temporada']['media']} - Media últimos 5: {away['estadisticas']['Tiros al Arco']['ultimos_5']['media']} - Volatilidad: {away['estadisticas']['Tiros al Arco']['temporada']['volatilidad']}% **Corners:** - Media temporada: {away['estadisticas']['Corners Favor']['temporada']['media']} - Media últimos 5: {away['estadisticas']['Corners Favor']['ultimos_5']['media']} - Volatilidad: {away['estadisticas']['Corners Favor']['temporada']['volatilidad']}% **Disciplina:** - Tarjetas amarillas (media): {away['estadisticas']['Tarjetas Amarillas']['temporada']['media']} - Faltas (media): {away['estadisticas']['Faltas Cometidas']['temporada']['media']} ## INSTRUCCIONES DE ANÁLISIS Debes analizar AL MENOS 5 categorías de mercado diferentes de forma independiente: 1. **Goles**: Over/Under, Ambos Marcan, Asian Handicap 2. **Resultado**: 1X2, Draw No Bet, Double Chance 3. **Resultado Medio Tiempo**: HT 1X2, HT Asian Handicap 4. **Corners**: Totales, Por equipo, Hándicap asiático 5. **Mercados alternativos**: Líneas asiáticas secundarias Para CADA categoría que analices, DEBES proporcionar: ### 1. Modelado Probabilístico (Poisson) - Calcula λ (lambda) local y visitante basado en las medias - Deriva probabilidades para cada resultado - Considera la volatilidad para ajustar el modelo ### 2. Conversión de Cuotas y Valor Esperado - Probabilidad implícita de las cuotas Bet365 - Probabilidad real estimada (tu modelo) - **EV = (P_real × Cuota) - 1** - Solo continuar si **EV > 0** ### 3. Simulación Monte Carlo (10,000+ iteraciones) - P&L esperado - Varianza y desviación estándar - Percentiles (5%, 25%, 50%, 75%, 95%) - Probabilidad de pérdida ### 4. Stake con Kelly Completo - **f* = [(b × p) - q] / b** - b = cuota - 1 - p = probabilidad real - q = 1 - p - % de banca a apostar - Crecimiento esperado - Riesgo de ruina ### 5. Formato de Salida Estricto para CADA apuesta recomendada: ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ MERCADO: [Nombre del mercado] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📊 LÍNEA Y CUOTAS • Apuesta: [descripción exacta] • Cuota: [valor] 🎲 MODELO PROBABILÍSTICO • λ Local: [valor] • λ Visitante: [valor] • Probabilidad Real: [XX]% • Probabilidad Implícita: [XX]% 💰 VALOR ESPERADO • EV: +[XX]% • Edge sobre mercado: [XX]% 🎰 SIMULACIÓN MONTE CARLO (10,000 iteraciones) • P&L Esperado: +$[XX] por $100 apostados • Desv. Estándar: $[XX] • Percentil 5%: $[XX] • Percentil 95%: $[XX] • Probabilidad de pérdida: [XX]% 💎 KELLY COMPLETO • Stake óptimo: [X.XX]% de banca • Crecimiento esperado: +[XX]% • Volatilidad de banca: [XX]% ⚠️ RIESGO DE RUINA • Riesgo: [BAJO / MEDIO / ALTO] • Max Drawdown esperado: [XX]% • Confianza: [X]/10 📝 JUSTIFICACIÓN [Explicación técnica del edge identificado, por qué el mercado está mal preciado, factores que respaldan la apuesta] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` ## PRINCIPIOS FINALES - NO fuerces apuestas si no hay valor real - Descarta apuestas con riesgo de ruina alto aunque tengan EV+ - Prioriza supervivencia del capital sobre maximización teórica - Si necesitas información adicional (lesiones, clima, árbitro), búscala - Considera el contraste entre volatilidad de temporada completa vs últimos 5 partidos - La consistencia (baja volatilidad) aumenta la confiabilidad del modelo Proporciona tu análisis completo ahora.""" # Llamar a OpenAI headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {api_key}' } data = { 'model': 'gpt-4o', 'messages': [{'role': 'user', 'content': prompt}], 'temperature': 0.7, 'max_tokens': 4000 } response = requests.post( 'https://api.openai.com/v1/chat/completions', headers=headers, json=data, timeout=120 ) if response.status_code == 200: result = response.json() return result['choices'][0]['message']['content'] else: return f"❌ Error en API OpenAI: {response.status_code}\n{response.text}" except Exception as e: return f"❌ Error: {str(e)}" # Crear instancia analyzer = FootballAnalyzer() # Interfaz Gradio with gr.Blocks(title="⚽ Analizador Profesional de Apuestas de Fútbol", theme=gr.themes.Soft()) as app: gr.Markdown(""" # ⚽ Analizador Profesional de Apuestas de Fútbol ### 📊 Análisis cuantitativo completo con todas las categorías estadísticas **Incluye**: Goles, Tiros, Corners, Faltas, Tarjetas | Temporada completa + Últimos 5 partidos | Media, Mediana, Moda, Volatilidad """) with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📥 Paso 1: Descargar Fixtures") download_btn = gr.Button("Descargar Fixtures", variant="primary", size="lg") status_fixtures = gr.Textbox(label="Estado", interactive=False) gr.Markdown("### 🎯 Paso 2: Seleccionar Partido") match_dropdown = gr.Dropdown(label="Partidos Disponibles", choices=[], interactive=True) gr.Markdown("### 📊 Paso 3: Ver Cuotas") show_odds_btn = gr.Button("Ver Cuotas Bet365", variant="secondary") gr.Markdown("### 📈 Paso 4: Análisis Completo") analyze_btn = gr.Button("Descargar Datos y Analizar", variant="primary", size="lg") status_analysis = gr.Textbox(label="Estado", interactive=False) with gr.Column(scale=2): odds_output = gr.Markdown(label="Cuotas") analysis_output = gr.Markdown(label="Resumen") gr.Markdown("## 📊 Tablas Estadísticas Detalladas") tables_output = gr.HTML(label="Estadísticas Completas") gr.Markdown("---") gr.Markdown("## 🤖 Análisis Profesional con IA (Apostador Cuantitativo)") gr.Markdown(""" **Incluye**: Modelado Poisson, Simulación Monte Carlo, Criterio de Kelly, Análisis de 5+ categorías de mercado """) with gr.Row(): api_key_input = gr.Textbox( label="OpenAI API Key", placeholder="sk-...", type="password" ) predict_btn = gr.Button("🔮 Análisis Cuantitativo Completo", variant="primary", size="lg") ai_prediction_output = gr.Markdown() # Events download_btn.click( fn=analyzer.download_fixtures, outputs=[match_dropdown, status_fixtures] ) show_odds_btn.click( fn=analyzer.show_bet365_odds, inputs=[match_dropdown], outputs=[odds_output] ) analyze_btn.click( fn=analyzer.download_league_data, inputs=[match_dropdown], outputs=[status_analysis, analysis_output, tables_output] ) predict_btn.click( fn=analyzer.get_ai_prediction, inputs=[api_key_input], outputs=[ai_prediction_output] ) gr.Markdown(""" --- ### 📝 Leyenda de Volatilidad - **Verde (< 30%)**: Muy consistente - Alta predictibilidad - **Naranja (30-60%)**: Moderadamente consistente - **Rojo (> 60%)**: Muy inconsistente - Baja predictibilidad ### 🎯 Categorías Analizadas **Goles**: A favor, En contra, Medio tiempo | **Tiros**: Totales, Al arco, A favor y contra **Corners**: A favor, Contra | **Faltas**: Cometidas, Recibidas **Tarjetas**: Amarillas, Rojas (propias y del rival) Cada categoría incluye: Media, Mediana, Moda, Min-Max, Volatilidad, Desv.Std, Total """) if __name__ == "__main__": app.launch(share=True)