Onder de motorkap

Hoe LotteryCortex werkt

LotteryCortex combineert vier technieken die normaal alleen door quants en data-scientists worden gebruikt. We maken ze toegankelijk voor iedereen die een lot koopt.

Deze pagina beschrijft elk algoritme dat LotteryCortex draait — van data-ingest tot ticket-selectie. Alles is open documenteerbaar: voor elke stap noemen we de bronfile in de codebase. Geen wondermiddel, wél de meest complete publiek gedocumenteerde stack voor loterij-analyse die we konden bouwen.

De pipeline in 7 stappen

Elke trekking doorloopt deze keten. Onderaan vind je per algoritme de details.

  1. 01

    Data-ingest & anomaly-detection

    Per loterij scrapen we de officiële archieven (direct HTML-fetch met Firecrawl-fallback). Elke nieuwe trekking gaat door detectDrawAnomaly(): shape-check, range-check, duplicate-detect en een chi-kwadraat-vergelijking tegen de laatste 200 trekkingen. Verdachte trekkingen worden geblokkeerd voordat ze de historie raken.

  2. 02

    Engine-core: zes basissignalen

    engine-core.ts berekent per nummer zes onafhankelijke scores (Bayes, EMA × 3 half-lifes, PMI, Markov), normaliseert ze naar ranks en combineert ze met getuneede gewichten tot één ensemble-score.

  3. 03

    Meta-learner & GBM stacking

    De zes engine-scores + 6 afgeleide features (interacties, recency, hot/cold streaks) gaan in een per-loterij logistic regression (meta-learner.ts) en een gradient boosted stumps model (gbm.ts). De stacked blender combineert beide out-of-fold zonder leakage.

  4. 04

    Ticket-generatie met anti-clustering

    We samplen duizenden kandidaat-tickets uit de top-scorende nummers, maar verwerpen alles met te veel clustering: max 3 uit dezelfde decade, pariteit binnen historische band, som binnen IQR-band van werkelijke trekkingen.

  5. 05

    Portfolio-optimalisatie

    Greedy submodulaire selectie (portfolio-optimizer.ts) + Markowitz min-variance selector (markowitz.ts) kiezen K tickets die samen de meeste waarschijnlijkheidsmassa dekken met minimale overlap.

  6. 06

    Popularity-correctie & EV-gating

    popularity.ts straft 'mooie' combinaties (verjaardagen ≤ 31, opeenvolgende reeksen, lage helft) want bij een hit moet je delen. ev-gating berekent jackpot-bewuste EV per €1 inzet en geeft 'speel' of 'sla over' advies.

  7. 07

    Walk-forward backtest & auto-tuning

    Elke zondag draait een nested k-fold cross-validation (nested-cv.ts) plus grid-search over engine-gewichten en half-life-presets. Een Thompson-sampling bandit (bandit.ts) selecteert online de best presterende preset op basis van gerealiseerde ROI.

Index — 20 algoritmes

Algoritmes in detail

1. Bayesiaanse frequentie-shrinkage

Hoe vaak een nummer is getrokken, gecorrigeerd voor de hoeveelheid data die we hebben.

  • Pure ruwe frequentie overschat 'hot' nummers wanneer de geschiedenis kort is. We leggen er een Dirichlet-prior overheen met automatische alpha-schatting (dirichletAlphaAuto, method-of-moments op de empirische variantie).
  • Het resultaat is een posterior-kans per nummer die naar 1/N (uniform) krimpt zolang de geschiedenis klein is, en de echte frequentie gaat volgen zodra we genoeg trekkingen hebben.
p̂(n) = (count(n) + α) / (totalDraws · k + α · N)

src/lib/engine-core.ts · src/lib/advanced-stats.ts

2-4. Tijd-gewogen EMA met meerdere half-lifes

Recente trekkingen tellen zwaarder dan oude — drie horizonnen tegelijk.

  • Voor elke trekking i vanaf het einde geeft we gewicht w_i = 0.5^(i / H). We draaien drie EMA's parallel met H = 8, 26 en 78 trekkingen (kort/middel/lang).
  • Korte half-life vangt 'hot streaks' op. Lange half-life voorkomt over-reageren op één rare trekking. De ensemble-blender kiest hoeveel gewicht elke horizon krijgt per loterij — getuneed met grid-search.
EMA_H(n) = Σ_i  0.5^(i/H) · 1[n ∈ draw_i]

