#!/usr/bin/env python3 """ Backtest - Analyse des performances des prédictions Calcule ROI, précision, et métriques par type de pari """ import sqlite3 import os from datetime import datetime, timedelta from collections import defaultdict DB_PATH = os.environ.get("DB_PATH", "/home/h3r7/turf_scraper/turf.db") def get_db(): return sqlite3.connect(DB_PATH) def calculate_backtest(start_date=None, end_date=None): """Calcule les statistiques de backtest""" conn = get_db() if not start_date: start_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d') if not end_date: end_date = datetime.now().strftime('%Y-%m-%d') # Récupérer toutes les recommandations avec résultats query = """ SELECT r.date, r.race_name, r.type_pari, r.cheval1, r.cheval2, r.numero1, r.numero2, r.cote, r.mise, r.resultat, r.gain_potentiel, h.position as position_reel FROM recommendations r LEFT JOIN results h ON h.date = r.date AND h.race_name LIKE '%' || SUBSTR(r.race_name, 1, 20) || '%' AND h.horse_name = r.cheval1 WHERE r.resultat IS NOT NULL AND r.resultat != '' AND r.date BETWEEN ? AND ? """ cursor = conn.execute(query, (start_date, end_date)) rows = cursor.fetchall() if not rows: conn.close() return { 'summary': { 'total_bets': 0, 'message': 'Aucune donnée disponible pour cette période' } } # Métriques globales stats = { 'total_bets': len(rows), 'gagne': 0, 'perdu': 0, 'mise_totale': 0, 'gain_total': 0, 'by_type': defaultdict(lambda: { 'count': 0, 'gagne': 0, 'mise': 0, 'gain': 0 }) } details = [] for row in rows: date, race_name, type_pari, cheval1, cheval2, numero1, numero2, cote, mise, resultat, gain_potentiel, position = row mise = float(mise or 1) cote = float(cote or 1) stats['mise_totale'] += mise stats['by_type'][type_pari]['mise'] += mise if resultat == 'GAGNE': stats['gagne'] += 1 stats['perdu'] += 0 stats['gain_total'] += mise * cote stats['by_type'][type_pari]['gagne'] += 1 stats['by_type'][type_pari]['gain'] += mise * cote result = 'GAGNE' else: stats['perdu'] += 1 stats['gain_total'] += 0 result = 'PERDU' details.append({ 'date': date, 'race_name': race_name[:30] if race_name else '', 'type_pari': type_pari, 'cheval': cheval1, 'cote': cote, 'mise': mise, 'resultat': result, 'gain': mise * cote if result == 'GAGNE' else 0 }) # Calculer ROI roi = 0 if stats['mise_totale'] > 0: roi = ((stats['gain_total'] - stats['mise_totale']) / stats['mise_totale']) * 100 # Calculer précision precision = 0 if stats['total_bets'] > 0: precision = (stats['gagne'] / stats['total_bets']) * 100 # Métriques par type de pari by_type_results = {} for pari_type, data in stats['by_type'].items(): pari_roi = 0 if data['mise'] > 0: pari_roi = ((data['gain'] - data['mise']) / data['mise']) * 100 pari_precision = 0 if data['count'] > 0: pari_precision = (data['gagne'] / data['count']) * 100 by_type_results[pari_type] = { 'count': data['count'], 'gagne': data['gagne'], 'mise': data['mise'], 'gain': data['gain'], 'roi': round(pari_roi, 1), 'precision': round(pari_precision, 1) } # Précision par rang de prédiction precision_by_rank = get_precision_by_rank(conn, start_date, end_date) conn.close() return { 'period': { 'start': start_date, 'end': end_date }, 'summary': { 'total_bets': stats['total_bets'], 'gagne': stats['gagne'], 'perdu': stats['perdu'], 'precision': round(precision, 1), 'mise_totale': round(stats['mise_totale'], 2), 'gain_total': round(stats['gain_total'], 2), 'roi': round(roi, 1) }, 'by_type': by_type_results, 'precision_by_rank': precision_by_rank, 'details': details[-50:] # Last 50 bets } def get_precision_by_rank(conn, start_date, end_date): """Calcule la précision par rang de prédiction""" query = """ SELECT p.prediction_rank, COUNT(*) as total, SUM(CASE WHEN p.prediction_rank = r.position THEN 1 ELSE 0 END) as hits, SUM(CASE WHEN r.position <= 3 THEN 1 ELSE 0 END) as top3 FROM predictions p INNER JOIN results r ON r.date = p.date AND r.horse_name = p.horse_name WHERE p.date BETWEEN ? AND ? AND p.prediction_rank > 0 AND r.position > 0 GROUP BY p.prediction_rank ORDER BY p.prediction_rank """ cursor = conn.execute(query, (start_date, end_date)) rows = cursor.fetchall() results = {} for rank, total, hits, top3 in rows: precision = (hits / total * 100) if total > 0 else 0 top3_rate = (top3 / total * 100) if total > 0 else 0 results[f'rank_{rank}'] = { 'total': total, 'hits': hits, 'precision': round(precision, 1), 'top3': top3, 'top3_rate': round(top3_rate, 1) } return results def get_daily_stats(days=30): """Statistiques quotidiennes sur les N derniers jours""" conn = get_db() end_date = datetime.now().strftime('%Y-%m-%d') start_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d') query = """ SELECT date, COUNT(*) as total_bets, SUM(CASE WHEN resultat = 'GAGNE' THEN 1 ELSE 0 END) as gagne, SUM(CASE WHEN resultat = 'PERDU' THEN 1 ELSE 0 END) as perdu, SUM(CASE WHEN resultat = 'GAGNE' THEN mise * cote ELSE 0 END) as gains, SUM(mise) as mises FROM recommendations WHERE date BETWEEN ? AND ? AND resultat IS NOT NULL AND resultat != '' GROUP BY date ORDER BY date DESC """ cursor = conn.execute(query, (start_date, end_date)) rows = cursor.fetchall() daily = [] for date, total, gagne, perdu, gains, mises in rows: roi = ((gains - mises) / mises * 100) if mises > 0 else 0 precision = (gagne / total * 100) if total > 0 else 0 daily.append({ 'date': date, 'total_bets': total, 'gagne': gagne, 'perdu': perdu, 'precision': round(precision, 1), 'mises': round(mises, 2), 'gains': round(gains, 2), 'roi': round(roi, 1) }) conn.close() return daily def get_weekly_summary(): """Résumé hebdomadaire""" conn = get_db() # Semaine en cours today = datetime.now() week_start = today - timedelta(days=today.weekday()) week_start_str = week_start.strftime('%Y-%m-%d') query = """ SELECT COUNT(*) as total, SUM(CASE WHEN resultat = 'GAGNE' THEN 1 ELSE 0 END) as gagne, SUM(mise) as mise, SUM(CASE WHEN resultat = 'GAGNE' THEN mise * cote ELSE 0 END) as gain FROM recommendations WHERE date >= ? AND resultat IS NOT NULL AND resultat != '' """ cursor = conn.execute(query, (week_start_str,)) row = cursor.fetchone() total, gagne, mise, gain = row weekly = { 'week_start': week_start_str, 'total_bets': total or 0, 'gagne': gagne or 0, 'mise': round(mise or 0, 2), 'gain': round(gain or 0, 2), 'roi': round(((gain - mise) / mise * 100) if mise and mise > 0 else 0, 1), 'precision': round((gagne / total * 100) if total and total > 0 else 0, 1) } # Semaine dernière last_week_start = week_start - timedelta(days=7) last_week_end = week_start - timedelta(days=1) query = """ SELECT COUNT(*) as total, SUM(CASE WHEN resultat = 'GAGNE' THEN 1 ELSE 0 END) as gagne, SUM(mise) as mise, SUM(CASE WHEN resultat = 'GAGNE' THEN mise * cote ELSE 0 END) as gain FROM recommendations WHERE date BETWEEN ? AND ? AND resultat IS NOT NULL AND resultat != '' """ cursor = conn.execute(query, (last_week_start.strftime('%Y-%m-%d'), last_week_end.strftime('%Y-%m-%d'))) row = cursor.fetchone() total, gagne, mise, gain = row weekly['last_week'] = { 'total_bets': total or 0, 'gagne': gagne or 0, 'mise': round(mise or 0, 2), 'gain': round(gain or 0, 2), 'roi': round(((gain - mise) / mise * 100) if mise and mise > 0 else 0, 1), 'precision': round((gagne / total * 100) if total and total > 0 else 0, 1) } conn.close() return weekly if __name__ == "__main__": import json print("="*60) print("BACKTEST - Analyse des performances") print("="*60) # Test result = calculate_backtest() print(json.dumps(result, indent=2, default=str))