📐 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 :
- Cote 1.20 : gain net 20€. Une défaite efface cinq victoires.
- Cote 2.50 : gain net 150€. Avec une vraie probabilité proche de 45-50%, le rendement devient intéressant.
- Cote 4.00+ : gain très fort, mais variance élevée. Le site exige donc un edge plus haut et une mise plus petite.
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.
| Source | Sport(s) | Apport | Cadence |
|---|---|---|---|
| ESPN (scoreboard + core API) | Tous | Calendrier, scores live, cotes ML, classements, blessures US | 5 min |
| Sofascore | Foot top-5 | Compositions, blessures, arbitres | 2 h |
| Winamax catalog | Tous | Cotes 1N2 (publiques, sans login) | 5 min |
| ClubElo | Foot | Elo des clubs (sur ~30 ans d'historique) | 20 h |
| Jeff Sackmann (tennis_atp / tennis_wta) | Tennis | Elo + Elo surface + L10 + fatigue + H2H | 24 h |
| Football-Data.co.uk | Foot 14 ligues | Closing odds + calibration ligue (BTTS, +2.5, avg goals) | 6 h |
| MLB Stats API | Baseball | Pitcher partant probable + ERA/WHIP/K9/BB9 | 15 min |
| NHL API | Hockey | Pace (GF/GA), goalie SV%, splits home/road | 15 min |
| Open-Meteo | Foot | Mé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 :
- Fetchers (
scripts/fetch_*.py) : interrogent les sources et produisent des sidecars JSON par domaine. - Patchers (
scripts/patch_*.py) : injectent les sidecars dansdata.js(le JSON principal). - Snapshot des cotes pré-match (
snapshot_odds.py) : fige la cote courante dansodds_history.jsonlavant kickoff. Sans ce snapshot, on ne peut pas calculer le CLV après coup. - Archive des résultats (
snapshot_results.py) : append-only dansresults_archive.jsonl. Permet une fenêtre de backtest qui ne se rétrécit pas avec le rolling dedata.js. - 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) ÷ boù 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
- Univers : tous les matchs réglés dans
data.js(fenêtre rolling 13 jours) +results_archive.jsonl(append-only depuis le déploiement de l'archive). - Capture cote : la cote prise en compte est celle figée par
snapshot_odds.pydansodds_history.jsonlau plus proche du coup d'envoi (typiquement 5-15 min avant). Pas de cote post-match, pas de cote "early". - Modèle évalué : la vraie fonction
predictMatch()embarquée danspronostics.html, exécutée en Python viascripts/model_loader.py(V8 embarqué) — pas une réimplémentation simplifiée. - Filtrage : on évalue tous les picks que le modèle aurait surfacés (lock + standard + lowconf), pas une sélection rétroactive. Les "skip" sont aussi tracés mais hors-ROI (ils représentent ce que le modèle refuse de jouer).
- Paris annulés : exclus du calcul de WR et de ROI. Comptés séparément dans les métadonnées.
- Staking évalué : Kelly 0.25× cap 10% bankroll, démarrage 100 unités. ROI flat 1u/pick reporté en parallèle pour comparaison.
- Cron :
backtest.ymlrejoue tout chaque dimanche, écritbacktest_report_v2.{json,md}à la racine. - Reproductibilité : tout est public. Tu peux
git cloneet relancerpython scripts/backtest_v2.pysur ta machine. Les sorties.json+.mdsont dans le repo, comparables historiquement. - Transparence symétrique (v31.7.27) : on publie aussi les 10 plus beaux gains et les 10 pires picks en symétrie pour ne pas biaiser le framing négatif. Les séries publiques (longest win/lose + streak courante) sont aussi visibles, pour montrer que les "froids" font partie du jeu.
🧠 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 :
hockeyScorePredictionblend 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
- Faible n par segment : les ROI par sport / par ligue sur quelques semaines peuvent être pure variance. Il faut typiquement ≥150 paris dans une catégorie pour qu'un ROI ait du sens. Avant ça, c'est du bruit.
- Look-ahead bias possible : le modèle utilise des features (forme L5, classements, blessures) qui sont scrapées à un moment donné. Si une ligne d'historique scrape les blessures de "maintenant" pour évaluer un match passé, on a un look-ahead. Mitigation : les snapshots sont datés, mais 100% de l'historique pré-snapshot est imparfait.
- Survivorship bias léger : seuls les events que ESPN+Sofascore couvrent sont inclus. Les ligues sous-couvertes (Asie, Amérique du Sud) ont moins de signaux disponibles donc des picks plus volatiles. Ne pas extrapoler la perf moyenne aux sous-segments mal couverts.
- Variance court terme : un WR cible 60% sur le long terme reste compatible avec des séries de 4-5 défaites consécutives. 3-5 pertes d'affilée sont normales, ne pas casser son Kelly après une bad run. Le hasard reste irréductible.
- Modèle calibré sur le passé : les paramètres (poids des composants, seuils des tiers) ont été tunés sur l'historique. Une rupture structurelle (changement de règle, nouveau format) peut dégrader la perf jusqu'à recalibrage. Le journal des changements modèle est dans le commit log.
- Pas de garantie : "Les performances passées ne garantissent pas les performances futures." Phrase légale mais aussi vraie. Ce site est un outil d'aide à la décision, pas une promesse de gains.
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.