src/lib/engine-core.ts (HALF_LIVES = [8, 26, 78])

5. PMI co-occurrence boost

Welke nummers samen verschijnen vaker dan het toeval zou voorspellen?

  • We berekenen Pointwise Mutual Information: PMI(a,b) = log [P(a,b) / (P(a)·P(b))]. Positieve PMI = paar verschijnt vaker samen dan onafhankelijk verwacht.
  • Per kandidaat-nummer sommeren we de PMI met alle nummers van de meest recente trekking. Dat geeft een 'follow-the-pattern' signaal zonder over-fitten: PMI is bounded en wordt geclipt op ±3.
score_PMI(n) = Σ_{m ∈ lastDraw}  clip(log[P(n,m) / (P(n)P(m))], ±3)

src/lib/engine-core.ts

6. Markov-overgangskansen

Vanuit de laatste trekking schatten we de transitie-kans naar het volgende nummer.

  • We bouwen een N×N transitiematrix T waarin T[a,b] = P(b in trekking_t+1 | a in trekking_t), geschat met Laplace-smoothing.
  • Voor de live-score nemen we het gemiddelde van T[a, n] over alle a in de laatste trekking. Cold nummers blijven niet altijd cold; deze stap modelleert eindige geheugen.

src/lib/engine-core.ts

7. Ensemble & getuneede gewichten

De zes signalen worden via gewogen rank-aggregatie samengevoegd.

  • Elk signaal wordt naar rank-percentielen [0, 1] omgezet. Daarna lineair gecombineerd met gewichten w = (w_bayes, w_ema1, w_ema2, w_ema3, w_pmi, w_markov). Default uniform; per loterij getuneed via grid-search + ROI-validatie.
  • Rank-aggregatie is robuust tegen scale-verschillen tussen signalen en voorkomt dat één outlier-score het ensemble domineert.
score(n) = Σ_s  w_s · rank_s(n) / N

src/lib/engine-core.ts · src/lib/engine-tuning.functions.ts

8. Meta-learner (logistic regression, 12 features)

Een per-loterij logreg leert de optimale non-lineaire combinatie van engine-signalen.

  • Feature-vector (12 dims): de 6 engine-ranks + interacties (EMA1·PMI, Bayes·Markov, EMA3·Bayes) + recency + hot streak (verschijningen laatste 10) + cold streak (consecutive absences).
  • Training: L2-geregulariseerde logreg met SGD over de hele historie. Verlies = log-loss op label '1 als nummer in de volgende trekking voorkwam'. Backward-compatible: oude 9-dim modellen blijven werken.
P(n in volgende trekking) = σ(w · features(n) + b)

src/lib/meta-learner.ts

9. Gradient Boosted Decision Stumps

Tweede meta-learner die non-lineaire interacties opvangt zonder neuraal net.

  • Mini-LightGBM clone: itereratief decision stumps op de residuen. Elke stump kiest 1 feature + threshold die de loss maximaal reduceert; we voegen lr · stump.output toe aan de huidige predictie.
  • Output is JSON-serializable en wordt naast de logreg gestored in engine_config.meta_weights.gbm. De stacked blender combineert beide.

src/lib/gbm.ts

10. Out-of-fold stacking blender

Logreg + GBM worden gecombineerd zonder data-leakage.

  • We splitsen de historie in 5 stratified folds. Per fold trainen we logreg + GBM op 4 folds en voorspellen op de overgebleven fold. Die OOF-predicties worden de inputs voor een tweede-laag logreg-meta die de optimale weging leert.
  • Resultaat: kalibreerbare kansen zonder dat de meta-learner zichzelf zijn eigen trainingsdata terug ziet.

src/lib/advanced-stats-v3.ts · src/lib/nested-cv.ts

11. Isotonische & Platt-kalibratie

Ruwe model-kansen worden gemonotoniseerd zodat ze écht kansen zijn.

  • Platt scaling fit een 1-parameter sigmoid over de raw scores op validatiedata. Isotonische regressie (Pool-Adjacent-Violators) is een non-parametrische versie die alleen monotonie afdwingt.
  • We meten Expected Calibration Error (ECE) op out-of-fold predicties; de calibrator met laagste ECE wordt productie.

src/lib/advanced-stats-v2.ts · src/lib/advanced-stats-v3.ts

12. Anti-clustering ticket-generator

