Methodology
The mechanism behind every claim on the landing page — model, tiers, comparison math, verification stack, code.
The model
Two predictors blended at training time:
- A Poisson goal-rate model (Dixon-Coles correction for low-scoring outcomes, per-league rho fitted from historical scoreline frequencies)
- An XGBoost gradient-boosted classifier trained on form, ELO, expected-goals, lineups, injuries, market movement signals
- Blended weights are optimised on a rolling holdout; weights refit weekly via
workers/jobs/weekly_blend_refit.py
Retrained weekly on every completed match across 280+ leagues via workers/jobs/weekly_retrain.py (Sunday 03:00 UTC). Promotion stays manual — a new model only goes live after a comparison report (scripts/compare_models.py) and the operator flips the MODEL_VERSION env on Railway.
Calibration
The raw model outputs are not betting probabilities yet — they tend to be over-confident at the tails. We run two calibration stages:
- Shrinkage toward Pinnacle (sharp-book anchor) scaled by data tier
- Platt scaling per market (1X2, O/U 2.5, BTTS, AH, DC) fitted on settled bets; 2-feature logistic for O/U 2.5
- Isotonic regression as the final layer (one-time fit during training)
Platt weights refit weekly viascripts/fit_platt_live.py.
Bot strategies
Production runs ~16 paper-trading bots simultaneously, each with its own market / league / edge-threshold filter. They share the same prediction backbone; the filters differ:
- Calibrated — promotion-grade. The only tier that places real money via the Coolbet placer. ~6 bots today.
- Beta — paper-only, accumulating live evidence after a backtest passes. Auto-promoted by
scripts/weekly_bot_review.pyat ROI ≥ +10% / CLV ≥ +5% on 60+ settled. - Active — paper-only, observation tier.
- Retired — failed experiments. Excluded from headline numbers but kept in shadow_bets so they continue to be evaluated — reactivation gated on shadow performance recovery.
How pick rows are computed
Every settled bet on the public ledger represents one row in our simulated_bets table. The pipeline writes a row before kickoff with:
match_id+kickoff_utc— the fixturemarket+selection— what we're bettingodds_at_pick+recommended_bookmaker— the price + bookedge_percent+calibrated_prob— the model's edge estimateplaced_at_utc— timestamp of the row insert
After the match completes, settlement adds result, pnl, closing_odds, clv, clv_pinnacle. All historical rows are immutable — we never edit a settled bet.
How competitor comparisons work
For each competitor we pull their public per-bet history, filter to the same time window + same markets + same settlement state, and compute ROI assuming a €10 flat stake on every side. Scripts live in the engine repo:
scripts/production_audit_vs_winnerodds.py— WinnerOdds public GraphQL endpoint atapp.winnerodds.com:4000scripts/audit_vs_signalodds.py— SignalOdds paginated HTML atsignalodds.com/predictions/pastscripts/audit_vs_deepbetting.py— DeepBetting/backend/api/predictions-api.php?type=stats
The output of each audit lands at ledger/comparison_*.json — same hash committed by GitHub Actions, anyone can clone the repo and re-run.
Set your own stake
The landing comparison uses €10 flat as a clean apples-to-apples unit. Drag the slider below to see what the same matched-window ROI translates to at any per-bet stake.
Stake simulator
per bet · pnl scales linearly
Per-bet stake assumed flat. Numbers scale linearly because ROI is a percentage — doubling the stake doubles every euro figure. For Kelly-sized comparisons see the next section.
Why flat stake, not Kelly
Conventional wisdom says Kelly sizing maximises bankroll growth when your edge estimate is well-calibrated. We checked. On the same n=1,181 production-cohort pre-match sample since 2026-05-04:
- Flat €10 stake — ROI +9.08% (€612 on €6,741 staked)
- Kelly-weighted — ROI +7.31% (same picks, sized by model edge × bankroll fraction)
Flat beatsKelly by ~1.8pp on our current sample. Reason: Kelly sizes up the highest-edge picks, and our highest-edge picks are exactly where the model's known longshot miscalibration hurts most (see "Conditional miscalibration at high odds" in MODEL_WHITEPAPER §11). Kelly amplifies the bets we're most overconfident on.
We surface flat €10 as the publishable headline because (a) it honestly matches the comparison baseline, and (b) it's currently our best strategy. Once the calibration fix lands (CAL-ALPHA-ODDS), we'll re-run this comparison — Kelly should win after the high-odds correction.
Competitor stake methods, as best we can tell from their public surfaces: WinnerOdds publishes "unit" stakes (Kelly-like internally); SignalOdds shows EV%-weighted; DeepBetting shows per-confidence-tier flat; Tipstrr shows "level stakes" (flat); Forebet doesn't publish stake — we apply €10 flat at our end to settle their picks. Translation: our €10-flat comparison is internally consistent on OUR side; per-competitor stake differences add minor noise we can't correct for from outside their books.
Per-competitor caveats
Each competitor row on the landing is the headline number. A few have noteworthy structure worth knowing:
- Forebet — algorithmic predictor with a free public history. Their +15.33% headline is driven entirely by Over/Under 2.5 (+35.67% on n=611 in this window); their 1X2 picks alone return essentially break-even at +0.23% on n=823. The OU lift is consistent with a low-scoring stretch of European fixtures during this audit window — likely to mean-revert as the new season starts. We show the headline honestly; the OU dependency is what to watch.
- Tipstrr — only monthly aggregates are public, per-bet detail is paywalled. The audit pools three active football tipsters (
star-tips,mls-value,main-draws-model-top-euros) and covers all football bet types (1X2 + O/U + AH + BTTS) — slightly wider than ours (1X2 + O/U only). The wider scope works in their favour; even so, they're at -5.22% in the matched window. - Betaminic — public strategies list is auth-walled (free signup gate plus paid tiers). Status is
auth_requiredinledger/comparison_betaminic.jsonwith the activation lever documented (BETAMINIC_COOKIEenv). Not displayed on the landing comparison until we have credible data. - WinnerOdds + SignalOdds + DeepBetting — fully public per-bet history, no paywall, scope notes pinned to the same 1X2 + OU 2.5 cohort as ours.
Why the comparison count differs from the hero count
The landing page hero shows 1,181 settled bets at +9.08% ROI— that's our full production cohort across 1X2 + O/U 2.5 + BTTS since the calibrated tier launched.
The comparison card on the landing shows 989 bets at +11.91% ROI — a subset, because SignalOdds and DeepBetting both publish 1X2 + O/U only, not BTTS. To stay apples-to-apples we drop BTTS from BOTH sides. Dropping BTTS actually lifts our ROI from +9.08% to +11.91% because our BTTS model is weaker than 1X2/OU (see Known Limitations in MODEL_WHITEPAPER.md). Both numbers are real; they measure different cohorts.
Why the window starts 2026-05-04: that's the day our calibrated bot tier launched. Going further back would mix in pre-calibration bets we don't deploy real money on. The window will grow as more weeks of live evidence accumulate.
Verification stack
- Live API — /api/v1/track-record(settled) and /api/v1/upcoming (live picks). Public, no auth, JSON, CORS-open.
- Daily GitHub commit— every night at 22:45 UTC, a deterministic JSON of the day's settled bets is committed to ledger/YYYY-MM-DD.json and signed by
github-actions[bot]. - Bitcoin blockchain anchor via OpenTimestamps. Each daily JSON is hashed and committed into a Bitcoin block. After ~1-6 hours the proof is permanent — anyone with
ots verifycan confirm "this exact JSON existed at this Bitcoin block height" without trusting GitHub or us.
Source code
Both repos are open source on GitHub:
- msellin/odds-intel-engine — the model, training, scheduler, scrapers, ledger
- msellin/odds-intel-web — this site, the picks page, the API routes
What we're not
This is a research project that publishes its track record. It is not financial advice, not a guarantee of future returns, and not a substitute for your own judgment. Past results are real (every row is in the public ledger), but the future is variance. Bet responsibly.
Have a question this page doesn't answer? Open an issue on GitHub or message the Telegram channel.