Protocole & définitions

📐 Méthodologie

Tout ce qu'il faut pour reproduire ou auditer la promesse data-driven du site. Pas de boîte noire.

🎯 Pourquoi privilégier les cotes hautes ?

Le site ne cherche plus la cote la plus sûre, il cherche le meilleur couple probabilité × gain. Une cote à 1.20 peut passer souvent, mais le profit réel est minuscule : il faut gagner presque tout le temps pour compenser une seule erreur.

Exemple simple pour 100€ misés :

La règle Phase 7 est donc volontaire : minimum @2.00 par défaut, sweet spot Big Bets autour de @2.20 à @3.50, et handicaps masqués sauf anomalie énorme. Moins de tickets, plus de discipline.

📚 Sources de données

Toutes les sources ci-dessous sont publiques. Le code des fetchers est dans scripts/fetch_*.py sur le repo.

SourceSport(s)ApportCadence
ESPN (scoreboard + core API)TousCalendrier, scores live, cotes ML, classements, blessures US5 min
SofascoreFoot top-5Compositions, blessures, arbitres2 h
Winamax catalogTousCotes 1N2 (publiques, sans login)5 min
ClubEloFootElo des clubs (sur ~30 ans d'historique)20 h
Jeff Sackmann (tennis_atp / tennis_wta)TennisElo + Elo surface + L10 + fatigue + H2H24 h
Football-Data.co.ukFoot 14 liguesClosing odds + calibration ligue (BTTS, +2.5, avg goals)6 h
MLB Stats APIBaseballPitcher partant probable + ERA/WHIP/K9/BB915 min
NHL APIHockeyPace (GF/GA), goalie SV%, splits home/road15 min
Open-MeteoFootMétéo prévue au coup d'envoi (température, pluie, vent)30 min

⚙️ Pipeline

Toutes les 5 minutes (jour) / 30 minutes (nuit), GitHub Actions enchaîne :

  1. Fetchers (scripts/fetch_*.py) : interrogent les sources et produisent des sidecars JSON par domaine.
  2. Patchers (scripts/patch_*.py) : injectent les sidecars dans data.js (le JSON principal).
  3. Snapshot des cotes pré-match (snapshot_odds.py) : fige la cote courante dans odds_history.jsonl avant kickoff. Sans ce snapshot, on ne peut pas calculer le CLV après coup.
  4. Archive des résultats (snapshot_results.py) : append-only dans results_archive.jsonl. Permet une fenêtre de backtest qui ne se rétrécit pas avec le rolling de data.js.
  5. Build health snapshot + finalize inline + commit + push.

Le workflow complet est dans .github/workflows/refresh.yml.

📏 Dictionnaire des métriques

WR (taux de réussite)
Nombre de picks gagnés ÷ nombre total de picks réglés. Pure base, ignore la cote. Un WR 60% sans cote moyenne ne dit rien sur la rentabilité.
ROI flat (mise plate)
(somme_retours − somme_mises) ÷ somme_mises × 100. Mise constante 1 unité par pick. Mesure brute de la rentabilité indépendante du staking.
Edge (avantage marché)
p_modèle − (1 ÷ cote). Si modèle dit 60% et cote = 1.80 (implicite 55.6%), edge = +4.4 pts. Positif = on a un avantage théorique sur le marché.
Kelly fractionnaire
f* = (b·p − q) ÷ b où b = cote − 1, p = prob modèle, q = 1 − p. On applique 0.25× Kelly cap 10% bankroll par défaut : croissance à long terme avec variance contenue. Le Kelly plein est correct mathématiquement mais brûle les bankrolls réelles à cause du modèle imparfait.
Brier score
Σ (p_modèle − résultat)² ÷ N. Mesure l'erreur de prédiction probabiliste. 0 = parfait, 0.25 = pile/face. Un Brier < 0.22 sur ≥150 picks indique un signal réel.
Calibration
Quand le modèle dit "70% de chance", est-ce que 70% de ces prédictions gagnent vraiment ? On bin par tranche de 10pp et on compare prob_moyenne vs win_rate observé. Gap < 5pp partout = bien calibré.
CLV (Closing Line Value)
Cote prise au moment du pick comparée à la cote juste avant kickoff. +CLV moyen sur ≥50 paris = preuve qu'on bat le marché, indépendamment du résultat short-term. C'est le signal que les bookmakers utilisent pour identifier les "smart money".
Tier de fiabilité
  • lock : prob ≥ 72% + ≥3 composants non-marché concordants
  • standard : prob ≥ 55% avec couverture data minimale
  • lowconf : prob ≥ 50% mais data thin
  • skip : prob < 50% ou inversion modèle / marché

🔬 Protocole de backtest

🧠 Signaux du modèle

predictMatch() combine 18 signaux pondérés. Chacun produit une probabilité (ou un nudge) sur l'issue 1N2, puis ils sont agrégés via une moyenne pondérée. Les signaux non disponibles pour un match (data manquante) sont simplement skip.

1. Cote marché (Winamax)
Référence de base, dérivée de 1/cote normalisée (suppression de la marge bookmaker). Poids ~0.35 dans la moyenne. Tous sports.
2. Force des équipes (Elo Clubelo / Sackmann)
Foot : Clubelo daily ratings (1500 baseline, ±100 par victoire/défaite). Tennis : Sackmann ATP/WTA Elo (avec Elo surface : Hard/Clay/Grass). Poids ~0.25.
3. Forme récente (last 5)
5 derniers résultats W/L/D, encodés en string ("WWLDW"). Tous sports. Poids ~0.15.
4. Buts marqués / encaissés (foot)
Moyenne GF/GA sur 5 derniers matchs, alimente le composant Poisson.
5. Poisson (foot uniquement)
Modèle de scores exacts via λ_home × λ_away (Poisson indépendant). Amélioré par la correction Dixon-Coles τ pour scores nuls bas. ρ par ligue (eng.1=-0.07, ita.1=-0.18 etc., 35 entrées) mesuré en CI via likelihood max (scripts/measure_dixon_coles_rho.py) dès qu'une ligue a ≥40 matchs archivés ; sinon fallback littérature (Constantinou-Fenton 2017). Poids ~0.20.
6. Face-à-face (H2H)
Bilan des 3-6 dernières confrontations directes. Source ESPN summary. Poids ~0.10. Foot/basket/hockey.
7. Classement / standings
Position au classement. Différentiel rH-rA → bonus pour la meilleure équipe. Foot top-5 + US sports.
8. Blessures & suspensions
Sources : ESPN (US sports), Sofascore (foot top-5). Chaque absence sévère pénalise ~1.5%, cap ±6%.
9. Compositions probables (foot)
Sofascore top-5. Lecture des starting XI permet de détecter remplacements clés non capturés par "blessures".
10. Météo (foot)
Open-Meteo par stade. Pluie >3mm/h → -0.2 buts xG. Vent >25 km/h → -0.15 buts. Froid <0°C → -0.10 buts.
11. Arbitre (foot top-5)
Sofascore : cartons jaunes/rouges par match. Arbitre sévère → +rouges → -xG.
12. Calibration ligue (Football-Data.co.uk)
Stats historiques par ligue (avg goals, home_wr, draw_rate, BTTS, +2.5). Affine les priors Poisson selon le contexte ligue.
13. Pitcher MLB
Statsapi.mlb.com : ERA, WHIP, K/9, BB/9, hand. Différentiel ERA >0.5 → signal fort en baseball.
14. Goalie + pace NHL
api-web.nhle.com : SV%, GAA du gardien probable. Différentiel SV% >1.5pt → signal pred.reasons. Pace = GF/GA per game. v31.7.25 : hockeyScorePrediction blend last5 (60%) avec season averages (40%) pour réduire la variance ; correction λ adverse par sv% goalie titulaire (ratio (1-sv%)/(1-0.905), clamp [0.85, 1.15]).
15. Congestion (charge 7j) — foot
Nombre de matchs joués dans les 7 derniers jours. ≥3 matchs = fatigue, pénalité ~1.5% par match additionnel, cap ±4%.
16. Repos minimum (jours depuis dernier match) — foot
v31.7.6. Si l'équipe joue à <48h ou <72h après son dernier match : pénalité 1-2.5pt. Si >9-12 jours sans match : rouille, pénalité 0.5-1.2pt. Optimum 3-9j.
17. Voyage longue distance — NBA/NHL/MLB
v31.7.12. Calcul Haversine entre venue précédent et venue courant via stadiums.json. ≥2000km = pénalité 1pt, ≥3500km (transcontinental) = 1.8pt.
18. Motivation contexte fin de saison — foot top-5
v31.7.10. Avril-mai + zone top4 ou bot3 → bonus motivation +1.2pt si une équipe est en zone tendue et l'autre non.

Recalibration automatique chaque semaine : backtest_v2.py tourne sur les paris réglés, calcule l'écart entre proba prédite et WR observé, applique une correction isotonic (PAV) pour garantir la monotonicité. Voir page Crédibilité.

⚠️ Biais & limites

Une question, une remarque méthodologique, un biais que je n'ai pas vu ? Ouvre une issue GitHub. La transparence c'est aussi accepter d'être contredit.