Kandidaat-tickets worden gesampled, slechte structuren afgewezen.

  • We trekken nummers proportioneel aan ensemble-score (softmax met temperatuur τ getuneed per loterij). Elke kandidaat wordt gevalideerd:
  • • max 3 uit dezelfde decade (anti-clustering),
  • • som binnen historische IQR-band (geen alleen-laag of alleen-hoog tickets),
  • • pariteits-split binnen empirische distributie,
  • • geen rekenkundige reeks van ≥4 opeenvolgende getallen.
  • Afgewezen kandidaten worden hergesampled tot we N geldige tickets hebben.

src/lib/engine-core.ts (generateAntiClusterTickets)

13. Greedy submodular portfolio selection

Uit duizenden kandidaten kiezen we K tickets die samen maximaal dekken.

  • Probleem: kies K tickets die de som van P(nummer komt) over de geselecteerde tickets maximaliseren, met diminishing returns voor overlap.
  • Submodulaire functies hebben een 1 − 1/e ≈ 63% optimaliteits-garantie voor de greedy oplossing. Per iteratie kiezen we het ticket met de grootste marginal gain en discounten gedekte nummers met factor 0.4.
f(S ∪ {t}) − f(S) = Σ_{n ∈ t}  remain[n] · (½ als bonus)

src/lib/portfolio-optimizer.ts

14. Markowitz min-variance portfolio

Naast dekking minimaliseren we onderlinge correlatie tussen tickets.

  • Correlatie tussen ticket i en j ≈ |overlap| / √(|t_i|·|t_j|). Greedy: kies steeds het ticket met hoogste EV minus λ · Σ corr(huidig, gekozenen).
  • Risico-aversie λ is een tuneable parameter (default 1.2). Hogere λ = meer diversificatie, lagere verwachte score per ticket maar lagere variantie van de totale portfolio-uitbetaling.
util(t) = EV(t) − λ · Σ_{j ∈ picked}  overlap(t, j)

src/lib/markowitz.ts

15. Shared-jackpot popularity correctie

Een hit op een 'populaire' combinatie wordt met meer winnaars gedeeld.

  • popularityScore() telt: % nummers ≤ 31 (verjaardagen), % in onderste helft, opeenvolgende reeksen ≥3, rekenkundige progressies, allemaal-zelfde-eindcijfer, kruisjes/diagonalen op de speelkaart.
  • Op basis daarvan schatten we het aantal mede-winnaars en passen we adjustJackpotForSharing() toe: de EV in de gating-stap gebruikt de verwachte uitbetaling ná deling, niet de bruto jackpot.

src/lib/popularity.ts

16. Jackpot-aware EV-gating

Adviseer 'spelen' alleen als de verwachte waarde positief is.

  • EV = Σ_tier P(tier) · payout(tier) − ticketCost. P(tier) is exact uit combinatorische kansen (binomial coefficients per loterij-formule). Payout(tier) komt uit PRIZE_TIERS — voor de jackpot-klasse vervangen door de huidige jackpot (na popularity-deling).
  • We berekenen ook de break-even jackpot: het bedrag waarvoor EV = 0. Onder dat bedrag toont de UI '⏸ overslaan'.
  • Kelly-criterion bepaalt optionele bet-sizing voor abonnees die meerdere weken plannen.
EV = Σ_t  P(t) · payout(t) − cost   |   break-even = (cost − Σ_{t≠jp} P(t)·payout(t)) / P(jp)

src/lib/ev-gating.ts

17. Regime-detectie (CUSUM + 2-state HMM + particle filter)

Detecteren of de generator van trekkingen zelf is verschoven.

  • CUSUM houdt per nummer een cumulatieve afwijking van de verwachte frequentie bij en alarmeert bij overschrijding van de drempel h.
  • Een 2-state HMM (hot/cold) wordt met Baum-Welch op de frequentie-tijdreeks getraind; transities worden Viterbi-gedecodeerd.
  • Een Sequential Monte-Carlo particle filter onderhoudt 200 deeltjes over de verborgen regime-state en geeft een soft probability per draw.
  • Bij regime-shift krijgt de korte-EMA tijdelijk meer gewicht (online adaptatie).

src/lib/advanced-stats-v2.ts · src/lib/advanced-stats-v3.ts

18. Niveau-3 statistische uitbreidingen

Conformal prediction, Negative-Binomial gap-model, copulas, Shapley, en meer.

  • • Split-conformal prediction → bandbreedte op meta-probs met gegarandeerde coverage.
  • • Dirichlet-Multinomial posterior over de volledige trekking (niet alleen per nummer).
  • • Negative-Binomial gap-model voor over-dispersie tussen verschijningen.
  • • Gaussian-copula voor pair-dependencies die PMI mist.
  • • Permutatie-test per signaal (label-shuffle) → p-waarde voor 'beter dan random'.
  • • SHAP-attributie per ticket (lineaire benadering) → uitleg waarom dit ticket gekozen is.
  • • CVaR / Expected Shortfall over portfolio-uitbetalingen.
  • • Anderson-Darling goodness-of-fit op uniformiteit.

src/lib/advanced-stats-v2.ts · src/lib/advanced-stats-v3.ts

19. Auto-tuner: nested CV + grid-search

Wekelijkse pg_cron herziet engine-gewichten en meta-modellen per loterij.

  • Outer 5-fold stratified CV → out-of-sample ROI-schatting. Inner CV → preset selectie.
  • Grid-search over 6-dim weight-simplex (BMA-weighted) + half-life presets (HALF_LIFE_GRID). Voor elke configuratie: simuleer K tickets per fold, scoor met prize-tiers, aggregeer ROI met 95% bootstrap CI.
  • Een nieuwe preset wordt alleen geaccepteerd als CI-low > baseline-ROI (geen geluksshot).
  • Resultaten gaan in engine_config (DB) en zijn meteen live voor alle abonnees.

src/lib/engine-tuning.functions.ts · src/routes/api/public/hooks/auto-tune.ts

20. Thompson-sampling bandit (online preset-selectie)

Tussen auto-tunes door schakelt een bandit live tussen presets op basis van actuele ROI.

  • Per preset houden we een Beta(α, β) posterior: α = winnen (ROI > 0), β = verliezen. Bij elke productie-pick samplen we uit elke Beta en kiezen de hoogste sample.
  • Updates komen uit de tracker: zodra een trekking afgelopen is, krijgen de gekozen presets hun α of β +1.
  • Resultaat: zelfs zonder volledige her-tuning convergeert het systeem online naar de beste preset.
pick = argmax_p  Beta(α_p, β_p).sample()

src/lib/bandit.ts

21. Walk-forward backtest

Iedere strategie wordt getoetst zonder toekomstkennis (geen leakage).

  • Vanaf MIN_HISTORY itereren we trekking voor trekking. Per trekking bouwen we het model op uit ENKEL trekkingen daarvoor, genereren N tickets, en scoren tegen de werkelijke trekking.
  • We aggregeren hits per tier, gemiddelde ROI, cumulatieve P&L én een random-baseline ter controle. Een strategie 'wint' alleen als het CI-band boven random ligt.

src/lib/backtest.functions.ts

22. Wheeling-systemen (covering designs)

Wiskundige garantie: bij M juiste nummers in de pool altijd een Y-uit-X win.

  • We implementeren full wheels, key wheels en abbreviated covering designs. Voor elke (pool-size, ticket-size, garantie) kiezen we de combinatorisch optimale set.
  • De wheel-generator combineert met de portfolio-optimizer: kandidaten worden eerst gewheel'd, daarna submodulair gekozen.

src/lib/wheel.functions.ts

Eerlijk gezegd

Loterijen zijn en blijven kansspelen. Geen enkele analyse kan een trekking voorspellen. Wat LotteryCortex wel doet: je helpen betere beslissingen te nemen over welke combinaties je speelt, wanneer je meer inzet en wanneer je een week overslaat.

Loterij-trekkingen zijn ontworpen om willekeurig te zijn. Geen enkel algoritme — hier of elders — kan de uitkomst voorspellen. Wat we wél meetbaar doen: betere nummer-spreiding kiezen, populaire combinaties vermijden (zodat je minder hoeft te delen bij een hit), en eerlijk advies geven om over te slaan wanneer de verwachte waarde negatief is. De auto-tuner valideert wekelijks dat onze ROI-curve significant boven random ligt — als dat ooit niet meer zo is, lees je dat hier eerst.

Waarom LotteryCortex?

De meeste loterij-tools zijn 10+ jaar oud, alleen Engelstalig en desktop-first. LotteryCortex is modern, mobile-first en EU-gericht — met een AI-motor die continu leert van nieuwe trekkingen.